pax_global_header00006660000000000000000000000064151277260170014522gustar00rootroot0000000000000052 comment=2a7ccef207be68f5e1365467ab064c4c98e829f6 deathkiller-jazz2-native-2a7ccef/000077500000000000000000000000001512772601700171105ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/.editorconfig000066400000000000000000000214111512772601700215640ustar00rootroot00000000000000# Remove the line below if you want to inherit .editorconfig settings from higher directories root = true # C# files [*.cs] #### Core EditorConfig Options #### # Indentation and spacing indent_size = 4 indent_style = tab tab_width = 4 # New line preferences end_of_line = crlf insert_final_newline = false #### .NET Coding Conventions #### # this. and Me. preferences dotnet_style_qualification_for_event = false:silent dotnet_style_qualification_for_field = false:silent dotnet_style_qualification_for_method = false:silent dotnet_style_qualification_for_property = false:silent # Language keywords vs BCL types preferences dotnet_style_predefined_type_for_locals_parameters_members = true:silent dotnet_style_predefined_type_for_member_access = true:silent # Parentheses preferences dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent # Modifier preferences dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent # Expression-level preferences csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_inlined_variable_declaration = true:suggestion csharp_style_throw_expression = true:suggestion dotnet_style_coalesce_expression = true:suggestion dotnet_style_collection_initializer = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion dotnet_style_null_propagation = true:silent dotnet_style_object_initializer = true:suggestion dotnet_style_prefer_auto_properties = false:silent dotnet_style_prefer_compound_assignment = true:suggestion dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion # Field preferences dotnet_style_readonly_field = false:suggestion # Parameter preferences dotnet_code_quality_unused_parameters = all:suggestion #### C# Coding Conventions #### # var preferences csharp_style_var_elsewhere = false:silent csharp_style_var_for_built_in_types = false:silent csharp_style_var_when_type_is_apparent = false:silent # Expression-bodied members csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_constructors = false:silent csharp_style_expression_bodied_indexers = true:silent csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent csharp_style_expression_bodied_methods = false:silent csharp_style_expression_bodied_operators = false:silent csharp_style_expression_bodied_properties = true:silent # Pattern matching preferences csharp_style_pattern_matching_over_as_with_null_check = true:suggestion csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion # Null-checking preferences csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async # Code-block preferences csharp_prefer_braces = true:suggestion # Expression-level preferences csharp_prefer_simple_default_expression = false:suggestion csharp_style_pattern_local_over_anonymous_function = true:suggestion csharp_style_prefer_index_operator = true:suggestion csharp_style_prefer_range_operator = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent #### C# Formatting Rules #### # New line preferences csharp_new_line_before_catch = false csharp_new_line_before_else = false csharp_new_line_before_finally = false csharp_new_line_before_members_in_anonymous_types = true csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_open_brace = accessors,methods,properties,types csharp_new_line_between_query_expression_clauses = true # Indentation preferences csharp_indent_block_contents = true csharp_indent_braces = false csharp_indent_case_contents = true csharp_indent_case_contents_when_block = false csharp_indent_labels = one_less_than_current csharp_indent_switch_labels = true # Space preferences csharp_space_after_cast = false csharp_space_after_colon_in_inheritance_clause = true csharp_space_after_comma = true csharp_space_after_dot = false csharp_space_after_keywords_in_control_flow_statements = true csharp_space_after_semicolon_in_for_statement = true csharp_space_around_binary_operators = before_and_after csharp_space_around_declaration_statements = false csharp_space_before_colon_in_inheritance_clause = true csharp_space_before_comma = false csharp_space_before_dot = false csharp_space_before_open_square_brackets = false csharp_space_before_semicolon_in_for_statement = false csharp_space_between_empty_square_brackets = false csharp_space_between_method_call_empty_parameter_list_parentheses = false csharp_space_between_method_call_name_and_opening_parenthesis = false csharp_space_between_method_call_parameter_list_parentheses = false csharp_space_between_method_declaration_empty_parameter_list_parentheses = false csharp_space_between_method_declaration_name_and_open_parenthesis = false csharp_space_between_method_declaration_parameter_list_parentheses = false csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # Wrapping preferences csharp_preserve_single_line_blocks = true csharp_preserve_single_line_statements = true # IDE0063: Use simple 'using' statement csharp_prefer_simple_using_statement = false:suggestion # IDE0066: Convert switch statement to expression csharp_style_prefer_switch_expression = false:suggestion # C++ files [*.{c++,cc,cpp,cppm,cxx,h,h++,hh,hpp,hxx,inl,ipp,ixx,tlh,tli}] #### Core EditorConfig Options #### # Indentation and spacing indent_style = tab tab_width = 4 # New line preferences end_of_line = crlf insert_final_newline = false #### C++ Coding Conventions #### cpp_generate_documentation_comments = doxygen_slash_star cpp_indent_braces = false cpp_indent_multi_line_relative_to = innermost_parenthesis cpp_indent_within_parentheses = indent cpp_indent_preserve_within_parentheses = true cpp_indent_case_contents = true cpp_indent_case_labels = true cpp_indent_case_contents_when_block = false cpp_indent_lambda_braces_when_parameter = false cpp_indent_goto_labels = one_left cpp_indent_preprocessor = leftmost_column cpp_indent_access_specifiers = false cpp_indent_namespace_contents = true cpp_indent_preserve_comments = false cpp_new_line_before_open_brace_namespace = ignore cpp_new_line_before_open_brace_type = ignore cpp_new_line_before_open_brace_function = ignore cpp_new_line_before_open_brace_block = same_line cpp_new_line_before_open_brace_lambda = same_line cpp_new_line_scope_braces_on_separate_lines = false cpp_new_line_close_brace_same_line_empty_type = false cpp_new_line_close_brace_same_line_empty_function = false cpp_new_line_before_catch = false cpp_new_line_before_else = false cpp_new_line_before_while_in_do_while = false cpp_space_before_function_open_parenthesis = remove cpp_space_within_parameter_list_parentheses = false cpp_space_between_empty_parameter_list_parentheses = false cpp_space_after_keywords_in_control_flow_statements = true cpp_space_within_control_flow_statement_parentheses = false cpp_space_before_lambda_open_parenthesis = false cpp_space_within_cast_parentheses = false cpp_space_after_cast_close_parenthesis = false cpp_space_within_expression_parentheses = false cpp_space_before_block_open_brace = true cpp_space_between_empty_braces = true cpp_space_before_initializer_list_open_brace = true cpp_space_within_initializer_list_braces = true cpp_space_preserve_in_initializer_list = true cpp_space_before_open_square_bracket = false cpp_space_within_square_brackets = false cpp_space_before_empty_square_brackets = false cpp_space_between_empty_square_brackets = false cpp_space_group_square_brackets = true cpp_space_within_lambda_brackets = false cpp_space_between_empty_lambda_brackets = false cpp_space_before_comma = false cpp_space_after_comma = true cpp_space_remove_around_member_operators = true cpp_space_before_inheritance_colon = true cpp_space_before_constructor_colon = true cpp_space_remove_before_semicolon = true cpp_space_after_semicolon = true cpp_space_remove_around_unary_operator = true cpp_space_around_binary_operator = insert cpp_space_around_assignment_operator = insert cpp_space_pointer_reference_alignment = left cpp_space_around_ternary_operator = insert cpp_wrap_preserve_blocks = never deathkiller-jazz2-native-2a7ccef/.github/000077500000000000000000000000001512772601700204505ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/.github/ISSUE_TEMPLATE/000077500000000000000000000000001512772601700226335ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/.github/ISSUE_TEMPLATE/bug_report.yml000066400000000000000000000032741512772601700255340ustar00rootroot00000000000000name: Bug report description: Report a bug in Jazz² Resurrection labels: - bug body: - type: markdown attributes: value: | - Write a descriptive issue title above. - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. - Verify that you are using the latest stable or development version of Jazz² Resurrection. - type: input attributes: label: Jazz² Resurrection version description: > Specify the Git commit hash if using a development or non-official build. If you use a custom build, please test if your issue is reproducible in official builds too. placeholder: "1.7.0" validations: required: true - type: input attributes: label: System information description: | Specify the OS version, and when relevant hardware information. For graphics-related issues, specify the GPU model and driver version. placeholder: Windows 10, Intel HD Graphics 620 (27.20.100.9616) validations: required: true - type: textarea attributes: label: Issue description description: | Describe your issue briefly. What doesn't work, and how do you expect it to work instead? You can include images or videos with drag and drop, and format code blocks or logs with ``` tags. validations: required: true - type: textarea attributes: label: Steps to reproduce description: | List of steps or sample code that reproduces the issue. Having reproducible issues is a prerequisite for contributors to be able to solve them. validations: required: truedeathkiller-jazz2-native-2a7ccef/.github/ISSUE_TEMPLATE/feature_request.yml000066400000000000000000000025241512772601700265640ustar00rootroot00000000000000name: Feature request description: Request a feature to be added or improved in Jazz² Resurrection labels: - enhancement body: - type: markdown attributes: value: | - Write a descriptive issue title above. - Search [open](https://github.com/godotengine/godot/issues) and [closed](https://github.com/godotengine/godot/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been requested. - Verify that you are using the latest stable or development version of Jazz² Resurrection. - type: input attributes: label: Jazz² Resurrection version description: > Specify the Git commit hash if using a development or non-official build. If you use a custom build, please test if your issue is reproducible in official builds too. placeholder: "1.7.0" validations: required: true - type: textarea attributes: label: Problem or limitation description: | Describe the problem or limitation you're encountering and why a new feature is needed. You can include images or videos with drag and drop, and format code blocks or logs with ``` tags. validations: required: true - type: textarea attributes: label: Proposed solution description: | Describe a solution to resolve the problem you've outlined above. validations: required: true deathkiller-jazz2-native-2a7ccef/.github/workflows/000077500000000000000000000000001512772601700225055ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/.github/workflows/android.yml000066400000000000000000000207021512772601700246510ustar00rootroot00000000000000name: 'Android' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: ARM - BuildType: Release Platform: x64 runs-on: 'ubuntu-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt update -y sudo apt install -y cmake curl g++ - name: 'Configure CMake' run: | case "${{ matrix.Platform }}" in ARM ) architecture="arm64-v8a;armeabi-v7a" universalApk="ON" ;; x64 ) architecture="x86_64" universalApk="OFF" ;; * ) exit 1 ;; esac rm -f ./Content/Translations/*.po export JAVA_HOME=$JAVA_HOME_17_X64 cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_BUILD_ANDROID=ON -D NCINE_UNIVERSAL_APK="$universalApk" -D NCINE_NDK_ARCHITECTURES="$architecture" - name: 'Build' run: | export JAVA_HOME=$JAVA_HOME_17_X64 make -j $(nproc) -C ./_build/ cd ./_build/Android/ gradle assemble${{ matrix.BuildType }} - name: 'Create Package' run: | case "${{ matrix.BuildType }}" in Debug ) buildDir="debug" fileSuffix="debug" ;; * ) buildDir="release" fileSuffix="release-unsigned" ;; esac case "${{ matrix.Platform }}" in ARM ) filename="app-universal-$fileSuffix.apk" ;; x64 ) filename="app-x86_64-$fileSuffix.apk" ;; * ) exit 1 ;; esac mkdir ./_package/ $ANDROID_HOME/build-tools/34.0.0/zipalign -p 4 "./_build/Android/app/build/outputs/apk/$buildDir/$filename" "./_package/Jazz2.apk" cp -f ./LICENSE ./_package/LICENSE if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then echo 'Pull requests are not signed!' else echo 'Signing APK file...' echo '${{ secrets.ANDROID_KEYSTORE_FILE }}' | base64 --decode > ./_keystore.jks $ANDROID_HOME/build-tools/34.0.0/apksigner sign --ks-key-alias Main --ks "./_keystore.jks" --ks-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" --key-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" "./_package/Jazz2.apk" fi - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Android_${{ matrix.Platform }} path: ./_package/ # Testing builds with workarounds Workarounds: if: ${{ false }} # Temporarily disabled, it will be removed in the future as it's no longer needed. strategy: fail-fast: false matrix: include: - BuildType: Release Platform: ARM WorkaroundSwitch: WITH_FIXED_BATCH_SIZE - BuildType: Release Platform: x64 WorkaroundSwitch: WITH_FIXED_BATCH_SIZE runs-on: 'ubuntu-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt-get update -y sudo apt-get install -y cmake curl g++ - name: 'Configure CMake' run: | case "${{ matrix.Platform }}" in ARM ) architecture="arm64-v8a;armeabi-v7a" universalApk="ON" ;; x64 ) architecture="x86_64" universalApk="OFF" ;; * ) exit 1 ;; esac rm -f ./Content/Translations/*.po export JAVA_HOME=$JAVA_HOME_17_X64 cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_BUILD_ANDROID=ON -D NCINE_UNIVERSAL_APK="$universalApk" -D NCINE_NDK_ARCHITECTURES="$architecture" -D DEATH_DEBUG=ON -D NCINE_WITH_FIXED_BATCH_SIZE=10 #cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_BUILD_ANDROID=ON -D NCINE_UNIVERSAL_APK="$universalApk" -D NCINE_NDK_ARCHITECTURES="$architecture" -D DEATH_DEBUG=ON -D NCINE_INPUT_DEBUGGING=ON - name: 'Build' run: | export JAVA_HOME=$JAVA_HOME_17_X64 make -j $(nproc) -C ./_build/ cd ./_build/Android/ gradle assemble${{ matrix.BuildType }} - name: 'Create Package' run: | case "${{ matrix.BuildType }}" in Debug ) buildDir="debug" fileSuffix="debug" ;; * ) buildDir="release" fileSuffix="release-unsigned" ;; esac case "${{ matrix.Platform }}" in ARM ) filename="app-universal-$fileSuffix.apk" ;; x64 ) filename="app-x86_64-$fileSuffix.apk" ;; * ) exit 1 ;; esac mkdir ./_package/ $ANDROID_HOME/build-tools/34.0.0/zipalign -p 4 "./_build/Android/app/build/outputs/apk/$buildDir/$filename" "./_package/Jazz2d.apk" cp -f ./LICENSE ./_package/LICENSE if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then echo 'Pull requests are not signed!' else echo 'Signing APK file...' echo '${{ secrets.ANDROID_KEYSTORE_FILE }}' | base64 --decode > ./_keystore.jks $ANDROID_HOME/build-tools/34.0.0/apksigner sign --ks-key-alias Main --ks "./_keystore.jks" --ks-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" --key-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" "./_package/Jazz2d.apk" fi - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Android_DEBUG_${{ matrix.Platform }} path: ./_package/ # Testing builds with online multiplayer MultiplayerPreview: #if: ${{ false }} # Temporarily disabled, it will be removed in the future as it's no longer needed. strategy: fail-fast: false matrix: include: - BuildType: Release Platform: ARM - BuildType: Release Platform: x64 runs-on: 'ubuntu-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt-get update -y sudo apt-get install -y cmake curl g++ - name: 'Configure CMake' run: | case "${{ matrix.Platform }}" in ARM ) architecture="arm64-v8a;armeabi-v7a" universalApk="ON" ;; x64 ) architecture="x86_64" universalApk="OFF" ;; * ) exit 1 ;; esac rm -f ./Content/Translations/*.po export JAVA_HOME=$JAVA_HOME_17_X64 cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_BUILD_ANDROID=ON -D NCINE_UNIVERSAL_APK="$universalApk" -D NCINE_NDK_ARCHITECTURES="$architecture" -D WITH_MULTIPLAYER=ON - name: 'Build' run: | export JAVA_HOME=$JAVA_HOME_17_X64 make -j $(nproc) -C ./_build/ cd ./_build/Android/ gradle assemble${{ matrix.BuildType }} - name: 'Create Package' run: | case "${{ matrix.BuildType }}" in Debug ) buildDir="debug" fileSuffix="debug" ;; * ) buildDir="release" fileSuffix="release-unsigned" ;; esac case "${{ matrix.Platform }}" in ARM ) filename="app-universal-$fileSuffix.apk" ;; x64 ) filename="app-x86_64-$fileSuffix.apk" ;; * ) exit 1 ;; esac mkdir ./_package/ $ANDROID_HOME/build-tools/34.0.0/zipalign -p 4 "./_build/Android/app/build/outputs/apk/$buildDir/$filename" "./_package/Jazz2.apk" cp -f ./LICENSE ./_package/LICENSE if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then echo 'Pull requests are not signed!' else echo 'Signing APK file...' echo '${{ secrets.ANDROID_KEYSTORE_FILE }}' | base64 --decode > ./_keystore.jks $ANDROID_HOME/build-tools/34.0.0/apksigner sign --ks-key-alias Main --ks "./_keystore.jks" --ks-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" --key-pass "pass:${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" "./_package/Jazz2.apk" fi - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Android_MultiplayerPreview_${{ matrix.Platform }} path: ./_package/deathkiller-jazz2-native-2a7ccef/.github/workflows/docs.yml000066400000000000000000000026161512772601700241650ustar00rootroot00000000000000name: 'API Docs' on: push: branches: - 'master' pull_request_target: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: 'ubuntu-latest' #runs-on: 'ubuntu-22.04' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Checkout Repository (m.css)' uses: actions/checkout@v4 with: repository: 'deathkiller/m.css' ref: 'death-main' ssh-key: ${{ secrets.DOCS_DEPLOY_PRIVATE_KEY }} path: 'm.css' - name: 'Create Build Environment' run: | sudo apt update -y #sudo apt install doxygen sudo apt install texlive-base texlive-latex-extra texlive-fonts-extra texlive-fonts-recommended pip3 install jinja2 natsort Pygments - name: 'Install Doxygen' uses: ssciwr/doxygen-install@v1 with: version: "1.15.0" - name: 'Build' run: | mkdir -p ./build/docs/ ./m.css/documentation/doxygen.py ./Docs/Config.py - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_ApiDocs path: ./build/docs/html/deathkiller-jazz2-native-2a7ccef/.github/workflows/emscripten.yml000066400000000000000000000041001512772601700253740ustar00rootroot00000000000000name: 'Emscripten' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Backend: GLFW Mode: - BuildType: Release Backend: SDL2 Mode: - BuildType: Release Backend: GLFW Mode: SharewareDemo - BuildType: Release Backend: SDL2 Mode: SharewareDemo runs-on: 'ubuntu-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Install Emscripten SDK' run: | sudo update-alternatives --install /usr/bin/python python /usr/bin/python3 10 cd .. git clone https://github.com/emscripten-core/emsdk.git cd emsdk #EMSDK_VERSION=latest EMSDK_VERSION=3.1.64 ./emsdk install $EMSDK_VERSION ./emsdk activate $EMSDK_VERSION - name: 'Configure CMake' run: | case "${{ matrix.Mode }}" in SharewareDemo ) sharewareDemo="ON" ;; * ) sharewareDemo="OFF" ;; esac export OS=emscripten export CC=emcc source ../emsdk/emsdk_env.sh rm -f ./Content/Translations/*.po emcmake cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D SHAREWARE_DEMO_ONLY=$sharewareDemo - name: 'Build' run: | make -j $(nproc) -C ./_build/ #- name: 'Create Package' # run: | # mkdir ./_package/ # cp -f ./_build/jazz2 ./_package/jazz2 # cp -f -r ./Content/ ./_package/Content/ # #- name: 'Upload Package' # uses: actions/upload-artifact@v4 # with: # name: Jazz2_Emscripten_${{ matrix.CC }}_${{ matrix.Backend }} # path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/linux.yml000066400000000000000000000202211512772601700243640ustar00rootroot00000000000000name: 'Linux' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x64 Backend: GLFW CC: gcc CXX: g++ - BuildType: Release Platform: x64 Backend: GLFW CC: clang CXX: clang++ - BuildType: Release Platform: x64 Backend: SDL2 CC: gcc CXX: g++ - BuildType: Release Platform: x64 Backend: SDL2 CC: clang CXX: clang++ #runs-on: 'ubuntu-latest' runs-on: 'ubuntu-22.04' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt update -y sudo apt install -y cmake curl g++ libgl1-mesa-dev libopenmpt-dev libcurl4-openssl-dev rpm - if: matrix.CC == 'clang' name: 'Configure Clang Compiler' uses: egor-tensin/setup-clang@v1 with: version: 15 - name: 'Configure CMake' run: | export CC=${{ matrix.CC }} export CXX=${{ matrix.CXX }} rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_STRIP_BINARIES=ON -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_ASSEMBLE_DEB=ON -D NCINE_ASSEMBLE_RPM=ON -D NCINE_WITH_GLEW=OFF - name: 'Build' run: | make -j $(nproc) -C ./_build/ - name: 'Create Package' run: | make package -C ./_build/ mkdir ./_package/ cp -f ./_build/jazz2 ./_package/jazz2 cp -f ./_build/*.deb ./_package/jazz2.deb cp -f ./_build/*.rpm ./_package/jazz2.rpm cp -f -r ./Content/ ./_package/Content/ cp -f ./LICENSE ./_package/LICENSE case "${{ matrix.CC }}" in gcc ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}" ;; clang ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_Clang" ;; * ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_${{ matrix.CC }}" ;; esac echo "artifactPath=$artifactPath" >> $GITHUB_ENV - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_package/ Build-AppImage: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x64 Backend: GLFW CC: gcc CXX: g++ - BuildType: Release Platform: x64 Backend: SDL2 CC: gcc CXX: g++ #runs-on: 'ubuntu-latest' runs-on: 'ubuntu-22.04' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt-get update -y sudo apt-get install -y cmake curl g++ libgl1-mesa-dev libglew2.2 libglfw3 libsdl2-2.0-0 libopenal1 libvorbisfile3 libopenmpt-dev libcurl4-openssl-dev fuse - name: 'Configure CMake' run: | export CC=${{ matrix.CC }} export CXX=${{ matrix.CXX }} rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_STRIP_BINARIES=ON -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_WITH_GLEW=OFF -D NCINE_LINUX_PACKAGE=jazz2 -D NCINE_PACKAGED_CONTENT_PATH=ON -D CMAKE_INSTALL_PREFIX=/usr - name: 'Build' run: | DESTDIR=../_appdir/ make install -j $(nproc) -C ./_build/ - name: 'Create AppImage' run: | if [ ! -f linuxdeploy-x86_64.AppImage ]; then curl -s -S -O --location 'https://github.com/darealshinji/linuxdeploy-plugin-checkrt/releases/download/continuous/linuxdeploy-plugin-checkrt.sh' curl -s -S -O --location 'https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage' chmod a+x linuxdeploy-x86_64.AppImage linuxdeploy-plugin-checkrt.sh fi mv -f ./_appdir/usr/share/jazz2/Content/ ./_appdir/usr/bin/Content/ rm -f -r ./_appdir/usr/share/jazz2/ rm -f -r ./_appdir/usr/share/doc/ LDAI_OUTPUT="./_build/jazz2.AppImage" ARCH=x86_64 ./linuxdeploy-x86_64.AppImage --appdir=./_appdir/ --icon-filename=jazz2 --desktop-file=./_appdir/usr/share/applications/jazz2.desktop --executable=./_appdir/usr/bin/jazz2 --custom-apprun=./Sources/AppRun --output=appimage --plugin checkrt artifactPath="Jazz2_${{ matrix.Platform }}_${{ matrix.Backend }}.AppImage" echo "artifactPath=$artifactPath" >> $GITHUB_ENV - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_build/jazz2.AppImage CodeQL: name: 'CodeQL Analyze' strategy: fail-fast: false runs-on: 'ubuntu-latest' permissions: security-events: write steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Initialize CodeQL' uses: github/codeql-action/init@v3 with: languages: cpp - name: 'Create Build Environment' run: | sudo apt-get update -y sudo apt-get install -y cmake curl g++ libgl1-mesa-dev libopenmpt-dev libcurl4-openssl-dev - name: 'Configure CMake and Build' run: | export CC=gcc export CXX=g++ cmake -B ./_build/ -D CMAKE_BUILD_TYPE=Release -D NCINE_WITH_GLEW=OFF make -j $(nproc) -C ./_build/ - name: 'Perform CodeQL Analysis' uses: github/codeql-action/analyze@v3 # Testing builds with online multiplayer MultiplayerPreview: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x64 Backend: SDL2 CC: gcc CXX: g++ #runs-on: 'ubuntu-latest' runs-on: 'ubuntu-22.04' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt update -y sudo apt install -y cmake curl g++ libgl1-mesa-dev libopenmpt-dev libcurl4-openssl-dev rpm sudo apt install -y libdw-dev - if: matrix.CC == 'clang' name: 'Configure Clang Compiler' uses: egor-tensin/setup-clang@v1 with: version: 15 - name: 'Configure CMake' run: | export CC=${{ matrix.CC }} export CXX=${{ matrix.CXX }} rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_WITH_GLEW=OFF -D DEATH_DEBUG_SYMBOLS=ON -D WITH_MULTIPLAYER=ON - name: 'Build' run: | make -j $(nproc) -C ./_build/ - name: 'Create Package' run: | make package -C ./_build/ mkdir ./_package/ cp -f ./_build/jazz2 ./_package/jazz2 cp -f ./_build/jazz2.pdb ./_package/jazz2.pdb cp -f -r ./Content/ ./_package/Content/ cp -f ./LICENSE ./_package/LICENSE cp -f ./Docs/Snippets/ServerConfiguration.json ./_package/Jazz2.Server.config case "${{ matrix.CC }}" in gcc ) artifactPath="Jazz2_Linux_MultiplayerPreview_${{ matrix.Platform }}_${{ matrix.Backend }}" ;; clang ) artifactPath="Jazz2_Linux_MultiplayerPreview_${{ matrix.Platform }}_${{ matrix.Backend }}_Clang" ;; * ) artifactPath="Jazz2_Linux_MultiplayerPreview_${{ matrix.Platform }}_${{ matrix.Backend }}_${{ matrix.CC }}" ;; esac echo "artifactPath=$artifactPath" >> $GITHUB_ENV - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_package/deathkiller-jazz2-native-2a7ccef/.github/workflows/linux_cc.yml000066400000000000000000000054231512772601700250400ustar00rootroot00000000000000name: 'Linux (Cross-compile)' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: aarch64 Backend: GLFW CC: gcc CXX: g++ - BuildType: Release Platform: aarch64 Backend: SDL2 CC: gcc CXX: g++ runs-on: 'ubuntu-22.04' #runs-on: 'ubuntu-22.04-arm' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Configure Cross-Compile Support (ARM64)' uses: cyberjunk/gha-ubuntu-cross@v4 with: arch: arm64 - name: 'Create Build Environment' run: | sudo apt install -y cmake curl sudo apt install -y libgl1-mesa-dev:arm64 libglfw3-dev:arm64 libsdl2-dev:arm64 libopenal-dev:arm64 libopenmpt-dev:arm64 libcurl4-openssl-dev:arm64 zlib1g:arm64 #sudo apt install -y libgl1-mesa-dev libglfw3-dev libsdl2-dev libopenal-dev libopenmpt-dev libcurl4-openssl-dev zlib1g - name: 'Configure CMake' run: | export CC=aarch64-linux-gnu-${{ matrix.CC }} export CXX=aarch64-linux-gnu-${{ matrix.CXX }} #export CC=${{ matrix.CC }} #export CXX=${{ matrix.CXX }} rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D CMAKE_SYSTEM_NAME=Linux -D CMAKE_SYSTEM_PROCESSOR=aarch64 -D CMAKE_FIND_ROOT_PATH=/usr/aarch64-linux-gnu -D NCINE_STRIP_BINARIES=ON -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_WITH_GLEW=OFF #cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_STRIP_BINARIES=ON -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_WITH_GLEW=OFF - name: 'Build' run: | make -j $(nproc) -C ./_build/ - name: 'Create Package' run: | mkdir ./_package/ cp -f ./_build/jazz2 ./_package/jazz2 cp -f -r ./Content/ ./_package/Content/ cp -f ./LICENSE ./_package/LICENSE case "${{ matrix.CC }}" in gcc ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}" ;; clang ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_Clang" ;; * ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_${{ matrix.CC }}" ;; esac echo "artifactPath=$artifactPath" >> $GITHUB_ENV - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_package/deathkiller-jazz2-native-2a7ccef/.github/workflows/linux_legacy.yml_000066400000000000000000000044451512772601700260610ustar00rootroot00000000000000name: 'Linux (Legacy)' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x64 Backend: GLFW CC: gcc CXX: g++ - BuildType: Release Platform: x64 Backend: SDL2 CC: gcc CXX: g++ runs-on: 'ubuntu-20.04' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Create Build Environment' run: | sudo apt update -y sudo apt install -y cmake curl g++ libgl1-mesa-dev libglew-dev libglfw3-dev libsdl2-dev libopenal-dev libopenmpt-dev libcurl4-openssl-dev rpm - name: 'Configure CMake' run: | export CC=${{ matrix.CC }} export CXX=${{ matrix.CXX }} rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_STRIP_BINARIES=ON -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_ASSEMBLE_DEB=ON -D NCINE_ASSEMBLE_RPM=ON -D NCINE_DOWNLOAD_DEPENDENCIES=OFF -D NCINE_WITH_GLEW=OFF - name: 'Build' run: | make -j $(nproc) -C ./_build/ - name: 'Create Package' run: | make package -C ./_build/ mkdir ./_package/ cp -f ./_build/jazz2 ./_package/jazz2 cp -f ./_build/*.deb ./_package/jazz2.deb cp -f ./_build/*.rpm ./_package/jazz2.rpm cp -f -r ./Content/ ./_package/Content/ cp -f ./LICENSE ./_package/LICENSE case "${{ matrix.CC }}" in gcc ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_Legacy" ;; clang ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_LegacyClang" ;; * ) artifactPath="Jazz2_Linux_${{ matrix.Platform }}_${{ matrix.Backend }}_Legacy${{ matrix.CC }}" ;; esac echo "artifactPath=$artifactPath" >> $GITHUB_ENV - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/macos.yml000066400000000000000000000064021512772601700243340ustar00rootroot00000000000000name: 'MacOS' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: ARM64 Backend: GLFW - BuildType: Release Platform: ARM64 Backend: SDL2 - BuildType: Release Platform: x64 Backend: GLFW - BuildType: Release Platform: x64 Backend: SDL2 runs-on: 'macos-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Configure CMake' run: | case "${{ matrix.Platform }}" in ARM64 ) architecture="arm64" withVorbis="OFF" ;; x64 ) architecture="x86_64" withVorbis="ON" ;; * ) exit 1 ;; esac rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D NCINE_STRIP_BINARIES=ON -D CMAKE_OSX_ARCHITECTURES="$architecture" -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_WITH_VORBIS=$withVorbis - name: 'Build' run: | make -j 3 -C ./_build/ - name: 'Create Package' run: | make package -C ./_build/ mkdir ./_package/ cp -f ./_build/*.dmg ./_package/ cp -f ./LICENSE ./_package/LICENSE - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_MacOS_${{ matrix.Platform }}_${{ matrix.Backend }} path: ./_package/ # Testing builds with online multiplayer MultiplayerPreview: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: ARM64 Backend: SDL2 - BuildType: Release Platform: x64 Backend: SDL2 runs-on: 'macos-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Configure CMake' run: | case "${{ matrix.Platform }}" in ARM64 ) architecture="arm64" withVorbis="OFF" ;; x64 ) architecture="x86_64" withVorbis="ON" ;; * ) exit 1 ;; esac rm -f ./Content/Translations/*.po cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D CMAKE_OSX_ARCHITECTURES="$architecture" -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_WITH_VORBIS=$withVorbis -D DEATH_DEBUG_SYMBOLS=ON -D WITH_MULTIPLAYER=ON - name: 'Build' run: | make -j 3 -C ./_build/ - name: 'Create Package' run: | make package -C ./_build/ mkdir ./_package/ cp -f ./_build/*.dmg ./_package/ cp -f ./LICENSE ./_package/LICENSE cp -f ./Docs/Snippets/ServerConfiguration.json ./_package/Jazz2.Server.config - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_MacOS_MultiplayerPreview_${{ matrix.Platform }}_${{ matrix.Backend }} path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/switch.yml000066400000000000000000000050601512772601700245320ustar00rootroot00000000000000name: 'Switch' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release runs-on: 'ubuntu-latest' container: #image: devkitpro/devkita64:20241023 image: devkitpro/devkita64:latest options: --user root steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Configure CMake' run: | rm -f ./Content/Translations/*.po git config --global --add safe.directory '*' # DEATH_TRACE_ASYNC is causing crashes on startup, so it's disabled for now cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D CMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/Switch.cmake -D DEATH_TRACE_ASYNC=OFF - name: 'Build' run: | make -j $(nproc) -C ./_build/ - name: 'Create Package' run: | mkdir ./_package/ cp -f ./_build/jazz2.nro ./_package/Jazz2.nro cp -f ./LICENSE ./_package/LICENSE - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Switch path: ./_package/ # Testing builds with online multiplayer MultiplayerPreview: strategy: fail-fast: false matrix: include: - BuildType: Release runs-on: 'ubuntu-latest' container: #image: devkitpro/devkita64:20241023 image: devkitpro/devkita64:latest options: --user root steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Configure CMake' run: | rm -f ./Content/Translations/*.po git config --global --add safe.directory '*' # DEATH_TRACE_ASYNC is causing crashes on startup, so it's disabled for now cmake -B ./_build/ -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D CMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/Switch.cmake -D WITH_MULTIPLAYER=ON -D DEATH_TRACE_ASYNC=OFF - name: 'Build' run: | make -j $(nproc) -C ./_build/ - name: 'Create Package' run: | mkdir ./_package/ cp -f ./_build/jazz2.nro ./_package/Jazz2.nro cp -f ./LICENSE ./_package/LICENSE - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Switch_MultiplayerPreview path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/uwp.yml000066400000000000000000000153251512772601700240510ustar00rootroot00000000000000name: 'Universal Windows Platform' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Backend: ANGLE Platform: x64 ArchExts: - BuildType: Release Backend: ANGLE Platform: x64 ArchExts: AVX - BuildType: Release Backend: ANGLE Platform: x64 ArchExts: AVX2 - BuildType: Release Backend: Mesa Platform: x64 ArchExts: AVX2 runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - name: 'Configure CMake' run: | switch('${{ matrix.Backend }}') { 'ANGLE' { $withAngle = 'ON' } 'Mesa' { $withAngle = 'OFF' } } switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } } rm -force ".\Content\Translations\*.po" if ($env:GITHUB_EVENT_NAME -eq 'pull_request') { Write-Output "Pull requests are not signed!" $certPath = "" $certPass = "" } else { $currentDirectory = Get-Location $certBytes = [System.Convert]::FromBase64String("${{ secrets.UWP_CERTIFICATE_FILE }}") $certPath = Join-Path -Path $currentDirectory -ChildPath "_cert.pfx" [IO.File]::WriteAllBytes("$certPath", $certBytes) $certPass = "${{ secrets.UWP_CERTIFICATE_PASSWORD }}" } cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D CMAKE_SYSTEM_NAME=WindowsStore -D CMAKE_SYSTEM_VERSION="10.0" -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D NCINE_ARCH_EXTENSIONS="${{ matrix.ArchExts }}" -D NCINE_STRIP_BINARIES=ON -D NCINE_UWP_CERTIFICATE_PATH="$certPath" -D NCINE_UWP_CERTIFICATE_PASSWORD="$certPass" -D NCINE_WITH_ANGLE="$withAngle" -D DEATH_TRACE=OFF - name: 'Build' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch -p:UapAppxPackageBuildMode="Sideload" -p:AppxBundlePlatforms="${{ matrix.Platform }}" -p:AppxPackageDir=".\_out\" - name: 'Create Package' run: | cd .\_build\_out\ $cerFiles = Get-ChildItem -Path .\*\*.cer $msixbundlePath = (Get-ChildItem -Path .\*\*.msixbundle)[0] cd ..\..\ mkdir _package if ($cerFiles.Count -gt 0) { $cerPath = $cerFiles[0] Move-Item -Path "$cerPath" -Destination ".\_package\Jazz2.cer" } Move-Item -Path "$msixbundlePath" -Destination ".\_package\Jazz2.msixbundle" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" if ('${{ matrix.ArchExts }}') { $artifactPath = 'Jazz2_UWP_${{ matrix.Backend }}_${{ matrix.Platform }}_${{ matrix.ArchExts }}' } else { $artifactPath = 'Jazz2_UWP_${{ matrix.Backend }}_${{ matrix.Platform }}' } Add-Content -Path $env:GITHUB_ENV -Value "artifactPath=$artifactPath" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_package/ # Testing builds with online multiplayer MultiplayerPreview: strategy: fail-fast: false matrix: include: - BuildType: Release Backend: ANGLE Platform: x64 ArchExts: AVX runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - name: 'Configure CMake' run: | switch('${{ matrix.Backend }}') { 'ANGLE' { $withAngle = 'ON' } 'Mesa' { $withAngle = 'OFF' } } switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } } rm -force ".\Content\Translations\*.po" if ($env:GITHUB_EVENT_NAME -eq 'pull_request') { Write-Output "Pull requests are not signed!" $certPath = "" $certPass = "" } else { $currentDirectory = Get-Location $certBytes = [System.Convert]::FromBase64String("${{ secrets.UWP_CERTIFICATE_FILE }}") $certPath = Join-Path -Path $currentDirectory -ChildPath "_cert.pfx" [IO.File]::WriteAllBytes("$certPath", $certBytes) $certPass = "${{ secrets.UWP_CERTIFICATE_PASSWORD }}" } cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -D CMAKE_SYSTEM_NAME=WindowsStore -D CMAKE_SYSTEM_VERSION="10.0" -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D NCINE_ARCH_EXTENSIONS="${{ matrix.ArchExts }}" -D NCINE_STRIP_BINARIES=ON -D NCINE_UWP_CERTIFICATE_PATH="$certPath" -D NCINE_UWP_CERTIFICATE_PASSWORD="$certPass" -D NCINE_WITH_ANGLE="$withAngle" -D WITH_MULTIPLAYER=ON - name: 'Build' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch -p:UapAppxPackageBuildMode="Sideload" -p:AppxBundlePlatforms="${{ matrix.Platform }}" -p:AppxPackageDir=".\_out\" - name: 'Create Package' run: | cd .\_build\_out\ $cerFiles = Get-ChildItem -Path .\*\*.cer $msixbundlePath = (Get-ChildItem -Path .\*\*.msixbundle)[0] cd ..\..\ mkdir _package if ($cerFiles.Count -gt 0) { $cerPath = $cerFiles[0] Move-Item -Path "$cerPath" -Destination ".\_package\Jazz2.cer" } Move-Item -Path "$msixbundlePath" -Destination ".\_package\Jazz2.msixbundle" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" if ('${{ matrix.ArchExts }}') { $artifactPath = 'Jazz2_UWP_MultiplayerPreview_${{ matrix.Backend }}_${{ matrix.Platform }}_${{ matrix.ArchExts }}' } else { $artifactPath = 'Jazz2_UWP_${{ matrix.Backend }}_${{ matrix.Platform }}' } Add-Content -Path $env:GITHUB_ENV -Value "artifactPath=$artifactPath" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: ${{ env.artifactPath }} path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/windows.yml000066400000000000000000000165071512772601700247330ustar00rootroot00000000000000name: 'Windows' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x86 ArchExts: SSE Backend: GLFW - BuildType: Release Platform: x86 ArchExts: SSE Backend: SDL2 - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: GLFW - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: SDL2 - BuildType: Release Platform: x64 ArchExts: AVX2 Backend: GLFW - BuildType: Release Platform: x64 ArchExts: AVX2 Backend: SDL2 runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - if: matrix.Platform != 'ARM64' name: 'Download Build Dependencies' run: | mkdir Libs cd Libs #Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL5/releases/download/v5.0.9/VC-LTL-5.0.9-Binary.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL/releases/download/v4.1.3/VC-LTL-4.1.3-Binary-VS2019.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" 7z x ".\VC-LTL-Binary.7z" -o"VC-LTL" - name: 'Configure CMake' run: | $archexts = '${{ matrix.ArchExts }}' switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' if(($archexts -eq 'SSE') -or ($archexts -eq 'SSE2')) { $archexts = '' } } 'ARM64' { $arch = 'ARM64EC' } } rm -force ".\Content\Translations\*.po" cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D NCINE_ARCH_EXTENSIONS=$archexts -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_STRIP_BINARIES=ON -D CMAKE_GENERATOR_TOOLSET=v142 - name: 'Build' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } 'ARM64' { $arch = 'ARM64EC' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch - name: 'Create Package' run: | mkdir _package Move-Item -Path ".\_build\Release\Jazz2.exe" -Destination ".\_package\Jazz2.exe" Move-Item -Path ".\_build\Release\Jazz2.pdb" -Destination ".\_package\Jazz2.pdb" Move-Item -Path ".\_build\Release\*.dll" -Destination ".\_package\" Move-Item -Path ".\Content\" -Destination ".\_package\Content\" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Windows_${{ matrix.Platform }}_${{ matrix.ArchExts }}_${{ matrix.Backend }} path: ./_package/ # Testing builds with online multiplayer MultiplayerPreview: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: SDL2 runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - if: matrix.Platform != 'ARM64' name: 'Download Build Dependencies' run: | mkdir Libs cd Libs #Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL5/releases/download/v5.0.9/VC-LTL-5.0.9-Binary.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL/releases/download/v4.1.3/VC-LTL-4.1.3-Binary-VS2019.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" 7z x ".\VC-LTL-Binary.7z" -o"VC-LTL" - name: 'Configure CMake' run: | $archexts = '${{ matrix.ArchExts }}' switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' if(($archexts -eq 'SSE') -or ($archexts -eq 'SSE2')) { $archexts = '' } } 'ARM64' { $arch = 'ARM64EC' } } rm -force ".\Content\Translations\*.po" cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D NCINE_ARCH_EXTENSIONS=$archexts -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D CMAKE_GENERATOR_TOOLSET=v142 -D WITH_MULTIPLAYER=ON - name: 'Build' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } 'ARM64' { $arch = 'ARM64EC' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch - name: 'Create Package' run: | mkdir _package Move-Item -Path ".\_build\Release\Jazz2.exe" -Destination ".\_package\Jazz2.exe" Move-Item -Path ".\_build\Release\Jazz2.pdb" -Destination ".\_package\Jazz2.pdb" Move-Item -Path ".\_build\Release\*.dll" -Destination ".\_package\" - name: 'Configure CMake (Dedicated Server)' run: | $archexts = '${{ matrix.ArchExts }}' switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' if(($archexts -eq 'SSE') -or ($archexts -eq 'SSE2')) { $archexts = '' } } 'ARM64' { $arch = 'ARM64EC' } } cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D NCINE_ARCH_EXTENSIONS=$archexts -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D CMAKE_GENERATOR_TOOLSET=v142 -D WITH_MULTIPLAYER=ON -D DEDICATED_SERVER=ON - name: 'Build (Dedicated Server)' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } 'ARM64' { $arch = 'ARM64EC' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch - name: 'Create Package (Dedicated Server)' run: | Move-Item -Path ".\_build\Release\Jazz2.Server.exe" -Destination ".\_package\Jazz2.Server.exe" Move-Item -Path ".\_build\Release\Jazz2.Server.pdb" -Destination ".\_package\Jazz2.Server.pdb" Move-Item -Path ".\Content\" -Destination ".\_package\Content\" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" Move-Item -Path ".\Docs\Snippets\ServerConfiguration.json" -Destination ".\_package\Jazz2.Server.config" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Windows_MultiplayerPreview_${{ matrix.Platform }}_${{ matrix.ArchExts }}_${{ matrix.Backend }} path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/windows_clang.yml000066400000000000000000000060761512772601700260770ustar00rootroot00000000000000name: 'Windows (Clang)' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x86 ArchExts: SSE Backend: GLFW - BuildType: Release Platform: x86 ArchExts: SSE Backend: SDL2 - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: GLFW - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: SDL2 - BuildType: Release Platform: x64 ArchExts: AVX2 Backend: GLFW - BuildType: Release Platform: x64 ArchExts: AVX2 Backend: SDL2 runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - name: 'Download Build Dependencies' run: | mkdir Libs cd Libs Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL5/releases/download/v5.0.9/VC-LTL-5.0.9-Binary.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" 7z x ".\VC-LTL-Binary.7z" -o"VC-LTL" - name: 'Configure CMake' run: | $archexts = '${{ matrix.ArchExts }}' switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' if(($archexts -eq 'SSE') -or ($archexts -eq 'SSE2')) { $archexts = '' } } } rm -force ".\Content\Translations\*.po" cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D NCINE_ARCH_EXTENSIONS=$archexts -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_STRIP_BINARIES=ON -D NCINE_COPY_DEPENDENCIES=ON -T ClangCL - name: 'Build' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch - name: 'Create Package' run: | mkdir _package Move-Item -Path ".\_build\Release\Jazz2.exe" -Destination ".\_package\Jazz2.exe" Move-Item -Path ".\_build\Release\Jazz2.pdb" -Destination ".\_package\Jazz2.pdb" Move-Item -Path ".\_build\Release\*.dll" -Destination ".\_package\" Move-Item -Path ".\Content\" -Destination ".\_package\Content\" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Windows_${{ matrix.Platform }}_${{ matrix.ArchExts }}_${{ matrix.Backend }}_Clang path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/windows_sln.yml000066400000000000000000000041151512772601700255770ustar00rootroot00000000000000name: 'Windows (Fallback)' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x86 - BuildType: Release Platform: x64 runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - name: 'Download Build Dependencies' run: | mkdir Libs cd Libs git clone https://github.com/deathkiller/jazz2-libraries.git . git checkout #Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL5/releases/download/v5.0.5/VC-LTL-5.0.5-Binary.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL/releases/download/v4.1.3/VC-LTL-4.1.3-Binary-VS2019.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" 7z x ".\VC-LTL-Binary.7z" -o"VC-LTL" - name: 'Build' run: | rm -force ".\Content\Translations\*.po" msbuild ".\Jazz2.sln" -t:Rebuild -p:Configuration=${{ matrix.BuildType }} -p:Platform=${{ matrix.Platform }} -p:VC_LTL_Root=".\Libs\VC-LTL\" -p:OutDir="..\_build\" - name: 'Create Package' run: | mkdir _package Move-Item -Path ".\_build\Jazz2.exe" -Destination ".\_package\Jazz2.exe" Move-Item -Path ".\_build\Jazz2.pdb" -Destination ".\_package\Jazz2.pdb" Move-Item -Path ".\Content\" -Destination ".\_package\Content\" Move-Item -Path ".\Sources\nCine\Shaders\" -Destination ".\_package\Content\Shaders\" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Windows_${{ matrix.Platform }}_Default path: ./_package/ deathkiller-jazz2-native-2a7ccef/.github/workflows/windows_v143.yml000066400000000000000000000065531512772601700255100ustar00rootroot00000000000000name: 'Windows (v143)' on: push: branches: - 'master' pull_request: types: [ opened, synchronize ] workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Build: strategy: fail-fast: false matrix: include: - BuildType: Release Platform: x86 ArchExts: SSE Backend: GLFW - BuildType: Release Platform: x86 ArchExts: SSE Backend: SDL2 - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: GLFW - BuildType: Release Platform: x64 ArchExts: SSE2 Backend: SDL2 - BuildType: Release Platform: x64 ArchExts: AVX2 Backend: GLFW - BuildType: Release Platform: x64 ArchExts: AVX2 Backend: SDL2 - BuildType: Release Platform: ARM64 Backend: GLFW - BuildType: Release Platform: ARM64 Backend: SDL2 runs-on: 'windows-latest' steps: - name: 'Checkout Repository' uses: actions/checkout@v4 with: fetch-depth: 0 - name: 'Setup MSBuild' uses: microsoft/setup-msbuild@v1.3.2 - if: matrix.Platform != 'ARM64' name: 'Download Build Dependencies' run: | mkdir Libs cd Libs Invoke-WebRequest -uri "https://github.com/Chuyu-Team/VC-LTL5/releases/download/v5.0.9/VC-LTL-5.0.9-Binary.7z" -Method "GET" -Outfile "VC-LTL-Binary.7z" 7z x ".\VC-LTL-Binary.7z" -o"VC-LTL" - name: 'Configure CMake' run: | $archexts = '${{ matrix.ArchExts }}' switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' if(($archexts -eq 'SSE') -or ($archexts -eq 'SSE2')) { $archexts = '' } } 'ARM64' { $arch = 'ARM64EC' } } rm -force ".\Content\Translations\*.po" cmake -B ".\_build\" -D CMAKE_BUILD_TYPE=${{ matrix.BuildType }} -A $arch -D CMAKE_SYSTEM_PROCESSOR=$arch -D CMAKE_SYSTEM_VERSION=10 -D NCINE_ARCH_EXTENSIONS=$archexts -D NCINE_PREFERRED_BACKEND=${{ matrix.Backend }} -D NCINE_STRIP_BINARIES=ON -D CMAKE_GENERATOR_TOOLSET=v143 - name: 'Build' run: | switch('${{ matrix.Platform }}') { 'x86' { $arch = 'Win32' } 'x64' { $arch = 'x64' } 'ARM64' { $arch = 'ARM64EC' } } cd .\_build\ msbuild ".\Jazz2.sln" -p:Configuration=${{ matrix.BuildType }} -p:Platform=$arch - name: 'Create Package' run: | mkdir _package Move-Item -Path ".\_build\Release\Jazz2.exe" -Destination ".\_package\Jazz2.exe" Move-Item -Path ".\_build\Release\Jazz2.pdb" -Destination ".\_package\Jazz2.pdb" Move-Item -Path ".\_build\Release\*.dll" -Destination ".\_package\" Move-Item -Path ".\Content\" -Destination ".\_package\Content\" Move-Item -Path ".\LICENSE" -Destination ".\_package\LICENSE" - name: 'Upload Package' uses: actions/upload-artifact@v4 with: name: Jazz2_Windows_${{ matrix.Platform }}_${{ matrix.ArchExts }}_${{ matrix.Backend }}_v143 path: ./_package/ deathkiller-jazz2-native-2a7ccef/.gitignore000066400000000000000000000141761512772601700211110ustar00rootroot00000000000000/Sign.props /Libs/ /desktop.ini /desktop.ico ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Ww][Ii][Nn]32/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Oo]ut/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # ASP.NET Scaffolding ScaffoldingReadMe.txt # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ # Fody - auto-generated XML schema FodyWeavers.xsddeathkiller-jazz2-native-2a7ccef/CMakeLists.txt000066400000000000000000000125141512772601700216530ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.15) # Policies if(POLICY CMP0127) cmake_policy(SET CMP0127 NEW) endif() if(POLICY CMP0141) cmake_policy(SET CMP0141 NEW) endif() if(POLICY CMP0144) cmake_policy(SET CMP0144 NEW) endif() if(NOT CMAKE_CONFIGURATION_TYPES) get_property(_haveMultiConfigGenerator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) if(_haveMultiConfigGenerator) set(CMAKE_CONFIGURATION_TYPES "Debug;Release") endif() endif() if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(_defaultBuildtype "Debug") message(STATUS "Setting build configuration to \"${_defaultBuildtype}\" as none was specified") set(CMAKE_BUILD_TYPE "${_defaultBuildtype}" CACHE STRING "Build configuration" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release") endif() # Project metadata set(NCINE_ROOT ${CMAKE_SOURCE_DIR}) set(NCINE_SOURCE_DIR "${NCINE_ROOT}/Sources") set(NCINE_APP "jazz2") set(NCINE_APP_NAME "Jazz² Resurrection") set(NCINE_APP_DESCRIPTION "Open-source reimplementation of Jazz Jackrabbit 2") set(NCINE_APP_DESCRIPTION_FULL "Jazz² Resurrection is reimplementation of the game Jazz Jackrabbit 2 released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension and MLLE.\n\nFurther information can be found here: https://deat.tk/jazz2/") set(NCINE_APP_VENDOR "Dan R.") set(NCINE_REVERSE_DNS "jazz2.resurrection") set(NCINE_VERSION "3.5.0") project(Jazz2 VERSION "${NCINE_VERSION}" DESCRIPTION "${NCINE_APP_NAME}" HOMEPAGE_URL "https://deat.tk/jazz2/" LANGUAGES CXX C) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) set_property(GLOBAL PROPERTY USE_FOLDERS ON) if(NOT CMAKE_GENERATOR_PLATFORM OR "${CMAKE_GENERATOR_PLATFORM}" STREQUAL "${CMAKE_SYSTEM_PROCESSOR}") if(EMSCRIPTEN) message(STATUS "Compiling for architecture: WASM (on ${CMAKE_SYSTEM_PROCESSOR} machine)") elseif(NINTENDO_SWITCH) message(STATUS "Compiling for architecture: ${CMAKE_SYSTEM_PROCESSOR} (Nintendo Switch)") elseif(APPLE AND CMAKE_OSX_ARCHITECTURES) message(STATUS "Compiling for architecture: ${CMAKE_OSX_ARCHITECTURES} (on ${CMAKE_SYSTEM_PROCESSOR} machine)") else() message(STATUS "Compiling for architecture: ${CMAKE_SYSTEM_PROCESSOR}") endif() else() message(STATUS "Compiling for architecture: ${CMAKE_GENERATOR_PLATFORM} (on ${CMAKE_SYSTEM_PROCESSOR} machine)") endif() if(APPLE AND CMAKE_OSX_ARCHITECTURES) if(CMAKE_OSX_ARCHITECTURES MATCHES "arm64") set(NCINE_ARM_PROCESSOR TRUE) elseif(CMAKE_OSX_ARCHITECTURES MATCHES "x86_64") # Default architecture else() message(FATAL_ERROR "Architecture \"${CMAKE_OSX_ARCHITECTURES}\" is not supported. Only one architecture (arm64 or x86_64) could be specified at build time.") endif() else() string(FIND ${CMAKE_SYSTEM_PROCESSOR} "arm" ARM_SUBSTRING_FOUND) string(FIND ${CMAKE_SYSTEM_PROCESSOR} "aarch64" AARCH64_SUBSTRING_FOUND) if (ARM_SUBSTRING_FOUND GREATER -1 OR AARCH64_SUBSTRING_FOUND GREATER -1) set(NCINE_ARM_PROCESSOR TRUE) endif() endif() include(ncine_options) include(ncine_get_version) include(ncine_imported_targets) include(ncine_imgui) include(ncine_tracy) if(NOT IS_DIRECTORY ${NCINE_CONTENT_DIR}) message(WARNING "Content directory not found at: ${NCINE_CONTENT_DIR}") else() message(STATUS "Content directory: ${NCINE_CONTENT_DIR}") endif() if(NCINE_BUILD_ANDROID) include(ncine_generated_sources) include(ncine_build_android) return() endif() add_executable(${NCINE_APP}) if(WINDOWS_PHONE OR WINDOWS_STORE) message(STATUS "Compiling for Windows RT") else() # Falling back to either GLFW or SDL2 if the other one is not available if(NOT GLFW_FOUND AND NOT SDL2_FOUND AND NOT Qt5_FOUND) message(FATAL_ERROR "No backend between SDL2, GLFW, and QT5 has been found") elseif(GLFW_FOUND AND NCINE_PREFERRED_BACKEND STREQUAL "GLFW") message(STATUS "Using GLFW as the preferred backend") elseif(SDL2_FOUND AND NCINE_PREFERRED_BACKEND STREQUAL "SDL2") message(STATUS "Using SDL2 as the preferred backend") elseif(Qt5_FOUND AND NCINE_PREFERRED_BACKEND STREQUAL "QT5") message(STATUS "Using QT5 as the preferred backend") elseif(SDL2_FOUND AND NOT GLFW_FOUND AND NCINE_PREFERRED_BACKEND STREQUAL "GLFW") set(NCINE_PREFERRED_BACKEND "SDL2") message(WARNING "Using SDL2 as backend because GLFW cannot be found") elseif(GLFW_FOUND AND NOT SDL2_FOUND AND NCINE_PREFERRED_BACKEND STREQUAL "SDL2") set(NCINE_PREFERRED_BACKEND "GLFW") message(WARNING "Using GLFW as backend because SDL2 cannot be found") endif() endif() include(ncine_compiler_options) include(ncine_headers) include(ncine_sources) include(ncine_extra_sources) include(ncine_generated_sources) # Organize main project files into folders ncine_assign_source_group(PATH_PREFIX ${NCINE_SOURCE_DIR} FILES ${HEADERS} ${SOURCES}) foreach(SOURCE_FILE IN LISTS SHADER_FILES) source_group("Shaders" FILES ${SOURCE_FILE}) endforeach() foreach(SOURCE_FILE ${GENERATED_SOURCES}) source_group("Generated Files" FILES ${SOURCE_FILE}) endforeach() target_sources(${NCINE_APP} PRIVATE ${SOURCES} ${HEADERS} ${SHADER_FILES} ${GENERATED_SOURCES}) # Windows RT uses custom packaging, enable it only for other platforms if(NOT WINDOWS_PHONE AND NOT WINDOWS_STORE AND NOT ANDROID AND NOT NCINE_BUILD_ANDROID AND NOT NINTENDO_SWITCH) include(ncine_installation) endif() include(ncine_strip_binaries) deathkiller-jazz2-native-2a7ccef/Content/000077500000000000000000000000001512772601700205225ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/000077500000000000000000000000001512772601700226245ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Common/000077500000000000000000000000001512772601700240545ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Common/player_shield.aura000066400000000000000000000024171512772601700275560ustar00rootroot00000000000000☄️ LL<<:<<"<&"<<<>9+<:'$6<<" "<<4455<<>$6  >3(((3> ť<1& >3((3>  >3(((3> &1<<1&& >3((3>  >3((3> &1<<1&& 3(Û(3> >3((3> &1<<&&1& >3(Û(3>  >3(3> &1<<1&&1& >3(ě(3> & >3>3((3>&1<<1&&1 >3(ě(3> &1& >3((3> &1<<1&&1<1& >3((> &1<1& >3((3 &1<1&1<1& >3(3> &1<<1&  >((3>&1<<1&1<1&>3(3> &1<<1&  >3›3> &1<<1<1& >33> &1<<1&  >(3> &1<(<<& >  &1<1&1& >3((>&1<$(<1&1<((<1& >3Ė( &1<$ĥ$(<1&&1<($(<1& >3ě( &1<$//$(<1&&1<($/$(<1& >33 &1<$/::/$(<1<($/:/$(<1& >33>&1<$/::/$(<($/::/$(<1&& >(3 &1<$/::/$(($($/::/(<1&1&ƛ>3(> &1<$/:/$($($/:/::/$<1&1<1& >33> &1<1<($/::/$/$/:::/$(<1<<1& >(> &1<1</:ť:&::/(<<1 33> &<¥<1<(/:&&1<1&::/$((Û >(3> &1<1<$/:<1&1<8<1&::/$Ĝ( >(3>&1<<1&1</:88<888<1&:/::/$/$(( >3›(3> &1<¥<1&1<$:8<1:/$/::/:/$($( >3(3> &1<1&1<$:8$$81&:/::/::/$(› >3>&1<<1<($:8›/$$//<:::/:/ě(Û 1<<(/:8›:/$/::$8::/(&1<<($/8:/::/Û/::/$(&&1(<($/:¥:/::/$<&/::/$($(11($/$(<(/:8¤::/$8&/::/$/$/$(1($/::$((/:8 /8/::/$/$/::/$(($/::$(((/&   :$</:&://::$<$::&:$((($/&  " :/8&›::/$/::/($:&1&:/$/:&< """ :$<&/::/(($/:&1&:/:&18 "-8-":/<1&&::/($/:&1<1&:&1< "-¥8-" :/$<1&&1&1&&://:&18<1&&&1<8 "-8-" :/$<1<1&&::&<8<1&&1<1&1<8$ "--" :/$<8<1&&1&&&1<888<1<8<1<8/: --" :/ě8<1&&1<1&&&&1<88<8<8$/:"-" :/<1&1<1&&&1&&1<88<88$/"-8" :/$$<1<8<1&1&1<1&1<888$/ "--" :/$/$$<1<8<8$8/ --" :/$<8<88$/$$88/: "-" :/$8<8$/$$88$: "-" :/$8<8$$/:/$/$8<88/: "-" :/$8<8$/$$/::/$/$8<88$/ "--" :/$/::/$$/:::/$/$8<1<88$/ "--" :/$$: :/$/: :/$/$<1&1<88$/: "--" :/$$/  :  :/$<1&&1<88<8$/:/: "-"  :/:/$$/: "-"  " :/$8<1&&1<8<&1<8$/:   ::/$$/: "-" "-" :/$81&1<1&1<8$/: " ::/$/: "--""-" :/$<1&1&&1$/: " :/: "88-"-" :/$<&&1<$/å "  : "-8ʥ8-"-" :/$›1:1<8$ť "-"  "-8-8-" :/$<&:ɥ&18$$/ "-" "   -åĥ-8" :/<1&:&<8$$ " "-" "$8- :/<1&($/:&1<8$ " "-""-$ $8-" /<1&:(($/:&1<8$ " "--"-8 $ $8-" /<&(($/:&1<¥ ""-88 + + + $8-" :/1&(($/:&1< "- ++ + $8-" :/<1(Û($/:&18 "-$ +6+ $ +$8-" :/<1&(Û($/:&18 "- $$ +6+ $$ + $8" :/<1&(›($/:&1 "- + $ +6+ $$ + $8-" :/›<1&ś($/:&18 "- + $ + $$ $8" :/<1&(ś<($/:&8  " + + $$ $8" :/<1&(ě1($:8  - + $ +6+ $$8" :<1&ě&1<($/8 " + $$8-" /1&(&1(($/:8: "- + $Û$8- :/<&›&1($($/:ƥ "- + $-8$8-"/<1&1&(&1<($/:8å¥ - + $-8$8-" /1&1&(1<($/:8å " $ $8-88-" :<1&($(<(/:& "- $-88-" 1&(Û<(/:&8 - $›-88-" <1&Û(ě<($/:&1" $8-8-"/<1&ě(ț<($/:& -¥-8-" :1&($(–&1($/:8"¥ $8-" :1&($((&1<($/: - $8-" :<&(((<&1<<($/: "-8-" :/1((&1<<1<($/: "-8-" :$<&((&1<1<($/: "-8-" /<&((&1<1&1<($/:8 "-å8-" $&(&1<1&1<($/:8 "-ť8-" :$1(őۛ&1<1&1<($/: "-88-" :$1(<<&1&&1<1&1<($/:8 --"  /(1<<1&1&&1<1<($((/ "--" /<(ś1<<1&1&›&1<($(( "--" ::<(ěě&1<1&1&&1<($$(/"-" :/::/8<ś&1&&1<<($$($: " :›::/$8<ś&1<1&1<($/$/8 " /$/::/$1( &1<1&1<($/å  ://:/$8<&(1 >3> &1<<1<($/8 :/$/$( > >33> &1<1<$/::/$/$81( >3Ǜ(3> &1<<$¥::/:/$/$81( >3((3 &<1<<$å:8:/:/$/$<1( >33>1<<1<<<1<$å:å/:/$8<1&( 3(3 &<<<<1</:&8/:/$81&( >(> &1<1<<1<1<Ī$:ĥ8/$81& 3>1<<1&1<ê$/ĥ$8<&: (> &1<1&1<$:/:ĥ8$8<1(>ç(3 &1<1&&13 &1&&1<¥ĥ¤/$/:88<1&:$( 33> &&1<1<å(/:88<1&:/( >3>  &1<1&1<$:88<1&:/( >(3  &1&&1</:88<1:/( >3(3  &&1/:88<1:/( >3Û3>   $/:88<&:/$( >3Û(3>   >  >3> &$/8<1Û:/( >3ě(3>   >3>3(3> &<$/11:/$ >3(Û(3>  > >3((3>1$/:/$1 >3(ě(3>3>  >3((> 1$/::/(& >3›(3>  >3((((3 &<$/:/$/::/$1 >3›(3(3>  >3((>1<$/$(($/::/$<& >3›(3(3> >3((3 1<($/:/(<1 >3ě(3(3> >3>3((3> 1<$(1& >3ś(3>  >3((3>&1<ǥ<1 >3((3>  >3(3((3> &1<1<<1& >3> >3((3>  >(3((3> &1& >3>  >3((3>  >(3((3>  >3>  >3(3(3> >(3((>   >3>  3(3>3(3> &>3((3>   > >3>  3((3> >3(3> & >3((3>  >3(3>3>  >3(((3>  >3(3> &>3(3>  > >3(3(3>  >3(3(3>  >3>  >3(3>  >3(((3>  >3> >3>  >  >3((3>  >3((3>  >  &  >3(3>  >3((3> > &1&  >3((3>   >3((3(3> > &1<<1&deathkiller-jazz2-native-2a7ccef/Content/Animations/Common/shield_lightning.aura000066400000000000000000000615461512772601700302550ustar00rootroot00000000000000☄️ JJJU///6 JJJxxx=$KKK[[[5:7SSS! 13$7%)70:(5[[[:::.@5== ³6 6:2$PPP<;4!aaa??? vvv);;;6 rrr5,@7@6```*)6&?77= ,53@#; $,% .6* JJJ '6JJJaaa; #;;;3UUU#@!1:4,8011!& $# > -33*7#أ ;@.!;; !>@3;:;@3:@%8:)@3 ="<;: ;30'# .$!> 936;;,6" + 2ڒ nnnJJJ@@@U<"U@ <">$$(>"< 9 1"<)?;%. -5= ' / 2 + "6,,+7040;?" >:#Ԃ+U(sssPPP***333 =HHH&0(@"CCCfff8 8"3 5/& > )(0- =2%+PPPsss!(#:$"?(;040;/;$@0#:6 ;@```*** MMM1(,57/ ,05 *//7(1?78) +; 6:6#% $@;NNN&&& =1:@bbbAAAfffU 6 ?))) +?$1UUUU* $-<41)0 ;?+? 6 73% /bbb:1=  111ʣ:"?.2"6 777@---XXX=9(WWW"% $3#8#3$( %" 19= ).$ 6@/4" 2#?" :͝ >>>{{{,,,===&/ggg@'. 45-119?-#; !-)$0?*1/18=- 54 3.', %> &```.UZZZ1lll4#5?>U%% =! '#?0"UUU U[[[2/ 5( 9277289. :,(5 /2 +++"/> 0?;#'!= %% ?'5-#4 1?=4 %%.6~~~)2!!!!0-,U'7zzzHHHSSS2-7',0!$0 ) 3695;.:!%+++>% 4=XXX0>>>ZZZ". %! -+Ӱ?rrr*"lllHHHBBBssskkk666\\\ :!2- "*#? +%9>( 6 .";'700XXX9)[[[0(=" 555HHH"59 3;;23  95"-3?  /" ($*8=0 )9000BBB+50.@GGGSSS9 .%-,U;03OOO&@-!'9'! -1&(307;ZZZ.2-,#;"%www  .5!;6"nnn <#@ -}}} $%,,,?@EEE!%?0;7 PPP*# $9=( -:1< '"6)===/5 aaa9dž?0& "+U6!0}}}JJJ 70! 6'2+" &0?9+= $3"*>JJJ!(0" @>yyy.UUU 0/=YYY8U3 '}}}RRR*%rrr555<#% *.9'& -373'8,=/0 0www?  VVV> U"lll!```2> %ɥ5?0. kkk&Uˀ.OOO2= "-yyy3-" 8=2.www:')!9&5kkk 4.NNN0? <% ))) U>2TTT 0 U23?KKKՙLLL - + ͣ&:>>/pppzzz7)(;-&~~~AAACCCddd&-4 -"&CCC3 &-;76 /(=0)^^^>ppp>>>@@```7 9UUU,--*.)0QQQ>()564)$?'"444XXX1"1#0"' "?,$')?@ 4/65)(>*-;4:+ ).51?lll3FFF/UU/?? 4<>>$7#5 4???GGG -))- 0&4 .5#7 $8>>ddd<4 ?-?( =//3OOO ?724 ,;wwwMMM9 @(U (5, ( 30ם,>@666+?0?$+ >,!034 ( ,5(0: 3(1'>-=9QQQ8.;,(;  ^^^9@EEE~~~)hhh!)"4 iii ,0=NNN9;9?(=&01,5  64@;-")7/+ ! 4hhh)~~~.29"!@ QQQwww2 1*[[[3 5UUmmmvvvޜ-```@DDD 1?-;7>4%5"#5:( 3 zzz*6 2.QQQ;-* #@$ ;6 29 -!8 ```ӗ3kkkXXX <%:3# , 8!8- 92 6; $  #*-;uuu &$fffBBB5?3[[[qqq50/;.)!?!,.VVV&&&<<</?.":,-#!!).;4305=1 ?!5___/999)(((;"0/8?;.&.KKK=.,&4& 3.1 0;?8/<- !9#";(((+")iiiGGG<<;<<<=0EEE)LLL"""@JJJ /!9 48*<:Ͷ(  +*)+*  (&80:= (<*4  9!:/% ! 3)$?ddd- ###VVV wwwRRR;'''ooo+%-@ : ;;  /: 3;@"-% +:6>lll. 09?' =-```%%%U'/.?+ +/"(!405#!;)0!8 "*.*$lll<1?.444,'???.$QQQttt.>>> %-)-"@؆((7/" ;54-)-%-&%6, .)PPP3>'$.???...dddLLL =:$-?222Uvvv4!?.9(!½$$ $?;!( 9.?!8#4zzz5111-,:= +7)6'WWW:.)33359;)@?(7 8.Օ, 04.$83 ),7(#.7;---?:<% 9652 =);MMM 444kkk!"&? < }}}999UUU:;"ʧ9!1;1! 9.4 $'"?;::+;0( 8< &ccc"%,!kkkMMM}}}-;wwwFFF"""rrr;$/___0>0)))[[[ #/:77+UU;: ;xxx= ; :&; :!+7 7/%# [[[.0>04</$;#83www;:+2qqq; ``` NNN 0,;':@"/<7/87<8 76 ;:-)/*;0 0[[[ ,KKK*80!0;;UUU#.:NNN:01eee >[[[,4ttt!;27-(-^^^ $-!-$ 7-( (-=421; !)+,[[[>2?0 eeeDDD@&": ,>>>xxx;:GGG!(?3 -___(((ddd-4?; !9)!-.94&..&49.-!)29.! ;?4- ddd= - 3? (4!hhh:;6xxx>>>,WWW!MMMUttt?-(>3@;!<"+ZZZ() !4!.Ǫ );%3kkk:3%;;  ) 4.?!! )"(ZZZ+U(-4448~~~! 4___?^^^888;?9-/UHHH$,!,۔8MMM0rrr',+,'#09UUU58 63,48/!,$-"""?:&+;,<=7?( 4EEE ]]]~~~ 8NNN->!%' 444ŀ4;___0 -DDDooo??(61-' 0397 ;4< >[[[2 %!4-./8 ]]]%%% EEE*RRR&&& /VVV(0DDD(4; 1vvv,7>"/+8OOO%; ;%8+/"0>7 ',;(?1". :-4]]]---1/@(&&& /RRR*< ///!:vvv;PPP1= JJJ$Ӌ) =KKKsss-2>>2-2/70=;  !<)=$ ,3 1%9 *:86 !ttt2RRR... *!1,$> 3 U%"$/:NNN=QQQ?#!4%= :?/:$"=5>(  3 - $RRR, U1*'28$()8KKK *16)/2JJJ@.?& '=UUU-yyy++ -0='' !+&?.= 3.786$ :&2/)888(1*-%)Ɋ(@@@U[[[ZZZ>0-. * . TTT,! +8-. ;* 95* .OOO0#<>ZZZ3/ 2:7((@#0444 ; 9 ___8,,,=8..))#2.*"*555 ^^^*";*. 2#)4).8.=8+2-6'U!!!7kkk*333%U2WWW7>:@U#:>???PPP%7&,  >:1-#:>223*>$: 7')6 @8 NNN|||.&ppplll)*UUU---OOO#*"+ > VVV0333= 20? > ;+ "=*#+--- 0*!$)lll9&. /@!, >ثppp. RRR:;) AAAbbb GGGeee2' ,#7/QQQ ;45/7=# , '2 888( 3AAA): -NNN",#.㕈>;!<.: +@ 13?!!! ,,,)ttt(""34 . -"&9& :hhh = ہ/8 yyy "ddd  "#& 6>?&63 $#" 2# FFF@" , WWW1  8/4 !9=[[[ ::*  &MMM1-ttt&[[[8@:9U 3؁00 3 >5*9:8 &7,#-18rrr:&  9/:OOO;HHH; ,7 4/???/=.%;¿99$';%.%= /&/4 7(, ;8;-HHH>vvv:,: hhh(((333\\\GGG.!74,  .2#)!*666 $2+*&!)#68.  *,47)!.2%hhh>U -;:/,!,#="DDD[[[U48";34 U59 1}}}44U*7(3  3(7*44?.1, 9;5ZZZ 543")84 >1 "=#!<!,#=" 1DDD)> U48";345 U59 1.?44%*7(3  3(7*44?.1, 9;5ZZZ 543")84 >1 "=#!<,/:;- 8$ (((2\\\www!74,*  .26#)!&*+666 $2+*&!)#68.  *,47)!.2%hhh>U -;:/,:HHH;,(7 4???/=.%;'¿99$';%.%= /&/4 7(, ;8;-HHH>vvv::/9@  ;#MMM,7ttt& [[[8@:9U 3؁00 3 >5*9:8 &7,#-18rrr:&  9/:hhh= ہ!4  1yyy "3ddd  "#& >?&63 $#" 2# FFF@" , WWW1  8/4 !9=[[[ :/ &&"-Ӫ . @"":::]]]U=&hhh000/ ?;&=+'>(""34 . -"&9& ;.8*ޛ 00 ".8Ť ^^^666 (((777 3  -"RRR 1$*."- 2 , 777!  %74 <8" 00 ?٥)8.;rrr U 6#,7YYYQQQ--- MMM'U.#+*/%<<<%7+|||/*) 05#.?'8---4%%%(,;U2#$*6@ !,7/##.: @+8@ 13?57"!!!,,,LLLttt;!/ 9.???ppp )0!*UUU ---OOO+*@2+> ?0333= 20? > ;+ "=*#+--- 0*!$)lll9&. /@> :HHH'U)!!!7kkk*333%U2WWW7>:@ #1:>,&PPP%7&,  >:1-#:>223*>$: 7')6 @8@#444ZZZ <___,,,8..)#2.*"*7555 ^^^*";*. 2#)4).8.=8+2#0-. ** . TTT,! +8-. ;* 95* .OOO0#<>ZZZ3/ 2:7(()KKK%-6=)JJJ@. .& ''=0- ++ -0='' !+&?.= 3.786$ :&2/)888(1*-%)82RRR... *1$> UPPP("$/?:NNN=QQQ?#!4%= :?/:$"=5>(  3 - $RRR, U1*'28! 6!*%1 3,JJJ$Ӌ)=KKK/2HHH2>>2-2/70=;  !<)=$ ,3 1%9 *:86 !tttRRR&&& /VVV1(U;yyy-1:vvv"U >"/OOO; ;%8+/"0>7 ',;(?1". :-4]]]---1/@(&&& /RRR* (~~~8NNN>4%nnn 444ŀ<4;7___0 -DDDooo?(61-' 0397 ;4< >[[[2 %!4-./8 ]]]%%% EEE 4___?^^^=,;?-+UHHH$,!8,۔8MMM0rrr',+,'#09UUU58 63,48/!,$-"""?:&+;,<=7?( 4 WWW!MMMUttt?->@;!ZZZ()!!Ǫ );%3kkk/:3%;;  ) 4.?!! )"(ZZZ+U(-4448~~~! >>>xxx; GGG"!4(?3 -___(((ddd-4?; !.9Ӂ)!-.94..&49.-!)29.! ;?4- ddd= - 3? (4!hhh:;6xxx>>>,NNN :&0eee( ?>[[[,+ttt)!;2ޚ-(-^^^ $-!-$ 7-( (-=421; !)+,[[[>2?0 eeeDDD@&": :ddd7770;;!0*,OOO1110U0;*7-:;  8<>><8 76 ;:-)/*;0 0[[[ ,KKK*80!0;;UUU#.:WWW333qqq;4``` 1NNN 0;3%':@/<8/8704</$;#83www;:-MMM444kkk,"??? III }}}9990;+:;"'$ʧ9!;1! 9.4 $'"?;::+;0( 8< &ccc"%,!kkkMMM}}}- 2569;<)@?pppOOO77 ʹ.Օ, 04.$83 ),7(#.7;---?:<% 9652 =);...ddd) :?222Uvvv5QQQ8!?.9(!½$$ $?;!( 9.?!8#4zzz5111-,:= +7)6'WWW:&.$4>-ttt.>>>%-)-"@؆/((7/" ;54-)-%-&%6, .)PPP3>'$.???...fff.?1 $.*"!:::~~~!"0!(Ż@+@@3 >+ +/"(!405#!;)0!8 "*.*$lll<1?.444,'ttt/= ###'?90.'''ooo+-(@ : ;;  /: 3;@"-% +:6>lll. 09?' =-```%%%UEEELLL"""@JJJ %!48*U< :Ͷ(  +*)+*  (&80:= (<*4  9!:/% ! 3)$?ddd9kkk;'''|||<bbb+++55586 0eee4@##53ε!8/@OOO,,, 7/86 !8( 35#2#&!4" ###?0@:NNN6<9.>;<<<}}} iii)+"((("4#0!-<8;www3 &.KKK==.,&4& 3.1 0;?8/<- !9#";(((+")iiiGGG/BBB5? 50/;.)!?!YYY.VVV&&&<<</?.":,-#!!).;4305=1 ?!5___/EEE;*#,@$ ;ooo 2@ -88```ӗ3kkk <%:3# , 8!8- 92 6; $  #*-;uuu &$}}};.26*[[[3 (55U?mmmޜ-```@DDD 1?-;7>4%5"#5:( 3 zzz*6 2.QQQ !^^^92@EEE) !)"-4 109;9?(=&01,5  64@;-")7/+ ! 4hhh)~~~.29"!@  Ē,;wwwMMM90=U(3(, (@ 30ם,>@6660?$+ >,!034 ( ,5(0: 3(1'>-=9QQQ8.;,(; ? """FFFfff U/ ?? 4>7rrr5 4???GGG -))- 0&4 .5#7 $8>>ddd<4 ?-?( =//3OOO ?724*5)+QQQ>)5/4?)$,?'"444#1"1#0"' "?,$')?@ 4/65)(>*-;4:+ ).51-YYY09 6>>>>ǫzzz7)( -~~~AAA3CCCddd&- 4 -"&CCC3 &-;76 /(=0)^^^>ppp>>>@@```7 9UUU,--.&WWW0%%%@ @kkk.&,! )-JJJ);;)  -#) .9 !eee,5+!&[[[ :? GGG0&=.-*U26/KKKՙ=LLL - <ͣ:2TTT 0 " @>?yyy.UUU 0=,U3-&'}}}RRR %rrr555<#% *.9'& -373'8,=/0 0www?  VVV> U"(U! +>*" aaa=šdž@0"+2U!0}}}JJJ 70! 6'2+" &0?9+= $3"*>JJJ!(?5/6nnn <@ }}} (9%,,,0?@EEE!%?0;7 PPP*# $9=( -:1< '"6)===/5>>>';".%6!(9Ӱrrr"lllHHHssskkk666\\\ :!2- "*#? +%9>( 6 .";'70```0=U%%!. ~~~) sss!!!!0-,UzzzHHHSSSuuu+2-7',0!$0 ) 3695;.:!%+++>% 4=XXX.?ZZZ 4@-'?U%% ='#?U"UUU))) U[[[2/ 5, >>>277289. :,(5 /2 +++"/> 0?;#'!= %% ?'5-#4 1?*,,,& >%@=.!45 119VVV0)-##; !-)$0?*1/18=- 54 3.', %> &```111ʣ "?.2"&4/6 777@---XXX19(" $#8#3$( %" 19= ).$ 6@/4" 2#?" :͝ >>> =:@3AAAfff%7 6 ? ***?1UUU-* $-<41)0 ;?+? 6 73% /bbb:1= NNNzzz$ @6:6 ;```+ MMM?1(57/ ,05 *//7(1?78) +; 6:6#% $@;NNN&&&;00;("U$:#ԂU(2PPP*** 333[[[=HHH0(@"CCC/fff5 8"3 5/& > )(0- =2%+PPPsss!(#:$"?(;040;+,?,"+ ڒ 'JJJ=@@@ .U@"1U9 <">ݦ@$(>"< 9 1"<)?;%. -5= ' / 2 + "6,,+7;6>$#03; :;<"@3:8@%8:)@3 ="<;: ;30'# .$!> 936;;!U; ;;;@3UUU>4 #@&!1:4,8011!& $# > -33*7#أ ;@.!aaa???U;;;6 ;#,@7@6```*)6&?77= ,53@#; $,% .6* JJJ '6JJJaaa U2:///6 JJJxxx=5$)KKK[[[5:7SSS! 13$7%)70:(5[[[:::.@5== ³6 6:2$PPP<;4!deathkiller-jazz2-native-2a7ccef/Content/Animations/Common/shield_water.aura000066400000000000000000000356051512772601700274110ustar00rootroot00000000000000☄️ BEk!"FFFGGGOIIIYIIIXIII\IIIa;GGGPFFF@GGGSIIIYGGGTEEE<<:IIIYFFF<FFFFFFHHHIIIJJJJJJuU;FFFGGGUFFF;11;=GGGY'=;11;=<FFF{KKKGGG}DDDnDDDoEEEx36GGG|#GGGFFFx6EEEp6FFFw4EEEEEE{FFFtIIIIIIFFFuFFFFFF'#EEErJJJHHH~1#FFFFFFJJJ*!46674 DDDWHHHoJJJFFFGGGhGGGfFFF0JJJ=DDDXFFFkJJJyJJJHHHFFF'GGGR'%1.JJJ{GGGk3JJJtJJJFFFFFFGGGGGGEEE4 ;DDD^DDD[GGGiJJJv FFFFFF4%(HHHiDDDZDDD_FFFEEEREEE_DDDZEEE\IIIoJJJ~1FFFFFF1 ?DDDZEEE^EEESFFFDDD[,( IIIFFFFFF4%(;DDDZDDD_FFF3 FFFFFFGGG 4 FFF3 .1%FFF/'%1.HHHU&1HHHuHHHLHHHZHHH1HHHy88 1HHHhHHHNHHHOHHHg1+88%1HHH}>88>HHH|1088HHH1HHHQHHHP 1HHHEEEP㢈%1>88> 1088 10%1+88 1,>))>1+8EEEi1JJJ{FFFn 1EEEkUFFFJJJ4DDDVDDDSGGGsJJJFFFEEENFFFU2HHHoDDDeDDDbDDD]GGGlJJJFFF%UFFFJJJJJJ} GGGpFFFoJJJxJJJ{JJJ=%EEE{1 IIIxEEEeEEEP%"/ JJJJJJ1EEEd;EEE(U2 5 9FFFo>JJJ{7=% 2HHHoDDDbDDD]GGGlJJJFFF%UFFF4.DDDSGGGsJJJ=%FFFEEEn*&EEEbHHHIIIEEEoIIIFFFyFFFxIII (EEEp#(EEEJJJGGG~EEEu#FFFuEEErGGG{.(#( ( (GGG`IIIr@EEEXEEEPHHHXIIIZHHHUEEEU"-HHH`5HHHGGG];HHHX%HHHWEEEIGGGY"IIIFFFfFFFe5HHH(FFFQIIIUGGGU"<FFFe5HHH(FFFPIIITGGGO"<'FFFe5HHHFFF^EEEfEEEc-FFF\"<FFFe5HHH( #GGGO"<FFFe5HHH(."<51(;HHHX%HHHWEEEIGGGY"IIIFFFfFFF{1EEETGGGyIIIz@FFFl'IIIIII#GGGf1HHHFFF]FFFvFFFLEEE\HHH"GGGh 1$66GGG^2HHH"! 1FFFTHHHz6$2:"!EEE 1FFFFGGGn>GGGqGGGZEEEW"! 1FFFTHHHz6$2:"! 1$6632:"! 1GGGyIIIz,?HHH"!FFF~GGGDDDLIIIz6HHHv;@IIIFFFJJJHHHDDDh6EEEgDDDgHHHJJJFFF~JJJ846#FFFrDDDe89IIIIII84HHHZ6FFFr89III'III84DDDLGGGu6/EEEgDDDfHHH9III(846FFFr89>FFF~IIIJJJ846#FFFr89IIIFFF~JJJ846EEEg)HHH9FFFFFFt6HHHrDDDV%GGG{<FFF6GGGvEEEe02<066IIIyGGGlDDDH02GGGuIII%EEEPGGGv6: %<0 JJJFFFDDDJFFFs66 IIInGGGgDDDI; DDDa0<%EEEPGGGv6:GGGl%<0FFFJJJz66: DDDH020FFFtJJJz6;"02DDD 1GGGGGGq6GGGsEEEKFFFGGG~1GGGb 1GGGq6+FFF\1GGGu16GGGk1GGGw;1GGGGGGT6GGG1EEERHHH1GGGpDDDN;6GGGo1HHHEEES;1GGGT6 10161 16+1IIIrDDDODDDLHHHn6;DDDMJJJu-!HHH]IIIhIIIx66FFFb FFF{1HHH{!HHHE?IIIx6GGGr IIIFFF|.1!IIIc/6 FFF~GGGFFFlGGGc/6>5GGG.1HHH{!/6 #1!HHHE?IIIx6 .-!DDDL7?/66 EEEoIIIIII\EEEIHHHsKKKz6HHHEIIIYJJJFFF;FFFkJJJx6>FFFgIIIYFFFJJJGGG3>66#GGGJJJFFF~GGGJJJo66#GGGq4ɢ,3>66#FFFtHHHOFFF~466#GGGq49+3>66# JJJ;3>6>IIIYEEEpFFF"III1EEEPGGGt6HHHGEEEMGGG5FFFfFFFEEEe*GGG;IIIz6FFFoEEEM"5JJJyGGGn"5"HHHIII-FFFcJJJmIIIx6>"III1-JJJ~HHHDDDQ>@HHHy6HHHP!HHHJJJFFF|IIIDDDR>IIIz6:HHHpEEEQDDDPGGG|FFF~JJJ~7> +6(!HHH"1-JJJm/6>" 1-HHHSJJJ->JJJy#DDDH"5DDDUHHH >DDDW# 3FFFq>9IIIs1"JJJkKKKzUFFFmEEEd1IIIuEEEkHHHT$EEEi)#0EEEHHHOEEEnIIIGGG3JJJyJJJz#%EEEGFFF5$HHHT$)#0 81"/#UFFFm1IIIuDDDUHHHJJJy3FFFq>9HHHo1FFFlFFFj1=1>(1EEENJJJHHHeHHHb;1GGGHHHFFFeJJJGGGGGGfGGGvGGGsFFFEEE1HHH"%HHHe.EEEP=1>(1+HHHxJJJ+ 4III1 JJJFFFbEEEb)FFFPGGGHHHFFF`EEELEEEHEEEIEEE^"FFFR)JJJ)41GGGJJJ~+HHHx FFFxIII))FFF{EEE{UGGGIIIGGG IIIYDDDoFFFDDDlIII\ GGG #)III4FFF554FFF5HHHWEEEtEEErHHHHHHYIIIUDDDoDDDnIIIYFFF5EEErHHH III425FFFEEEEEENGGGS1GGG{1GGGUIIIVDDDuDDDr9IIIr6-9"9:121%GGGn>#IIIYDDDpFFF6III[IIIdJJJDDD[DDDYIIIIIIa&HHHEEErEEEp:HHHFFF} %>2IIIYJJJ FFF60# JJJGGGFFF9FFFIIIXJJJ .DDDc/IIIh*5HHH~EEEu1EEEoGGG{=/?*IIIXJJJ .09FFFFFFDDDGGGUFFFxFFFvHHHGGGTEEEKIII~JJJt-III{EEEWHHHHHHFFFXFFFVGGG4DDD\DDDZFFFKKK"FFF +1-III{GGGU,HHHGGGFFFMHHHGGGGGG~GGGFFFPIIIZ6 IIIWDDDjJJJiIIIfEEExDDDRDDDPFFFKKKHHHEEEQEEEOGGG,FFFDDDjJJJi)3%6 IIIIIIYDDDoFFF6;IIIYFFFEEEo.EEEg1?HHHeHHHFFFuJJJ$EEESEEERGGGHHH $ 81?!IIIYJJJEEEpFFF.IIIIIIYFFF;IIIYFFF.III|&EEEUEEES    &2 IIIYEEEpFFF.IIIIIIXJJJ3EEEvIII[IIIXJJJ3IIIT,FFFHHHEEES   ,0IIIXJJJ3#III; EEESIIIIII]; (IIIWJJJ12EEETGGGHHH2' /2' /2'  12' ; (IIIIIIaJJJrIIIoIIId2<  HHHEEEP4HHH;4;4;4 ;42EEEd2<EEE< '2<IIIp2<  EEEd2<IIIp'2< 2< 7HHHP87(EEEd2< '2< 2<  ;EEEOGGGHHH;0)8;0)8;0) ;0)2< 'III\JJJEEEQIIIIII];EEEQ( 1EEEUEEET+/2'+/2'+/2'+ 12'+;(IIIXJJJ3III[IIIXJJJ3#GGGDDDRDDDQFFFEEES   70IIIXJJJ3#IIIIIIYFFF;IIIYFFFEEEo.III}HHH2EEES JJJ   III}HHH2 IIIYFFF.III%6III]IIIYFFF.EEEh1?HHHeHHHFFFv$EEES  $ 1?,IIIYJJJEEEpFFF.IIIFFFNGGGGGG~GGGFFFP%6DDDlIIIWDDDjJJJiIIIf3DDDP0KKKHHHEEEQEEEOGGG,FFFDDDjJJJi)3%6 FFFJJJFFFHHHUFFFv!1HHHTEEEOIIIJJJrIIIpIII}EEE[HHHHHHFFFW2+*GGGDDDZFFFDDDYFFF2+*0IIIJJJrIIIp&EEE[,!1HHHGGG9GGGFFF9FFFIIIXJJJDDDpDDDo.DDDeJJJk?5EEEr5HHH~EEEoGGG{/?51IIIXJJJ .;9GGGlFFFkIIIY FFFIIIZIIIeJJJDDDYDDDWIIIIIIbFFFtHHHHHH1EEEp/$FFF~ 93=IIIYJJJ FFF%EEEFFFNEEELJJJGGGSHHH2GGGy1GGGVIIIVDDDuDDDr9IIIt6-9DDDu"9:1214EEELJJJ5GGGFFF55GGG'5FFFxJJJFFFyHHHWEEErHHHHHHZ.DDDo6IIIYFFF5EEErHHH IIIGGG'5GGG ) FFF{EEEyU GGG IIIYDDDoFFFDDDlIII\ GGG  #>) JJJHHHx JJJ  GGG1)FFFaEEE`GGGKFFFPHHHFFF^EEE]GGGFFFS)EEE`GGGK?1  HHHo1FFFl"FFF#FFFi1HHHp=1>1  01EEEPJJJHHHe  ,HHH`JJJEEERHHH1+*1HHHFFFeJJJGGGEEEFFFfHHH.#1;JJJHHHe9HHH`JJJ=1>00FFFi1DDDWJJJxwu>HHHDDDY9>>IIIt1EEEbN/FFF+1IIIuEEEnJJJHHHR HHHMEEEp4FFFFFF>6-1EEEb//FFF+1IIIu9HHHJJJx >HHHDDDYEEEe~}EEEdHHHX"~:h"1IIIuD\!IIIr1EEEf HHHDDDO 8DDDNHHHJJJFFF}IIIFFF DDDO8 8:JJJ"1 /!IIIr1EEEf"? HHHXGGGEEEXgNEEEW1"O!f1FFFcHHHHHHRb!FFFb FFFHHHQHHHFFFeHHHlGGG| !FFFb FFFGGG{HHHpGGGxHHHu d)F!iHHHrGGGyHHHl= FFFb FFF2> !FFFb FFF3HHH1":!7EEEWHHHEEEg*"EEEOKbEEEL"5FFFhFFF8*"0d:#. 4"5EEElJJJ.#::.bFFFFFFlIIIGGG.#::.bFFF4JJJ{=..4yJJJIIIGGG.#::.bFFF4/.#::.bFFF>8*"0d.#::#. 4EEEL"5FFFhEEEo1HHH[UvtHHHXIIIEEEp1!FFF@HHHXIIIJJJFFF%UFFFJJJFFF~JJJFFF,%UFFFGGGFFFɢEEEu/8EEErHHHOFFF,%UFFFFFF9%,%U;JJJHHH!FFF)HHHXIIIIIIsIIIDDDM4z/+"#4DDDIIIJJJuIIIs @% "jg" %%DDDIIIFFF|1HHH{ 4@""U8"1";4!DDDHHHyFFF}.HHHDDDM4@%;"""1";%U4!<+IIIFFFGGGFFFk%#URg"#%FFFj5@.HHH 4@%;"""1";%U4!<+III.HHH 4@;""U8"1";4!<+98IIIDDDM%@% "j " %%<4%%1GGGbGGG{IIIFFFs0>4%[!FFF2DDD2%!:0&49!@%2<2%!D0B!@FFF2GGGwIII%4!0%~!U4FFFIIIGGGxDDDaHHH{JJJFFF4!\0%Y!U@FFFDDDb%44! 0%?!U4FFFIIIGGG{%!D0+!@FFF22%!04!@%2<FFFGGG~<3033$.44<$33<44# .J¢+$33+%N#FFFFFF.4 33= 33 4#FFFFFFFFF{HHHIIIHHH]EEER4%%%vm"u%%%471FFF|GGGh1HHHFFF[@%%""%%!EEEY1GGGiGGGh1FFF[cU4U%%%4]HHH1,!1FFF[A4%%%%4d:1,GGGh1FFF[A44%%%%44DDDNEEET:1,!1FFF[ 4%U%%%4C:1,!1FFF[AU4%%%%4#:1,!1FFF[@%%.""%%!1,@GGGbIIIt@EEEWU%44%"IIIuHHHc<FFFg51FFF\%@44%FFFXGGG5FFFhFFFg51!U4U4!qFFFXGGGFFFhFFFg51FFF\p@U4wFFFXGGGFFFhFFFg51FFF]%U%GGGvFFFGGGFFFg51p@U4qFFFXGGGFFFhFFFg51!U4U4!qFFFXGGGFFFhFFFg51FFF\%@44%FFFXGGG5FFFhEEEo5UEEEb!U8!EEEaHHH5EEEoIIIJJJFFFx48III35>EEEr3FFFy <>13JJJGGGEEEKUGGG9JJJ>13 <>135 <5>13%48>EEEjHHHJJJxFFFl!!4!!>JJJx1EEElFFFKFFFJJJHHHrDDDU!4!DDDTGGGrJJJ~IIIFFFP=JJJHHHnDDD]DDDchU!DDDb;GGGlJJJ~III FFFJJJJJJ{>GGGo!JJJ{7 FFF{1JJJJJJ~>UEEEi/>1FFF}=III7>.!JJJ{7 = HHHn;DDDc!88+1%8HHHK#8HHH88+1518DDDYHHHpJJJIIIFFFFFFGGGgGGGeFFF%IIIJJJDDDZFFFFFFlJJJwJJJ{JJJ1FFFGGGSFFFGGGPFFF13 FFF>JJJvJJJuJJJJJJFFFFFFGGG4;JJJ DDDaDDD\GGGkJJJxJJJ|%FFFGGGFFF#*>HHHk0DDDbFFFEEESEEEb0EEE]IIIp7JJJ1FFF&JJJJJJ 06EEETDDDa0GGGk>*%FFF)FFF#*>02FFFFFFl(JJJ;FFF44;# >391FFFFFF:FFFFFF13 FFFFFF{9GGG{DDDlDDDoEEEw( 29FFF|UGGG9FFFv EEEo FFFu9GGG EEE{!IIIIII!FFFLFFFFFFFFF#!EEEsGGG~HHH !U1PPPU1 CCC@AAA 1uuuUqqq CCCU@@@ 7pppUlll">>>+U<<< "mmm)==iii" >>>+& 7;;; " 8=&˜jjj"===$鴈& ::: " +8=&.ggg;" *7& 999 "jjj)=&.>;" 97& "hhh=&.ccc-777>&!444-"bbb&ʖ___""1 &. 11-?zzz<&xxxCCC%)-U- &ˀ/?- ! @",3&+"!:$1====&9@=63,21***qqqү6&9.6nnn(((TTT/3&=RRR"^^^*&*/ ^^^>*&*/ 7*&: 3&YYY7(? $&!?3*&3SSS?*& !;?>3&$ ? &=; QQQ( & OOO;1NNN7 & ( KKK11*&LLL114'&1'''3&%%%-&$!!!---&+++;;;&6888 &HHH ^^^& <[[[;&ڜnnn"###&!!!"///&! >>>&&̒;;;8.&U9#9&KKK aaa&9#ݶ9& ]]]ttt9&@SSS000UUU&Ɲppp;1%%%&VVV,,,XXX&3###222&.(000!___.&+6 AAA&ccc444eeeɿ=&:>>> QQQ.U +hhh999<<;;;jjjϼ&NNN  ooo>>><" ???qqq'=```"vvv tttBBB""CCCwww? ;$$$}}}zzzGGG!!!118HHH|||yyy"""1[[[MMM%%%NNN6"3$%%$3"11"1"17PPPU1 CCC@ 1$uuuU CCCU@@@7ppp=lll">>>+U<<< "(mmm)=iii" >>>+ 7* " 8޾jjj;"===$騈CCC# :::  +8zzzggg" ;;;=EEE HHH 999<"jjj)9uuu yyy9>;" <<<79 &999<"hhh;9ccc-777>JJJMMM444-"bbb!!!___"999崈  8* . 11-)?(< """:xxxCCC%)-U- 行FFF III.ˀ/?- ! @",$9嵈9+"!.:$1===9U+3,NNN PPP,=*9,21***qqqү6*=< ?nnn(((TTT/3 # 11<"=# RRR7*mmm;;;1@1?*ooo&/ ^^^*@$$$ */ 7jjj"""mmm:\\\ +"""YYY7VVV $8~~~$$$&&&TTT?6&&&'''rrr3SSS?nnn"""###ppp !;?>|||###4.$ ?qqq(((***sss4; QQQ(ttt/"" www<OOO;NNN7 %&"1 ( KKK11 LLL11QQQ```ddd9NNN1'''394 !9%%%- >>>@@@$!!!---,,,""///+++;;;888 KKK90HHH ^^^vvv 8yyy9<[[[;qqq7")Ubbb*nnn"###㵈III"UUUSSS''' 1LLL=!!!///멈999(EEEUDDD:::+ >>> 1}}}屈9{{{CCC?+++;;;-8OOO. AAAxxx. #3vvv@@@;篈KKK aaannn333rrrݶ9ppp111qqq(]]]ttt9\\\bbbظSSS000UUU```4?ppp;%%%9VVV XXX9eee9###2229(000!___߾/// AAA.ccc444eeeɿ=2.&: QQQ.U +hhh999,<<;;;jjjϼ&9NNN  ooo>>><" ???qqq'=```"vvv 0tttBBB""CCCwww? ;$$$}}}zzzGGG!!!118HHH|||yyy"""1[[[MMM%%%NNN6"3$%$3"11"deathkiller-jazz2-native-2a7ccef/Content/Animations/Jazz/000077500000000000000000000000001512772601700235425ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Jazz/jump_shoot_end.aura000066400000000000000000000133751512772601700274420ustar00rootroot00000000000000☄️ 2:# %%%  U  ?U?   ???@0?   ??0??!??!?   !0??0!????0   !0??!0!??!   !0??0!0?!??!  U ?!??!?0!??   !0??0!0?0!?!0?   ?!??!?0?0!?   00??!0??!??!?   ?!??0?!?!?0?  00?!0!00?0?U7  ?!??!! 7?!?0 ;U; 7  0?!,U 70? ,, 7 !?0 ;0;; 0?! ;0;; ? ,0;0?0 ; 0!    ?!0,;!0!0?;,(0!?  ?0(; , !!0&&&?  04!0!?  /7 ,;0!0?/  /7( !CCC0!FFF  /; 0!%44?/ U /7?!BBB4?GGG/0!4%4!@@@0?/   >?4"54%4!?/ 33  /?4!4%!?/ 3/ />?4"4?/  3 /?!4%0!0?/ 39 />?%50!!0!?/ ?U +  >?%!0?0!!00/39 9 />?!!?04??0?>7?04%45"/?>7?!04%4!??!?!!?007 ,;(?!?!?"> ?!!0?0?7( 77?!?!0?'@? !?(;7(?!!?>?!!0?0?7 ;?0!!!0+:0?00? (7 0!!?0!?!'!!0?!0?(;(?0!0!0!!! ?07 (/0!!?' : 0!!0!0?7(!0?0!!!0?6''?7,/ ?0!!0 :+: 00!?( 0? 0?6/ /7;/ ?0?0!!0 '60!0;?;!0?//?!0 ?!06?0?; 0;!4!?/?!,!/ 4!?/ ?!;4?/%4!?/ (;%4!??0!4%4?/(;,;%??0!?!%4%?/ ?0!04%4??!0?4%4??0!0?!%4?!!?!4!??!0?4%?0?0!0?!?00!?!%4?!?0!!?!?0!?!0?!?0?00!?0!00!?!?0!?!0!0?!?0!0?!!?0!0?0?0!0!0?0??0!0?0!0?0?!0?00?0?!?0!0?!0?0!?0!?00!00??!?0!?0?0!??!!0!??0!??00!?0?!00!?!0!?00?!0?!0!?0?0?0?!0?!?!0!?!!0?0??!0???0???!0??0??0?!0??0!?0??0!?!0??!0?!??!?!??0!??0?!??0??!0!!??0!0??0?0?!0?0??!0!??!00!??0?00??0???!!?!0??0??70!0 7?!??0!!0?0!0?7;,;0;, 700?!0!07!?7;,,(?!?7?0!0 ;, 7?07 4;,;,; 7/!?;,, 7?!7 ;40!!4/ /0!; ! ;,;! 70(;!!!?/3/?0; !!0!07 0"!0/?/  3>!; 4!4!!0>04?/ 3 >0 (04/!4050?0/ 3//>7( 0! 0!// /  /?0!54%?/ 9 *9 >7;7?0!>!40/  3/?4%4%44%?/ 9 * /> ;?454!50?/ 3/3 >7?40?0!4!?/ ? >?!%!4%4?/ 3 /  />7( ;?44%4!0/ ?0!0??44!040?/ 3* *9 />7;,?!?!45>/ ?!!?7(?4%40!0!?/ 9>;7(?0!?!?11>'!00 ?!?4%5?>0?7/?0!!?!!?' !!00?0!0?0!01'0!0!?!?7/?0!!?!0?' + !00??!!4?0?''0?0!?!?7 > ?0!!?!0 + 60!0?0!0?0'!? 60?07> ?0!!!0 6'0!0?!!0' '!!0?07>3!?0!06'60!! :+: !0!?/?0 00!0?'60! :+ !!0?0!?/ 0;,!!0?0?6' '60!!0/;,;4?/ ?6!!0!?/;,;!04%40? ;!4%?!?> ?!?!%?/; %4!?!>?!0?4%?> ?0?%4%??!!?!%4?>?!?!4%4?!?04!??!0?4!?0?00??!!?0!?0!??!!?!?0?00?!?0!?0!!?0!??!?0!!??00!?0!!000??0!0!0?0!0?!00!??0!?0!0??0??!0?0!0?0!0?0??!0??!0?0!!0?0!?0!?0?00?0!0?0!0?0?00!?0!??0!?!0?0!?0!0?0!!?000?!!?!0?0!0!0?!?!0?!!0?!0?!0??0!0!0!!0?00?0???0??0??!0??!0????!??0!??0??0??0?!0?0??!?!!??!??!??0!?0??0???!?!0?!???!0!??0!!??!0?!??!00!??0!?00??0!??!0??!0?!??!?0!?00!???0?!0?!0!!?0??!?0?0!?0;0;0!;7??0?0??0;!,,;,;7!?0!0!07?!; ;,,;!(0 ;,,;7?!;,!40!0?0 ; ,,70 ,;!00(;, ,, ;7/(; 00!0//? ,;5" ; " / /7 0!5>!"/3  /7;; 50!01"> 3 /7?550?> 3 3/;0"5!!0EEE>/ 3 /?!540?'>/ 3/3 >0!5"!0!>??!0>/ 3/ /?%4"54%40!?' 0!0>/  >  />?!05>4%4"?'0!0>/ / 3/?!?!"0!!?' : 0!0>?%""!%504!?: 0!0>/ >3/?!0?!!0!50' : 6?4!0"4%4%?: 6>/  >?!0!!?04"?0? 6'?0!?4%4!4!4?0 6>?!!0!00!0?0!06' !!?%4!!!?0!060!: '!4%4!?0?' +0!0?!45"0!0 + '04%?? +60!0?1"!06:+ ?%!??!!6004!?06' :!!0???!?0!4%?6!!?!0?0!!?4%4??!!!???0!!?!%4??!00?40?!!0?!0!!??!?!?%4?0!4%40??0!?%!??4%40?0%4%?0??04%4???!!04%4!?!0??0!4%?0!0??!!?4?0!0??0!!%4!?0!!0??!!0!?0!!??!!04%?!!0!??0?0!?0!0??!!?0!0?!0?!0??0!!0?0???!0?0!?!0?!??0!0??0?!!?0!!0?!0?!0?0!!0!0??0!0!0??0!0!!0!0!!0!0??0!!?0!0??!0!!00??0?!0??!!0!!0?!0?0!0?0?0!0!00!0?0?!0?00?00!0?00!?0?0deathkiller-jazz2-native-2a7ccef/Content/Animations/Jazz/ledge_climb.aura000066400000000000000000000210271512772601700266440ustar00rootroot00000000000000☄️ 5@  &&&??U@0?/UU ?@0??0!0?/ / @ />??00?!0?!??0?/  / />?!??!0!0?0!0?0!0?/ / />?!?00!0??!?!?/  />?0!0??!0?0?//  />/?!??!0?!?/  >/  >/  >??0??!?!/ /  />>/  ?0!0?/ /  />?0?///?0!!0??!?0!0?/  / />>/ ?0!0??/  / />?0U76U@6?0??7(UUU !0?/ / />'>0!0?00??0 (/ / />?0!( (7' U6?7 ;UU,; 70?/  />:'0?0??0!;,;(  />0?!( ,(76:67 ,; 7/ KKK /> ' ?0! ,;(? />0?!( ;0?'+: 67( ,;,7/ DDD%===?>': '? ; 0!0? ? >0(;0!0?/6: 67(@0 7>/ %<</ ?0!0??!' :'?0?0;,;040%5?!?0??!? ,0!0%4!?  /  /6'0!0??!%"5!0!!%?>0!0??0?0>>>(' : '?!?0? ; 044!GGG%?!??0?! ;405%4?> /  /6?00??!0450!05?''0?0??!0 (0?0!6?0?!0( ;05%0?0NNN?0?? !544!?> / U/?!0??%054%40?': '6?00; (?0?0??!??( !50%0?0??!0?(?5%4!?0?> ??!!0?0!%54??: '6?!!!; (?!0??!??70!40!??!??!54%5!0!0FFF'0!0??!!0?004%!??0! '6?!!0; (?!?!??(70%4!!?0!0??0??%!?0!0??': '!0??0!!0?!!!!?0!06?0!0!0?; (5?!??0?!0??%0!?0!0??!??!%0!0?0: '?0!0??0!!00!0!?0!0??0?0?; (!?0?!??!0!?!!0??0??0?0!!0!0 '6??0 0!50?!!0??0? (!???!?!?!0?! !0??!?/0!??0!0?6? ,%0?0!0??(?0?4!0??0?0??0?:'??!?/  >! ??%?0??0EEE??!?(%0!0?0??0?/00:0!??0?/ >!;!>/0%!?!0(??!040!!??!?0?/  ?!: '0??0?/  >!0,!%!?> 0!4%4%??!(?!??!!?!??0?!?/!0?0'!??/>0, !0?>0?4!!?0(?!??0!?!???0?/ >0?0!0!?/  >, !4?>?44!?????(/?0!?4??/ /0!?4?/ > !4%0?> /0%4%0????  /?0%!/ >?0%4?/  >!!%4?>/?%4!????!?/>0 ?/  >?!%!?/>0!04%?>04%?!?!0? />0 0>/  >0!4%??0!0?0!?!%?0?0?/  >0,?>/ />00?440?0!0!0?0?04%???/ / / >, ??>?!!0?40%!!!0?!%0!/ /> 4!??!4!0!!0?!0!!0!!0?!0!%40!>  >0%?!4!0?0!0??!!0?0!!0!0?%40?!!>/ />0%0?!??0004?0!000?!0?0!!>UUU!!%!?777!0?!??0!?0??0!0?0!!4..!?0!?0!?0?0?0??0!!!0?0..2?0!!0?0!0?!?0?!0?0!0!0?0.2.??!0.!0?0!?0!0?!0?0!!0?.20!.0.20?.2!?0!!??0?0!?0!0?!0.2!0.!?.2!.#0?0!0!00!0?%!0?!!0?!0.2#2!0?.0?.#?.#!0!0!00!??00!0?!0?2#2!0?.!0.#0.#!?!?!0!0?00?0?.2#2!0?.!0.20.#!?0?0!!0?0?!?.2!0?.20?.#0.2#0?0!?0?2?OOO?0?.2?0?.2#!0.22!??!!0.0.20.0?.#!0?.0?00!.2?.#!?.0?.2!0?.2??!?!0.#0?#!0.0?.?0?0.!0?.20..!0.#2!0.2#!0.#20?.#2!?.20?.#!.0?.2.!0.20?.2.0?...?0?.??0??!?????0????!???0??00!0?!?0!??0??00????0!!0??0&(?0!?0??0??0??0!0?07 (0?0!????!?0??0???!??.; (0??0?0!0?70!?0?0??0?0??!??0??77;(???0??0!(70!?0&7((7?0??0?0!0?7 7;(?0!0?!??0!0?0 ; 70!?!07( (7?!?0?( ; (7 7(0!0?0??0?0( ;,; 7!?!?07( ;,;7?0?( ;,; (/7 (!!?0??!?( ?0?7/0!?07( ;,; 7(?( ;,,;(/ /((0!!00??!?0 0!!0?/  /0!?!!7(  7; ( ; 0(/  /( (!???!%?!0?'/  /!?7( 0&(7( ; (  !0!0 (/ / /( (??!(!%0!04!? '>/  /  /?7?!00!07/( !!!0?(/  >/  /0? ; ; (??0?0%0514! '>/ / /?0!4!0?/  /(/4!%!0!?/  / ?!%? ,;(???!4!!%0'6?>/ /?%!!0!?0'/ // /0?!%!40?/   ?0?!%0 ;(!00?04%4%%!?6? / /?!!%0!0!!?>/  // / /0?!4!!?>/   ?0!0?!40!(70!0!!?!#??0!0?>/?!4!4%4!?' &1 // / /0!0?!4%!54!?>/ / />0!!0?0????!0?5!''0!0?/ ?!04506'&*  // / /0!0?0!%10??0>/>?0!!?!0!?!00?!?: '0!!0?/ ?0!0?!06& / // / /0!?0!?!0%%??0!0>?;?00?0?0/?0!0?0! '6?0?/ ?0!!0!0?'EEE0!0>/ /// /0!0 ?!?00!??0!!0!0;?!0?/ /?0!0!0?'6/?0?0!0??! '>/  ///>,; 0!?0 5"/?0!!!0?0!0?!0?/ ??0!0?6/ ?0??0!?' :0!00>/>/ >?0! !?!0?:  / /?!!?!!0!0? '/ 0 0?/ ?0,0?0!0?6' 00?0?0>/ >0???!0?! /  /?!!!!0!0?!0? '/ 0, 00?/ ,00?206!0?!?!>/?0?0??0!0?0?  /?0!!!?00?/ , (!4?>, !4?!022#>0?0!!0??0!!0? >?0?!0!0?!??0?/ (0!40?> ?!00!#?!0!0!0?0!0??!0??0?0?0!!?00?0!0/ (0!0?>/?00!!22.0!!?0!?0!!0?!0?%?0!0?!?0!?0?> ?!!?0?>??02#.?!0?!??0!!0??%!0?0??0!0!!?>/ 00!!!0?0!?#90!!?0!!0??!0?0?!0!!0?>/0!0!0?0!0!?0!0!!0??0!?0!0?>00?!??0?!?0090?0!0?0??0!0?!0??!0!?0?0!!?!000.!?0??0.0!0?0?0!?00?00?00!?0#.!0?0.0?0??40?!0!#2.?!?0!2.!??0!.2?!??09#27.0!??.0??!?.#0?!?&0!#2.0??0!?!0?.!0?22.0?0?0?.2!02?!0?0?.2#020?00!0.#02!?00?!!?..2!?20!!?00.2.!0.#0?0?!!?.2.!0?#0?0!?0!00.2.!?.2?!!0?!0??..?270!00?0?0!??0??0?!??!0??0??0????!??0?0!??!??!0??0!??!0??0!0??!??!0?00??!0??!????!??!??!??!????0!??0??0??!!???0!??0?( (?!?0!?,0!!??0!??0( ; (?070!?,0!??0!?( ;; (?07;0!???0( ;  ;, (?07;;0!?0??( ;  ;(?7;?!0!0?( 0?(7;50!/?!40!0?7'!50!!5/   /?!?40!0??%50!EEE5>  3 /?4!50??!!50"5>  3 /?!054??!05!>  3 >/?!0?0%5??0!0?054"5"40+> /3 /?!!?04%??!!?!0%4!%0+:?> / />?!00?0%!04%0!??!0??04?&'!?>?!!?!?!0?00!#!??0??!?0!0??!?/ ?0!?04%!?5"?/?0?!4?!50/ ?0! !4%4>/  /' :'?!%4?05/ ? : 4%4!'?/  /' +:'?!%4!?  !0? : !40 '!?/ /  /6: ?!4%!?/ 0!0? 0?0!? ?0!?/ /  /6?!?!4!? ?000!?6!?0!?>/ / /!!?00!?0!04!?!0?00?>>0!0!!?!%?0? 0!0!0%4!?!!??0?00!000!?!%!?!0?!!?0!!%4!!!0&0!?0000!!?0!0??00?!!0?!?!0?0!0?0!00!?0!0???0!0?0?0?0!??0!004!0?!??0!0?0!?0?0!0?0!0000!???0!??0?!0?0?!??0?0!00!!0?0?0?0!?0!!0?!00!?0!0!0?0!!0??!0?!!0??0!!?0?0!00!0?0?0!0??0!0?00!!0!??!0!0??0!!0??0deathkiller-jazz2-native-2a7ccef/Content/Animations/Lori/000077500000000000000000000000001512772601700235315ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Lori/eol.aura000066400000000000000000000556621512772601700252000ustar00rootroot00000000000000☄️ :$"$68???U@9&9&U9&9&9&&&9&&&99&&&&9&&9&9&&&&9&&9&9&@9&&&9&9&9&9*9*99&&99&&99&9*99&9&9* *99&999 *9&&&&& * *9&&&9 9*9*&9&&* 91 9&9&&* *5 &9*9&1&** 95 0&& *9&&* *&*99 *50*09 YYY * 99 9&9&* 19 *, *9*9* *&* 99*9&** *9*9* *9 "5*&* 99 *99*9 9*9 *5 &&*9 *9&9*9 *9*9 *50 0&*9*9* *9&&9*9* *&*99 5009&*9* *9U9**&*9* *509*9* 9799*&*9* 9*&*9* 9"19&&&&9* *&9* *9&*9* "5*0$$$/* 9((&&99*1&&*9 50*9&0//9 *((9&9*(9*&99 *50&/& *^^^((99* 1(*(9*&99 5"9/9 *7((9&9 (((*9&/*9 9 *99/ *9199&* 9(U(*//&9 9* *9&/*9* 9'&9 *9(( */ '9 * 99*/9& 9&19 9&9/ *(( */ 9* * (9(* */ *&1((179//* 9&((&9 9/&&*9 ((1( *9*9 ((&/* * 9**&&&&9( 9 *9&&* 9*(7&/*9* 9&99^^^((* *9*9 (((&/ &U1&*97((9&&*& ((7(&) (&7(7&((7(&) ((&9 *9*)(779 ((&&7* 91(799((* 79*(7& *&9&&9 *1(99 9((1((9& &9 *9((7((9&* *(((9 9 9779*9((9 &9&* 999 9(((9 99* &9&9*7(79 *9&* &9&9&&9&* 9&9&&*&9&&99&999&&&9*99& 9 9&999 *9 &99* * 9&&99* * *&&99& 9 *&&99&9 * *&&99 *9 *99 9 9&99&* 9 9&99 9 *&99 *&&9* 9*&&&**9*&&&&&&&&&&&&&&&&&9*&&*9&9*&9*&&9&&9999&&&9*9* *&&91&&9*9**9&9&9 *9&&&&9*9* 9&&*9µ*9 *99*99*99* *U*9&9& 9*9 *9*& 59&9* *9&9*9¨&9 *509* *9* *9 9*9* *509* **9 *9 *50* *51*9 *9* 9*&&  50& 9 *9& *9* "50*0& 99 *9&&&9** 9 "50 *5&&9 9&&&9 9"9& *5099&9 (9( 9*9&&9*9*9 * *& 950*9* *9&9 *(9 9*&&9* *9 9 *&9 *501** *9&&9 *9(9 1(&9 * 9(9 &&9* *9 *9* *99* 7(9 * &* *99(*9(* &* *9*9* *9& (* 9(1* 9* * &&7(91( * * 9U9*99&*9*/*  &&&&9*9 &((79/ *9(9( *9&9*9*/ 9((* *&*9&&*77&/9* *&7(( 9 &&*9&/*((* * *97(7/&*99&(É *9 9&&//1(7&9* 9* *97(((9//&&97É* 9* *&&//8(7&9* *&(7(9//&7(7*99 */7(&9* *(((9//*((//9 */((&&9 (9/* 9(/9* /VVV& *(9 *((/&9* &' @ *((9* 9(//9*9& *&9*9*9* &9*9/9*99*9&&* 99 &9*99* *//&* *9&* 9&&9*9*&9*//9 *9&9*&*99*9&9/9* *&9&* * *99** *&9**99*  * 999 * *&9* 9 *& * *&99&* 9 9 9*  9 9&&& 9 &&* 9 99&9*9*9&&9&&&&&&&&&&&*9* 9&&9 *9* *&9* *&&9&* *9* *9&99*99&** *9* *&91"ĵ989 *9 &9&"19&* 1*9 *999*9&&9 951*9 99*9&&&* "5*9 9&99 *9&9&&* *50 *9 *9& *9U1*&&9 950*9 *9* * *9&9&&*9* 91*9 *99&& *9 *9&*&1*9* 9*9* *&9*1"*9*9*&&9*&9 *&99*9&* 9"5* &99&& 9*9* 9&&&&&9 50*5&&**9&* 9 ]]] 99 9&&* 5090*&9*99*9 9 * *(((  5099*1&9 9*9* *9 1 9(( 9&9** 9 *9**9&9* *** *9*  *(79*9 9* *99& ** 9* *&9 *'/(19* *&& 99&&9 &9 9 "* &&9 9//* *&&&* *9 *&* *& * * 15 *0 //* *719&9&  1(9*1* *&9* 9* * 50*909* * //9 *7@ *9 *&9 ** 1( 9&* 9 1*9* 9 501"* *//9 *9(9 9 &9 9*97( 9* "*9*  *50 *&UU U9 9*9]]] * 9 &* 9(9* *^^^ *9*9 95 *9*9 '* *9*91U 9* *9 *&9/ 9( *199 *99 *99 *(‰9* *9 *&/EEE* 97((9*9&&9 9*9*91((&9 *9 91&/ (7(7&&&9999 ((&((7* * *"/(79*9 7(77(* '/&9(7&&'9*9*7(7((*&* ((&,/9*97( *9&9 9((8&// ý(819 9&9 9* *77/* *9((" 9&*9* (&@/9 *99*9  *('/ ** & &9 &9&* ((9 9&9 *&* *&*9*9 *1(* * 9** * 9&9*9&999*9&*9&&9*99*&99&9*9*9& * * 9 *& ** *&&9 * &* * 9 * ** * 9& 9 9&* 9   9&* 9 * & 9 9&&&9&&&&&9&9&< <*"< 91 9&&9&<< )&9&<*&&< ) <) ) 9 << <)) <8&*< ) <*9&& < )8 )<&99&& <  ))<  < *9*9&&9*9 < )<)< < < **9& 9 << < )<)< <<* *9*  <)<)<)<) < )*  *9 ) <)< <)<<))<<< 89*9* & )< < < <8<< * 5*&&* * < )<< ) )8< ) 5 *9 *<< < <))< <  50*99*9  )< < )<)<<<  <9  501"*  )< )< < 98< <<)<9  *50 *   << < %%%89998888'''^^^>788< < < < < & * 95 *9* *<<)< < 8)<  8>9888EEE8>89898898998999989 >>>>88<  <)<8<) )< 999999999 ** *99 *&< < <) < < <)??  999*99*99999*9*99*99>8*998)<)< )<)< << <98EEE998899EEE&* * 9*9*91)<  <) )< << ?99*9*9***9***9***9***9**9****9*****9******889* **8**9* *9*8*8* *9* * 8 <)<<) < <)<& *999 (( < <<<<) <  ) 888*"EEE**8*8*"EEE*98>*>*8>8*>*89*98*8>*>*9*9**99** *<< << < < <)<99**9**9***99***************99*9 77  <) < ) <  < >>8>8>8> 8>>8>>8>>8>>EEE8>88>889*8>88*898*9*/*88"8 998* >**8*"8  *  * 95 <<< <)< )<  EEEEEEEEE**EEE*99*9*7UU79<8< < ) <UUU07777777700SSS007!777!077777!777!777!9*797*7 *!9* *7*7 7**!*9* 97*7 7*9!*9* *9 )< <)  < < </>/>>8/8>88/8888/88*9*9*9(98< < ) < <07!7!!7!!7!!07!07!77!77!7!!!7!!!97!9!9*79!9!9999*9799999!9*9<) <)<77777777777777777/77777*9'9* (988) )<< ) << 00!7!7!7!>07807EEE>77EEE>78>7!>777!>7797!>7!97**7>7!79*7***/7!79**9*<< < 7777!7777!77779//9 *9("< < <)<) )<.((8888)88878)79)**)*898)79!999!9*797**78988**9*))< < ))<)< < ) ) 7777777/7/7!/7!!7/!79// *99*8)  < )< <. 1 1118118*)))8*)))*9)))**)))77 8*98 9*97*7*9* **977998 9*9*8,,,  8 <<)<< )))))))77)9/* ** &<)8 < < ) 9999*9999999*9999*99*9999999999*999**9****9*9*,9 9*9*9* *  9  < <)< 8 <) < < ))))))))))))))))))))**333**))9&&/9 ((9 9<)<<<899****999*999****9*9*****999* 9*9*9*9*9*9*9 9 * *98 <  <) ) < )99999*99**99*9**9*999**999*'/ *1(< < < <  <)< 999999999999*9*999*99*9*999*9*99*9*9*9**9* *99 <) 8<8<)< )<99**99999&9&* 9< < <)8)999999*999*991 < < ) <))9999999999999999&*9*9&<)< < )99)< < < < ** *& <  << <)< << <) ) )9 * *<   <)<) ) < < < < <* 9 < < )<)< < )<<  <) < <)8 < 9 8 )< 8)< < <  <)< < <<9& )< ) < < )  <8)< 8)&< < ) )< )8)8) <) 8) 8) 8)< <<8 << ) )< 8<))< <)< <) ) <)<)8)<)< 8 <   <) < < < < )  < <)<8 ) <8))  < <<)<)8 )<8 ))< < )<) <)<) < )  <  <)< < <) ) <  < <  8< < <8 < <) )<)<)  < < 8 < <  << <8<) << < <8<<)<)<  < < )<< <8 < ) <  <)<)  )8) ) )<< <8  <)  )<)< <) ) < < <  ) <8<)<)<)<  << )) <)< < <8 << <) <)<<)<) < < < < < <<< <<< )< )  < < <  )8  < <  < < ) < <)<8<  <)< < < < ) <)<  <) <)<  <)8)< < < )  << <8 ) < < )< <) < ) << <)< )< <)< )<<<< < << < <) < <)<  < < << )<) )< <)8))< < <<) 8<)< )<< )< 8 < 8<  < <) <)8 < <<<)<8<8<<)8  < < <)< < 8)) )<)< <)< <)) < <)<)8 < << << <)<)< < <8 < < < < ) <  < << < < <8 )< < < 8)8< 8 < <)8 ) < )<)< < <8  <<)  < )< )8)  << << 8) <)<)))< 8<)< 8 ) 8 8)   <)< <)< <   8<  8))<8)<< )<) <  )< <)  <8) 8< )< )< <  )) )8< ) < < <) < < <) <)< 8< <) <) < <))<<  < <8 ))< < < <<) )< <))< ) < )8 <)<)< < < )  )))<) < <)< 8 <)< << 8<)  < < )< < ) <)< <  )88)<< <)<< <<8< < ) )8 < <)  <) )8< ) < <)<8< <)8 <) <<< ) <8<) <)< ))<<<<   <8< ) <<))< )< <))<) <8 )<     <<  < < <) << < <)) 8< )) )<<)<<< )8 < <8)< <)8< ))<8<   <) <<)< 8<8)8 < )<8 ) < ))8< ) < < <8 ) < <)) < ) <<)<))< < 8 < )< )<<  <  << )< )   ) <) ) 8<)))<))  < )< < < 8 <  ) < < 8< < )< <))< <) < )8< )< < <) < 8< 8< 8 < )<88< 8< << <<8 )8<)<< <))  <8< )< )  < )<  < )< <8< 8<) )< <) <  )<< < <) )  )< < ) 8 < <) <)<8)) <<)<<<) <)   ))deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/000077500000000000000000000000001512772601700240325ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/checkpoint_xmas.aura000066400000000000000000000255161512772601700300740ustar00rootroot00000000000000☄️ *Hk>UU!!!0!!?0!!0??0!!0?!!0GGGDDD0!0?0!0?0!01U BBB?0!0?!!0!0!0?"@;11?0??!0?0!00!!0?!0?0!!0?0?!!0!0!!0?!0!!0?;!0!!0?!!0?;!0!0!!0?!!!0?0?!;!0; !!0!!0!0!!0!0?0?;!0;!0?0!!0?0!!00!!!0?0?,!0!0?; !!0??0!0?0!!0!0!!0?!;0!0!0?0;;!0?00!0??0!!0?0,;!0!00,;!!0!;,; !!0?!;!0!0?0!0?!;!0!0?, !0!0! ;!0!;!00!;!0?,;!!0;!0!0! ; !!0!; !!0!00!;!0?,; !0!0;;!0!0! !0!,;!0!; !!0?, !0?,; !!0!!!0!0!; !!0!0!,;!0!0;!!0!00! ;!!0!00!!0!;;!0!; !!0;,;!00! ;!0!;,; !!0!00;;!0!0?! ,,; !!0?! !!0!! ;!0?!;,; !!00!;,;!0!0!!0!! ; !!0!00! ;!0??! ; !0!0?!!0!0?!! !0?!! ; !!00! ;!!0!0!!!0!00!! !0!0??!! !0!0!!0??0!00!!0?0!!!!0!0?0!!!!0?0!0??0?0?(( ;, ( ,; (,;" ( ,;@@@"(FFF,;5"",55""( ("551U ),;(U" U )551" < );,;(U"1"1)U)15"1"))8; EEE"151"1)85""5"1U1"1)85"111"1"5111"""511515U1""551 )115U1"1EEE""51 < )1"15@1"551") )811"5"1"55"U1)8EEE1"15"""5"5"1""5""51"5"EEE""51EEE" 5"(("15"5"511;5( ; ""515"51";5;,;("15"51"" ;EEE"( ;,;("1EEE5"15"15"1 , 11(;,;" (U1"51EEE5U"15""";, "(EEE, 5"(EEE515"U"1"5"51;, U1,;1"5"51"1U551;, 5U1", 51EEE51""""115115";EEE1U1,15"5""11"EEE5"15" "1" )"51"11"515EEE51EEE1 < )5"1EEE51"1"5"1"))8515???@1"55"15")85"15&&1"1"55U11"&9@"1"""5"1"1EEE111&&&&1"151"5115"1&9&0!!0?&911551"5""15"&*9&!!0?0?&9&U"115"5"5"!!0"1"51"!0&&0?0!0&*9&@"""515"0!0?510!?&*9&0?0!0?9*9@"5"5"?!?"51"0!0&&0?!0&9&1"1"1"(50""5?EEE5?!0&*&0?0!0&9&@1""( 51)?EEE150"0!0?0!0?&9&@51"( ;51 8""51"5?!?&9&0?0!09*95"1""1;,"")8115?EEE5!0&9*9&0?!0&&5"@1"1;,"0EEE505!0&&?0!0?&95@1 ;,1115"!00&9*9&?0!!0!0?&9*9&5@;,1"11"!0?90?&9&?0!0?EEE5" 55"1191"0?00@!0?&?0?0?"5EEE5"15019*90?0?!;!0!0&9*9&0?0!0EEE51"5EEE51?!;!00&9&?0?0!0?"1"555"5104!0!0?!; !!0!09*9?0!0?1&1""5""5"51!;!0!,;!004!0!0?0!0?1&9&1""5551"5!;!0!0!; !!0!0!;!0?0?@1&9*9&1"1551"5!; !!0!;;!0!;!0!0&9&1"5"5"5!,;!0!0!;,; !!0!0!; !!0&9&@"11"""51!; !!0!! ;!0!,;!0!0&9*9&1"5""1!;;!0!0!! ; !!0!0!; !!0&9&1"1@!;,; !!0!! !0!;;!0!0&9&?0!051!! ;!0!0!!!0!0!;,; !!00&9&?0!!05ĥ1!! ; !!00!!0?!! ;!0!0?0!9*9?0!0?0!?15"!! !0!0!! ; !!0?04!0?!0"5"?0!0!!!0!! !0!0?!;!0!00!?1"?00!!0!0?!!!0?!;!00!00!0?1"0!!0!0??0?0!!0!0??!; !!0!00!0?!4!0?0!!0!00!0??!,;!00!004!0!0?!!0?!; !!0!0?0?;!0!0?0!!0?!;;!0!; !!0!0??0!!0?!;,; !!0!0!,;!0??!! ;!0!,,; !!0??!! ; !!0!0;,,; !!0!0??!! !0?! ;!0!0!!!0!0!! ; !!0!0?0!!0?0!! !!0!0?U"U"5151"1@15"1@15"511"5""1"5""""(EEE5"""51"(EEE5"(11"( "51"51"51"( "511"1( ;"(11( ;,5" "51"5"11( ;,5" ""1 ;,";1" ;,";,"51 (("5"1" ;,";,"11 ;,5 ;(1;,"( ;(1; (1"( 15"1;,"( ;"15;@ 5"1 ( 5;,; 1""( ;,"; @ 5"1 1EEE555 ("51  ,  11" ;,";( "51 15""51" EEE55";"""( ;  )@;,"1; EEE55";555")"5""5" )"1@" ;" < )@ "5";"5""5" )551" )"555" < )@51))855" )"555" < )155"51))8155"))85)8""5 < )155"))81"")8@5"51"85"55))8@5"51"8"5"1"1""51"15""5"55)81""51"1"15"15"5"155515"5"115"5"1"15"5"1"""5"1551"1"511"1"""5"1U515"""51""5""5151"15""51""5"151"@"5"U""1"1""1151""1""15U""1"1"EEE"5115"1"5@1"5""151"5"1"5"15"11"1511"1"15"1151""5"151"15111@5"EEE15115""51151"511@5""1"511151"5"15""51"11151155"1151EEE""5"1151151""1"1U1"1511"5"U1&"9&9&1151"@51&9&&0&&0!01EEE"5"?0!01"1&?0!0&&&?0!0&9*9&??0!00&9*9&?0!!0EEE1"1?0!!0EEE&?0!!00&*9&?0!!09&?0!!0?90!0?0!??0!0?0!??0!0?0!??0!0?0!?*90!??04!0?!0?04!0?!0?04!0?!0?04!0?!04!0?!0?!;!0!00!??!;!0!00!??!;!0!00!??!;!0!00!?!;!0!??!;!00!0?!;!00!0?!;!00!0?!;!00!0!;!0!0!0?!; !!0!00!??!; !!0!00!??!; !!0!00!??!; !!0!00!?0; !!0!0?!!??!,;!00!0?!,;!00!0?!,;!00!0?!,;!00!0?,;!0?0!!0?!; !!0!0?0?!; !!0!0?0?!; !!0!0?0?!; !!0!0?0!,; !!0!0?0!0?!;;!0?!;;!0?!;;!0?!;;!0!;,; !!0?!;,; !!0!0?!;,; !!0!0?!;,; !!0!0?!;,; !!0!00! ,;!0?!! ;!0?!! ;!0?!! ;!0?!! ;!0!! ; !!0!0??!! ; !!0!0?!! ; !!0!0?!! ; !!0!0?!! ; !!0!0!!; !0?!! !0?!! !0?!! !0?!! !0?!!!!0!!!0!0!!!0!0!!!0!0!!!0!00!!!0?0!!0?0!!0?0!!0?0!!0?deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/crate_ammo_electro.aura000066400000000000000000000013011512772601700305230ustar00rootroot00000000000000☄️   ---UUUU+:  :+U+: ' :+:+ +  ' :+ +:+ ++: ' :++++:  : ' :  :+:'  : :++:+:+:'''' :+ :+ : : :'' +  : :: :' : '  : :+: ]]](:+:+ :':  '  : ((:++:+ :':' + : (( (+: +:' :' +:( ( (( : +:' ':'   ((: :' :  :'  :((2(: :' : :' + :(:+:+ :' : :' + :+2((( +:+ :' :'  : (2(:+:+ +:' : ':' + +:( : +:'+::':++:+:++ : +:' :  '' ::+++: +:' '' : : : ' +::  :+ +:++: ' :++:+: ' 'deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/crate_ammo_pepper.aura000066400000000000000000000013201512772601700303620ustar00rootroot00000000000000☄️   ---UUUU+:  :+U+: ' :+:+ +  ' :+ +:+ ++: ' :++++:  : ' :  :+:'  : :++:+:+:'''' :+ :+ + : : :'' +  ]]]( :: :' : '  : :((:+:+ :':  '  : ( :+:+ :':' + : ((2(2 +: +:' :' +: (( : : +:' ':'  (2 (: :' :  :'  :+ (:: :' : :' + :(( ( ((+:+ :' : :' + :( 2 (:+ :' :'  :((::+ +:' : ':' + +:((: : +:'+::':++:+::++ : +:' :  '' ::+++: +:' '' : : : ' +::  :+ +:++: ' :++:+: ' 'deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/powerup_empty.aura000066400000000000000000000165571512772601700276410ustar00rootroot00000000000000☄️   MMMUUU)88) )88) )88)  UU< )8)  <-< )8)  <-< )8) )8 <-< )88) )8 <-< )88) )8 <-< )88))8< )88))8< )88))88< )8))8Ę<-< )8))8<-< )8))888)8<-< )8)) < )8)&) < )8)8)8)8))8) < )88%%%UU )88  )88)U1)8)8)  )8&&&1@1) <) /8/11) <) /8/1"1)8)8)8) <) /88"&) < )8/88"&) < )8/881")8)8)8) < )8/881) < )8881) < )8881"8)8)8) < )88ɎUU828818)8)8)8)8288) )88) )88)1)8)8) )88) )88) )88)1)8)8)8)8) )888 )8888 )888)8)8)8 )8888 )88&8 )88)8)8)8)8 )88&/ /8&/ /8)8)8/ //8 )8/8 )8/88)88)8 )888 )888 )88)8)8)8)8)88 )88))888))8888)8))88)&& < )8) < )8)8)8)8)8 < )8 &8))8 &8))8 88))8) ) < )88) ) < )88) ) < )88 ))8))8 ))8))8 )8))8)8?8 )8)8!80 )8)88 )88)))))88)))))88)))))888888)888888)888888)8)8)8)8/ ) U /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<@< 282<;< 282< ;< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )8)  <-< )8) )8 <-< )88) )8 <-< )88) )8 <-< )88))88))< )88))88) << )88))88) << )8))88)) <-< )8))8) )< <<-< )8))8) -<<<-< )8))))))) < )8)<-<--<) < )8)) -< <--) < )88) )  ))  )88<- < <)<  )88) ) <<  )8/  ) <) /8/8)<)< <) <) /8/ )<- < < <) <) /88 )8) 8) < )8/88< <-<) < )8/88  < <-) < )8/88) )))) < )888  <  <) < )888< << <  ) < )88)8 ) )28 < <- -< 28/ < )< <- 288))8 8)8 ) )88-<)< <<) )88<)<<<<) )88)))) ) )88<<<< ) )88 <-< < <<) )88) 8 )))8 )888< <)<-<8 )888<-<<<-8 )888)) )8 )88< <-<<8 )88< <<-<8 )88) )8))8/ /8 -< )< < / /8<-)<)< -<- < / //) )) 8 )8/ < -< <8 )8/ - < <<<8 )88)))8 )88< <<)8 )88 <<)8 )88))) ))888) < <-<)<<8))888) ) < <8))88)))) < )8) <)< -) < )8)8))81" < )8 )) )8))8 )  )88))8 )81"UU"8))8) ) < )88) ) < )88) ) < )88 )""1))8 )""1))8 )""%1))8)14181" )8)1"4%"1814" )8)1"4%"1814" )88)11))))188)11))))"88)11))))"888888)888888)888888)8)8)8)8/ ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<;< 282<  < 282<< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )8)  <-< )8) )8 <-< )88) )8 <-< )88) )8 <-< )88))8< )88))8< )88))8< )8))8<-< )8))8&<-< )8))8&&<-< )8)) < )8)&&) < )8)&) < )88  )88  )88&  )8/11) <) /8/11) <) /8/11U1") <) /88") < )8/88") < )8/88"1"5) < )8/881) < )8881) < )888""5"1) < )8828&28"1288) )881") )88) )88) )881"5) )88&) )888 )888"5"18 )8888 )888&8 )88"1&8 )888 )881"/ /8/ /8&/ //1"58 )8/8 )8/8 )88"5"18 )888 )888 )88"1))888))888))88) < )8) < )8) < )8 8))8 8))8 8))8) ) < )88) ) < )88) ) < )88 )""41))8 )8))8 ))!8))8)14181" )8)8!8! )8)80!80 )88)11))))188)))))88)))))888888)888888)888888)8)8)8)8/ ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82< ;< 282<;< 282<;< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88)  <-< )8) )8 <-< )88))8< )8))815<-< )8)15"1) < )88"1  )8/11) <) /88"&) < )8/881) < )88@288@&) )88@) )88@8 )8888 )88/ //&8 )88&8 )88&))88) < )8 8))8) ) < )88 ))08))8)8?08? )88)))))888888)8)8/ ) 3 /8)88) < <-< )82<(< 288)) ) <-<)88)88) )8deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/powerup_shield_laser.aura000066400000000000000000000177761512772601700311450ustar00rootroot00000000000000☄️   MMMUUU)88) )88) )88)  UU< )8)  <-< )8)  <-< )8) )8 <-< )88) )8 <-< )88) )8 <-< )88))8< )88))8< )88))88< )8))8Ę<-< )8))8<-< )8))888)8<-< )8)U) < )8)) < )8)8)8)8)8)8) < )88@%%%UU )881  )88)1U8)8)  )8&&&1@111"1) <) /8/1111"1) <) /8/1"18)81"18) <) /88"11""1) < )8/88"11""1) < )8/881"8)"1""18) < )8/8811"U"1) < )88811""1) < )8881")81"")8) < )88"1""1UU'8'"1""12'8'81")81""182'88""1"1) )8'8""1"1) )8'8)1)"")81"18) )8'8""1) )88""1) )88)1"")81)8) )88U"8 )8885"8 )888")8)8 )888""8 )88""8 )88)"")8)8 )88""11/ /8""11/ /8)"")1)1)8/ //"118 )8/"118 )8/8)")11)8)8 )88118 )88118 )88)11)88 )8811))88811))888811)8))88)1 < )8)1 < )8)8)881)8)8 < )8 8))8 8))8 8)88))8) ) < )88) ) < )88) ) < )88 ))8))8 ))8))8 )8))8)8'''?8> )8)8!80 )8)88 )88)))))88)))))88)))))888888)888888)888888)8)8)8)8/ ) U /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )8'2<@< '2'8'2<;< '2'8'2< ;< '2'88)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )8)  <-< )8) )8 <-< )88) )8 <-< )88) )8 <-< )88))88))< )88))88) << )88))88) << )8))88)) <-< )8))8) )< <<-< )8))8) -<<<-< )8))))))) < )8)<-<--<) < )8)) -< <--) < )88) 8  1))  )88<- < <)<  )88) ) <<  )8/ 1 1"1) <) /8/8)<)< <) <) /8/ )<- < < <) <) /88 )1""18) < )8/88< <-<) < )8/88  < <-) < )8/88)1""1) < )888  <  <) < )888< << <  ) < )88')8" 1""12'8 < <- -< 28/ < )< <- 288)"118 ) )8'8-<)< <<) )88<)<<<<) )88)""1)1 ) )88<<<< ) )88 <-< < <<) )88)")))8 )888< <)<-<8 )888<-<<<-8 )888)""")8 )88< <-<<8 )88< <<-<8 )88)"1 1)1)8/ /8 -< )< < / /8<-)<)< -<- < / //)1)1) 8 )8/ < -< <8 )8/ - < <<<8 )88))"18 )88< <<)8 )88 <<)8 )881"1 ))888) < <-<)<<8))888) ) < <8))88)))1) < )8) <)< -) < )8)8))81" < )8 )) )8))8 )  )88))8 )81"5"8))8) ) < )88) ) < )88) ) < )88 )""1))8 )""1))8 )""%1))8)14181" )8)1"4%"1814" )8)1"4%"1814" )88)11))))188)11))))"88)11))))"888888)888888)888888)8)8)8)8/ ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )8'2<;< '2'82<  < 282<< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )8)  <-< )8) )8 <-< )88) )8 <-< )88) )8 <-< )88))8"1< )88))8< )88))88< )8))8"1""1<-< )8))8"<-< )8))811<-< )8)""1"1) < )8)""11) < )8)1) < )88""1  )88"11  )88  )8/15") <) /8/1111) <) /8/111") <) /88"") < )8/88"11) < )8/88"1"5) < )8/881""11) < )88811) < )888""5"1) < )88'"112'8'2'8'"12'8811) )8'81") )8'8) )8'811) )881"5) )88) )8818 )888"5"18 )88818 )8888 )88"18 )8811"18 )881"/ /8/ /811""1/ //1"58 )8/8 )8/1""18 )88"5"18 )8818 )88"1""18 )88"1))88811"1))888""1"1))88) < )8)11""1 < )8)""1 < )8 8))8 1""8))8 "5"8))8) ) < )88) ) < )88) ) < )88 )""41))8 )8))8 ))!8))8)14181" )8)8!8! )8)80!80 )88)11))))188)))))88)))))888888)888888)888888)8)8)8)8/ ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )8'2< ;< '2'8'2<;< '2'8'2<;< '2'88)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88)  <-< )8) )8 <-< )88))8< )8))815<-< )8)15"1) < )88"1  )8/11) <) /88") < )8/8811) < )88'@11"12'88@11""1) )8'8@1""1) )88@"1""18 )888@""1"18 )88@""1/ //"5"8 )88@""8 )88@""11))88)@"11 < )8 @118))8) ) < )88 ))08))8)8?08? )88)))))888888)8)8/ ) 3 /8)88) < <-< )8'2<(< '2'88)) ) <-<)88)88) )8deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/powerup_swap_characters_lori.aura000066400000000000000000000205301512772601700326630ustar00rootroot00000000000000☄️   MMMUUU)88) )88) )88)  UU< )8)  <-< ),,,8)  <-< ) @8) )8 <-< )8+8) )8 <-< )8+8) )8 <-< )8 8))8< )8+++8))8< )8:8))8< )8)))))8<-< )8 ×))8*<-< )8:))8*<-< )8+)* ) < )8)* ) < )8 )* ) < )8:8* *%%%UU )88* *  )8 8* *  )8:&&&1@1* *@ ) <) /8 /11* *9 ) <) /8/11* *9 ) <) /8 81U1 ) < )8/:"181"1 ) < )8/881"1 ) < )8/ 881*) < )8 1"181?*) < )8 881?*) < )88??UU8?!?28:?!?288??) )8+8?0?7) )8+8?0?7) )88?!77) )8:8?!7 7) )8 8?!7 7) )88???7 78 )8 88???7 78 )8188???7 78 )8 18800!?7 78 )8800!?7 78 )8+800!?7 78 )8:8??7 @7/ /:18??7 ,;7/ /:8??7 ,;7/ / 1/?!?, 78 )8 /?!?(, 78 )8 /?!?(, 78 )88?!?7(, 78 )88?!?7(, 78 )88?!?7(, 78 )8+8?!,,7))8+88?!,,7))8:188?!,,7))8:8)?7(7 < )8:)?7(7 < )8 )?7(7 < )8  8))8  8))8 8))8:) ) < )8+1) ) < )8?+8) ) < )8 "1 ))?8))8  ))!8))8: )8))8?1)8?8 )8 )80!80 )8 :)88 )8+8)))))8:8)))))8 18)))))8 :88888)8+88888)8:88888)8 1)8: )8 :)8 :/ ) U /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<; < 282<;< 282< (;< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< ):8)  <-< ):8)  <-< )8) )8 <-< )8+8) )8 <-< )8 8) )8 <-< )8:8))8?< )8 8))8< )8+8))8< )8 1))8*<-< )8))8*<-< )8 1))8*<-< )8)* ) < )8)* ) < )81)* *9 ) < )8+8* *  )8+8* *  )8+180   )8 /11* *9 ) <) /8:/11* *9 ) <) /8:/"0*) <) /881"1 ) < )8/:881"1 ) < )8/ 8?!?) < )8/+181?*) < )8 881?*) < )888?0?7) < )8+18?!?28 ?!?28?!7 728:8?0?7) )88?0?7) )88?1??7 7) )8 8?!7 7) )88?!7 7) )8?80!?7 7) )8 8???7 78 )888???7 78 )8?88?7 ,;78 )88800!?7 78 )8?800!?7 78 )8?8?!?(, 78 )88??7 ,;7/ /?8??7 ,;7/ /?8?!?7(, 7/ //?!?(, 78 )8/?!?(, 78 )8/?!,,78 )88?!?7(, 78 )8:18?!?7(, 78 )88?7(78 )8?8?!,,7))8 "188?!,,7))8 88))8?8)?7(7 < )8)?7(7 < )8+)1" < )8 8))8+ 8))8  1"UU"8))8 ) ) < )8:8) ) < )818) ) < )8:"18 )""41))8: )""1))8+ )""%1))8 )14181" )8 1)1"4%"1814" )8: 1)1"4%"1814" )8 18)11))))18+ 8)11))))"8 8)11))))"8+:88888)8 :88888)8:88888)8? :)8: 1)8 :)8 / ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<;< 282<  < 282<,< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )8)  <-< )8) )8 <-< )8 18) )8 <-< )8+18) )8 <-< )8 8))8< )8+8))8< )8 "18))8< )8:1))8<-< )8 1))8<-< )81))8,,7<-< )8 ")7) < )8)7 ,;7) < )8 1)?7(7) < )8"8?!7 7  )8+81?(, 7  )818  )8+1/?1?7 7) <) /8+1/""?7(, 7) <) /8+/111") <) /8:181"0!?7 7) < )8/ "181?!,,7) < )8/:88"1"5) < )8/ "181?7 ,;7) < )8181?7(7) < )8:88""5"1) < )81?1?(, 728+28 "128+8?!?7(, 7) )8 181") )8 8*) )8:8?!,,7) )8181"5) )88* ) )8 8?7(78 )8+88"5"18 )8 "18* *8 )8 888 )8:8"18 )8 18* *9 8 )881"/ / 8*/ /80 / //1"58 )8/* 8 )8+/0?*8 )8:8"5"18 )88* *8 )8:8?!?8 )8 18"1))8?88* *9 ))8 88?0?7))8"18)* < )8?)0  < )8)?!7  < )8+ * 8))8? 0?8))8 ??7 8))8:) ) < )8?8) ) < )8?8) ) < )8 8 )""41))8 )8))8? ))!8))8)14181" )8:1)8!8! )8)80!80 )8?8)11))))18 8)))))8 :8)))))8?88888)8+ 88888)8 188888)8 1)8:)8:)8 / ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82< ;< 282<;< 282<7;< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88)  <-< )8) )8 <-< )8:8))8< )8 ))815<-< )81)15"1) < )8 "8"1  )8 "/11*) <) /8"8"* ) < )8/+181* *) < )8:1@* *9 28 810 ) )8 180?*) )8 "8?!?8 )8188?0?78 )8+8?!7 7/ /:/???7 78 )8 800!?7 78 )88??7 ,;7))8?8)?!?(,; < )8  ?!?7(, 78))8 1) ) < )8"1 ))08))8?+)8?08? )8:8)))))8:"188888)8? 1)8?/ ) 3 /8)88) < <-< )82<(< 288)) ) <-<)88)88) )8deathkiller-jazz2-native-2a7ccef/Content/Animations/Object/powerup_upgrade_blaster_lori.aura000066400000000000000000000204131512772601700326550ustar00rootroot00000000000000☄️   MMMUUU)88) )88) )88)  UU< )8)  <-< )8)  <-< )8) )8 <-< )8DDD8) )8 <-< )8FFF8) )8 <-< )8%8))8™< )8EEE8))8< )8%18))8< )8))8<-< )8%1))8<-< )8))8<-< )8%1)) < )84)) < )8)) < )8418&&&%%%UU )88/  )8%8/  )81/U/) <) /81/ /) <) /84/ /) <) /88@@ / ) < )8/%"18"; / ) < )8/18"; / ) < )8/!81;/ ;/) < )84181;/ ;/) < )81881;/ ;/) < )88 ;/UU8 ;/28 ;/2808  /) )8%18  (/) )8!8  (/) )808/  /) )8418/  /) )8!8/  /) )88/ /8 )888/ /8 )8088/ / /8 )8?88/ /8 )88/ /8 )808/  /8 )8?8/ // /!8/ // /08/  // /?/// /8 )80// /8 )80// /8 )808/8 )808/ /8 )8?8/ /8 )808))8?88/))8?88/))8!8)8ŀ < )8?)8 < )80)8> < )8 )8>8))8? )8>8))8! )8>8))8%) ) < )8?8) ) < )8"18) ) < )8418 )""%1))80 )""41))8% )8))80)1"4%"1814" )81)14181" )8!41)8!8! )8!18)11))))"8!%8)11))))1808)))))8%088888)80!88888)8?!088888)80)800)80!%0)80!0/ ) U /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<< 282< ;< 282<;< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )!8)  <-< )!8) )8 <-< )800EEE8) )8 <-< )8!%18) )8 <-< )80EEE8))8< )8%EEE8))8< )84EEE8))8< )8EEE))8<-< )84EEE))8<-< )8))8<-< )8)) < )84)) < )8)) < )8!8/  )88/  )88/  )8!/ /) <) /8/ ; /) <) /8!/ , /) <) /808"; / ) < )8/88"; /; ) < )8/!88"; /;,) < )8/0881;/ ;/) < )8!881;/ ,;) < )80881;/;,;) < )8!8 ;/28! ; /280  (288  (/) )808 (/) )808 (/) )88/  / /) )808// />) )808// /) )8%8  / /8 )8088  /8 )8!188  /8 )84188/  /8 )8?8/  /8 )88/  /8 )88/   // /?8/  // /%18/ // /// / /8 )80// / /8 )84// /8 )8!8//8 )818//8 )88//8 )808))8%"188>))888>))818)8> < )84)8> < )8!)8> < )8% )8>8))8 )8>8))8 )8>8))84) ) < )88) ) < )8%"1) ) < )8?8 ))!8))80 ))08))8?41 ))?8))80)80!80 )8!%1)8?08? )80)8?8 )8!8)))))80!8)))))8!8)))))8%!188888)8!88888)8!%!188888)84)80%1)80!0)80!4EEE/ ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<;< 282<(< 282<; < 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88) )88) )88)  <-< )8)  <-< )8)  <-< )8) )8 <-< )8EEE8) )8 <-< )88) )8 <-< )8!EEE8))8< )88))8< )8!EEE8))8< )81))8<-< )8!×))8<-< )801))8<-< )8%")) < )80) ) < )8!")) < )84"8/   )808/  ;,  )8%"8/   )81/ ; /; ) <) /8!/ ; ;, ) <) /84"/ ; / ;) <) /818"; /;,;) < )8/"18"; /  /) < )8/18"; / ;) < )8/%"181;/ ;,;) < )8%1"181;/  /) < )8181;/ ;/) < )841  /284 /28!  />288 (/) )88  (/) )8!18 (/) )88/ />) )88/  />) )8%"8/  />) )8!8  /8 )8!88/ /8 )84188/  /8 )8!88/ /8 )808/ /8 )88/ /8 )808/ / // /18/ // /8/ // /0/// /8 )8%/// /8 )8!/// /8 )88/8 )848/8 )808/8 )8%18))888>))8?88>))84"18)8 < )8)8> < )8!)8> < )8 )88))8! )8>8))8%1 )8>8))8) ) < )81) ) < )84"1) ) < )8!8 ))!8))80% )8))8? )""41))80)80!80 )8!4)88 )80)14181" )8?8)))))848)))))8"18)11))))18?88888)80088888)8?!188888)8!01)8!)80?)8!%!EEE/ ) 3 /8)8/ ) 3 /8)8/ ) 3 /8)88) < <-< )88) < <-< )88) < <-< )82<;< 282< (;< 282<;< 288)) ) <-<)88)) ) <-<)88)) ) <-<)88)88) )88)88) )88)88) )88) )88)  <-< )8) )8 <-< )818))8< )8%"1))8<-< )841)) < )8%18/U  )841/@ / ) <) /88@"; /;) < )8/881;/ ;/) < )88U  />28!8@ (/) )8!8/  />) )80EEE8/  /8 )8!"18/ /8 )8%18/ // /4EEE/// /8 )8EEE8/8 )88>))8!8)8> < )80 )8>8))80) ) < )8?8 )""%1))8?)1"4%"1814" )808)11))))"8!88888)80%1)800EEE/ ) 3 /8)88) < <-< )82<  < 288)) ) <-<)88)88) )8deathkiller-jazz2-native-2a7ccef/Content/Animations/Pickup/000077500000000000000000000000001512772601700240575ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Pickup/ammo_thunderbolt.aura000066400000000000000000000051051512772601700302750ustar00rootroot00000000000000☄️  KKK))))@)LLL)<))<))<))<)MMM$$$)< )#)< )#)< )#NNN) )HHH) )8-) )8) )8))8)8))8)8-))8)8))8)8 @?U)8 ?!?:+)8- ?!?:+)8 ?!?:+)8 +!0!?:8 + 0!0!?:8- + 0!0!?:8-- + 0!0!?:8 + ?0?0: 8 + ?0?0': 8- + ?0?0': 8--+ ?0?0': 8 +' 8 +' 8-+' 8--' 8 )))+': ' 8 +': ' 8- +': ' 8- ': ' 8 + :' : 8 +6 :' : 8 +6 :' : 8- +6 :' : 8 '  8 '  8 '  8 '  8- +' '  8 +' '  8 +' '  8 +' '  8- +:' ' 8 +:' ' 8 +:' ' 8 +:' ' - '' '8 '' '8 '' '8 '' - :'8 :'8 :'8 :'8NNN) ' )2) ' )2) ' )2) ' ) ) ) ) ) ) ) ) )OOO)))))<))<))<))<))< #)< )#)< )#)< )#) )8-8) )8) )8) )8))8-8))8-))8)8))8)8 ?!?:+)8 ?!?:+)- ?!?:+)8 ?!?:+)8 + 0!0!?:8 + 0!0!?:8- + 0!0!?:8- + 0!0!?:8 + ?0?0': 8 + ?0?0': 8- + ?0?0': 8-- + ?0?0': 8 -' 8 +' 8 +' 8-+' 8 ': ' 8 +': ' 8 - ' 8-: ' 8 +6 :' : 8 +6 :' : 8 +:' : 8  :' : 8 '  8 '  8 '  8 '  8 +' '  8 +' '  8 +' '  8 +' '  8 +:' ' 8- +:' ' 8 +:' ' 8 +:' ' 8 '' -8- '' '8- '' '8 '' -8 :'8- :'8- :'8 :'82) ' )-2) ' )2) ' )2) ' ) ) ) ) ) ) ) ) ))))<))<))< )#)< )#) )8) )8-))8)8-))8)8- ?!?:+)8- ?!?:+)8- + 0!0!?:8- + 0!0!?:8- + ?0?0': 8- + ?0?0': 8-+' 8 +' 8+': ' 8 +': ' 8 +6 :' : 8 +6 :' : 8 '  8 '  8 +' '  8 +' '  8 +:' ' 8 +:' ' 8 '' -8 '' '8 :'-8 :'-82) '- )2) '- ) ) ) ) )deathkiller-jazz2-native-2a7ccef/Content/Animations/Pickup/ammo_toaster_disabled.aura000066400000000000000000000066601512772601700312620ustar00rootroot00000000000000☄️  ί!!BBB!CCC"!"@"!""!!!""!"EEE""!EEE""!DDD!"1!EEE1"!"1!"1!EEE11!EEE1"1!EEE1!EEE1"1!EEE11v!EEE11 ',!EEE1" ',!EEE1 ,!"1 6,!EEE1 6,!"EEE1  !1 '79 $!"1 '79 $!EEE '5~  !"EEE '5, .!EEE '5, .!""MMM  ...g !8   !8   ! ... =!= ;& 5=!= ;& 5=! p] !' 7]1 !' 7]1 !.: = .>, .>, H I!!?9 #!!?9 #!!?7NW!?=!?=!..!.=.!.=.!!!!"U"!"!!EEE""!EEE!!EEEU""!EEE1"!EEE1!EEE11!EEE11"!EEE11!EEE1"1!EEE1"1"!EEE1""!EEE11 v!EEE11 ',!EEE11" ',!EEE1 6,!EEE1 6,!EEE1 6,!EEE1  $!EEE1 79 $!EEE1 79 $!EEE 5~ .!EEE 1, .!EEE 5, .!8  ...g !8   !8  EEE != ;& 5=!= ;666 5=!= ;& 5=! ] !' 7]1 !' 7EEE !.> = .>, .>, H #!!?9 #!!?9 #!!?NW!?=!?=!..!.=.!.=.!‘!!U!!"!!""!""!""!!"!"!"U!EEE1!EEE1!EEE11!EEE11!EEE11!EEE1"1!EEE1"1!EEE1"1!EEE1 v!EEE11 ',!EEE11 ',!EEE1 6,!EEE1 6,!EEE1 6,!EEE1  $!EEE"" 79 $! '79 $!EEE 1 .!1 '5EEE .!" '" .!8  " !8  " !""8  1 != ;& 5=!= ;& 5=!= ;& 5=! ] !' 7? !' 7? !.> = .>, .>, H #!!?9 #!!?9 #!!?NW!?=!?=!..!.=.!.=.!!!!!!!!!!""!!!EEEU!!!EEE11!!!EEE1"1!!!EEE11 v!!EEE1 6,!!EEE1  $!!"U" '~ .!!"8  ...g !!""= ;& 5=!! ] !!.> =  #!!?NW!..!deathkiller-jazz2-native-2a7ccef/Content/Animations/Pickup/fast_fire_lori.aura000066400000000000000000000036651512772601700277320ustar00rootroot00000000000000☄️   U ( ; (/>( ; (/>(  ; (/>( ;U; ( ; ;,; ( ; ;,; ( ; ;,; (/ ;  ;(/ U;, > ;(/ ;, 7> ;(/ ;, 7> ;(/ ;, 7>(/ (;, />(/ (;, />(/ (;, />(/ (;, />>/  ;7/>>/  ;7/>>/  ;7/>>/  ;7/>>/  ( (7/>>/  ( (7/>>/  ( (7/>>/  ( (7/>>/  (7/>>/  (7/>>/  (7/>>/  (7/>>/  />>/  />>/  />>/  />>/ />>/ />>/ />>/ />>/ />>/ />>/>>/ />/>/>/>>/>>/>>/>>/>>/>>/>/ />>/>/ />>/>/ />>/>/ />>/ />>/ />>/>/ />>/>/ />>/>>/ />>/>>>/>>>/>>/>>>> (> ;((((> ;, ((/ ;, (; ; (/>( ( ; ; (/( ;; (/(;,;(; (/ (;,;(, ;(/ ( ; ( , ;(( ( ;,; ,; ( ;,; ,; ( ;,; >( ;(/ (;, (>( ;(/  ;,; ( ;(/ ;, (> ;(/ ;, (/>>/ ;, 7>>/ (;,; (7> (/ (;, /> (/ (;, />>/ ( ; (7/>>/  ,; (7>>/  ;(/>>/  ;(/>>/ ( (/>>/  ; (/ />>/  ( (/>>/  ( (/>>/  (/>>/  ( / />>/  (/>>/  (/>>/  />>/  />>  />>/  />>/ />>/  />>/ />>/ />>/ />>/ />>/ />>/ />>/>>/>>/>/>>/>>/>>/>>/>>/>/ />>/>/ />>/>/ />>/>/ />>/ />>/ />>/ />>/ />>>/>>>/>>/>>/>>>>>(>((/ ;  ((; (/ (;,; ; (/>( ;,; ( ;,; >;,; ( ; 7 ;(/ ;, (/> ;(/ ;, 7>(/ (;, />(/ (;, />>/  ;(/>>/  ;7/>>/  ( (/>>/  ( (7/>>/  (/>>/  (7/>>/  />>/  />>/ />>/ />>/ />>/ />/>/>>/>>/>>/>/ />>/>/ />>/ />>/ />>/>>/>>>deathkiller-jazz2-native-2a7ccef/Content/Animations/Spaz/000077500000000000000000000000001512772601700235415ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Spaz/jump_shoot_end.aura000066400000000000000000000122521512772601700274320ustar00rootroot00000000000000☄️ .500@00U00!00007000!07@UU7770!0077;77 @700!!07;7; 7( ;, 7770!U0 ,77 77 ; 77 7(7;70!!0 ;(7 (77(; 777,70!00( ;(77;77 ,, 77(77 ,;;700!!0(  77;77;  , 77( 7; ,70!0(;;77 ;77(;, 7;;,,( , 70!!0!0;;7 ,;77( ,; 7;(;( 70!0? 7 ,,77;,;, ( 7?0!0?7,,,;,777 ;,;#@#(;7?0?7 ,7;#,#, 77, ##7?7, (,2,;#7...UU+: 2#2( '???'U ;22;,#'': :  2+ '?0!?0!?!?':+ 2,+:''::+5  :?0!00?':+ 2+@ 5' '':+:5  + +'?!?!0':@5" + :'': :2#21:+ '????0!0!0!WWW' + :5" + + ''#211': '7(??0?0':2" :+ + '#2. '7((7; (2 :7 + 5(?' .+'7( ;«U ,, 72.:7 ;7 "1/?!!0!?6 ::6 67,;7 / ,, 7, 7'.. ';;7( /?@U40!?6 6 6.( (7/  , 7 ,7'+ . ';,7 / !4%4?667 : '7>;7 7''77;3/7!!%!%?7;:+' 7( : 3,00404!(, ';7 : :'>/,?!??!7 :'7,+ :'>;, (0!(;+'(:';,;7 +'7 ;+; 7 7 + '7 : '7, 7 '(7;(:'  7( 7(,;7+ '(,7;7 ( '7/,;7 (/7 7;7/3/; >/ /;,7;/> (;>/ 3 , ;/ >  /> /   ///>>//3> //3>>3//$ >/ /3$>>3/ 3$/>//3$3>>  >/$3>>3 >3$>> > $>> 3/>3 >/3>> /> >/>> >>> >>>77777 7777 7777, 7(; 77 77 77,( ,7;(; 7; 7; 7;7,7(;( (7; 7  77 77;7;7 ;77;7;7 77,7; 77;777(7;7(,7777; 7;7 7 (7 , 7;,7 77 7(7 ; ;, 7;((;(7(77 ,#2;7 , 7 7 7;7  #,2,( ,; 7;77 ;,2 2'7(, ;; 7;77 ;#2,;&@ '7 7 ;,5,"; 77,2&;,  : '' ;;5;  '' : ;5++: ''' 5 ,;& '' :+ "5+ 5':''+': + 5&,;, :'':: +:  : + 6 + '' :':+ :5:++ +'' ++:"51:+: ''  +"5 5': ''  2  5'2:+" 6: '7':#.1 + "#2. ":+:'/7 ;7': .. 7 : '/7.. +: ' , ( ;7''.'7; (7 / /7 (?.' +7+:15",(7 ;7',,(7 / ?!40?7 ('"  7 ,7 3$/7?4%40?7;,;: (>7 , 7>/;!4%00?;3/ +:'7( 7 ;:'> ,(!0%0!$3/ +'7, 77, :'(,,(!0404> $ ? ;(:' ;(?0> ;?!0?7 :+ ' ;0?0> ?!40?7 +';?40?7;( :'7 040!?7,7+ '77(004?!? ('( 77;(040?/( 7( 7(;(0?04!/ /,;7/7( /7;( '0?4!/ 77/3 /7; '!!/3/7 ;3> ;/ (/$3 > >/3/3> $3>> >/ >/3>/$>>//3>/ //$3 3 >/3$3>>3 //3$>>3> 3$3>> 3/>$3>> 3//$>>3/> >> >/3$3/>3/>>>3$>>3/>3>> >> >> >>777 77 777 77; 77777,(7,;(7 77( 7; 7;, 77,(7;7,7 ;77 7 ,(77(;77;7,7,( 7,77, 7 ;7,,7,;77;77 ( (;,777;(( 77 ,77;7(,7 7;7 ,7 ;7 77,77,7(;7(,(;, ;7,7 7(( 7 77( ;,,7, (77 7,777 ;;, 7 ;,7( 7'7;,;77(,";,;;7;7':7 ,5;,";7'7(;";;,(7' 7 ,5;"''+'(, "5",;;;'7';,;5&&: '7'  ;5;,;:'77(;, 72+55:'7(; 7 ,5 " :+'7 ;77;.2+5:++:7 ; 72;5& ,&1 '7 ;(;;.2 5 5' '/7 ( (7('.2 5 ; " '; ,;(77' .:+:: 6: '7  , (; 7' ."5:+:::'/  ;  ;, 7' /7' +: ' 3;;, 7?7/ ": 5+:'3>;( 7/;,;7' : ',; /> ;?0!!? $37 : + +6 '  />/$;; '5 7>?!%4, >  ;(7 +:': ' >/$37 ( "?!04!;(> 7'' / >( : ?040?(,7'"57 , 7+ +'?0?! +:1? , 77::'!%0!:+'?!? ,7:'!?7+  '?!?  '(: '?4?(+ :'7 +:'?!0!?7 :+'7,+?407; :'777+ '?!?0!07(;; ' 77 ;7;: :'00!07   :' , 7/7 ;  '!!  (',;7/ //  ;  ' 7  /( /3$3//3>' ;7/ />/ > $/ / ;7/ 3> 3> 3/>(;,7/ 3$>/3>>/3/;/3$>/ >>>/;/3$3>/ />/ ,>  />>3 ///>>3//$// />/3// / 3$/>  >> 3$ >>>  />>deathkiller-jazz2-native-2a7ccef/Content/Animations/Spaz/ledge_climb.aura000066400000000000000000000171361512772601700266510ustar00rootroot00000000000000☄️ 1: *???@!?0!4?07?!40!?77;???0!4?7@;7&&&7,?4?0!??0?7;7/U7 ?4!?!4?7 7;(;7/ @ ?!0?0!0?7( 7; 7//?0!?(!4?7(7,7(;7//?(;(?0!0!?,(7,;77;7( (  0?77,;7((;/3>7( , .04?(;7;(7/ 3>7,;7 (;,;#.?!4?(,7;, ;7(7 ; / >7, ;,BBB;77 (;,# ,;@@@0?(,7,  ,;;777;,5( ;7(, ,;;(/7 7 ;# ,???(?/7;;5;;57(77, 77 ,;5&( ;7'( ; ,,5,@U ?;7;7 (; # +:(/ 3/(;;"5;;<<<5777, 77 ;,;5(;7:'( ((,5,:5 + ??77(;(;# +U+:(???>3/'7;, 5;;&5;77 7 7(   (,;5(;7 ',(( ,59+ ?0!4?;:5 (?!???>/':';5;;5777 7 7( ,(,((;7'"( ((,59&:?0?40((;'5&+ :(0?040404?>' : +5&;57 ;  ;, 7(;(;  (:(,;7"(;(( ;+ !0!4?(;(':+5 :( (0(?4!4!40?> ':':5+7  (7,( ((+ ;7(;(( 5: 0!4!(,(': 5+ 5 ; FFF/?4!!??/7 ': :5 :+ +/' ( (('  , (;((, +" :?00(,(  ( 5/ 0!00!7 :+:5+51::'/ >(,;(; (:(;( (; ( 7;:?4!0?(;'+ :(,;((3/?0?!?!7  ::::+ / >(;,( ;;'+(;((;(( +:7;(?4!?(, ':': ( ;(; >(7 ;;( :+:, >(, (; +( ,7(( (;(:+7;(: /?!!0(,': 7;;>7 ;;7(:;::', 7((  +:'(;7 '( ( (:' 7 /?0!?(+'7 7;77 ;;:5';, 7(((:' 7:'(;(( ('7, /3( ( 7  : 77 ;(+: 5 ; 7(((;' '; 7+'( ( (7  3 >( '7 ::'7 :+7;(;('7 ; 7+'((;(7;/ >((;, +7+: ' (;((,;: '( (77>', ' '7 :+'((7, '(7: '7 ;:'7;: 7; '(  '(;'7,::'( :'; : ', : '7,+'7 , +'7 +:'7;(:'(;+'7; +'7;6:'7; ;,((; +'77 ,7 ,77 7;((;,; :+'(77 ,7;77; 7  (;; (+ ';(7 7( ,(7;77 7,((;  :'7;(7( ,>;(7;77 77 (;(';(7 > ;>/ />7;77,7>>(;(7 (7> >> >/;(;>; 7>/> >7> (7>>>> >7>; (> />/ >> >> >7 />>  >/>>; 7> />/ >> >> >>>> />> /> >>7>/ /> />>/ />>/ >>  >>3 >>  />//>> >/ /> />/ >>>  >> / >//>>>3 > />/3/>  >/>> / > 3 >>/3/>3>>/>/3 >/ >> >> / >/3>> 3 >>3>> />/3 >/ >/>> / >>  >>/ >>3>>/3 >>>>3 >>> / >>  >> >> 3>>/3 >> 3 >>//>> />> >/  >>/ //3>>/>  />> >>/ >> />/3 >>> />> >>/>>/ />> >>>>>/>>>((;((,((((( (7((, (7,((;( ; 7;((,( ( (( 7((,( , (( &7 ((;7(( ( ;(;, ( (7,(  7 ;7(;(;( ( ;((;,(((;(7,((;(7;7,7 ;(;;(7( ( ( ;((( ;(( ;((;(7,( (7;7; 7 ;(7,;,(7 (((( ,;,;;(7,, (7(;((, (;;  ;(;7, ; ,(7 ;(( ;,; ( 7, 7;,;7(,((; (,;, ,2;(,7 ; ,7(;((;,;#,;,,;('7 ; ;((77(,;#2, 2 ;( ,;#;2>( ,,;#,,;(':+: 7 ;,, ,;((7 ; #2, # ;(, ; , #;#>  >';##,,(;(' 7 ,;;;, 7 (; 2# ;/; 7 ;2# ;#>  >' :+: 2#;;(;(' :7 , :+, ,;7 , 7;7;2+;( /  :( ##;;#> @ >' :+:##;,(' 7;:+ ;, ,;7;('##5+;/ /? : #2,, 2/$ >' :+ +2#2 ;(7,;:+;;7 7(,( :+:#+'> /?!0?' +:+#,; > $3 >':' :+:# 7; ;(:+:: 777':': :+++>>?0?!?': : +# : >3>': ( :2:;(7 ;(;(:: #, 7(]]]':: :+:;: 4!00!?:: +:+:/$ >: 7;, ::;5: ((; (; (: #27,77;7:+:( :5>0!!!40?+( :+15" 3 >/7 , ( :+1+:((; ,, ( #27,7,(+:: ;(5>  ?00!0??':' , (:$/> /7 7 ,(: :++((; ,7,: (;(.(:' +( ;((3 />(0?0?00?';; ; (>3>> ?7; 7,(: ;+(,;(:76;,;(: ( (7'+( ;(  />07 ; ((;> ?!?7; :7 ;(5"??(;,:(;(7' '7  (>(( ;(> />0??7  + '7; (0!?!?+(; (/'(7 (77 ,( ; >  />!4040?7;( 7 (;>0?0??( :  6;(> /; 77 , (> />0!!!40?7 (;,;(7 > >!4040?7;   6 ;(> / ,: '(,(;7 >?00!??7 (   (7> 3 >0!40?7 , 6,( (>/ +: '7, 7; 70?!0?7; ;;/> 3$3 >!?? 6, > >  >,::'(7, 7; 7!7;(/>> $3 />0?6( ;(/ >  >7 + (((,7; 77;  />>$3 />!6;(; 7> 3 > 3>:(, ;(7 ;7; 7 ;( '> >>3 />6;7>/ 3 />7;(; 7(;(>7;7; 7'7( :'> > />6;>  3 />7 7>;(>/  >7(; 7 ';,7(7>  />6;> 3>7(> >> 3$ >7  7 :' ;7 ,7>/ >>;>   />( > >> $3 >7 7 :'7 7( ,7>>>>/ />>(>>> $3 >7'7,7( ,7>/  >>>/>> >  >>;7>/ 7> >> >>  />> >>  >>/ >> />>  />> >> />> /> >>> >>/>> >/> >> >>3>> >>>> 3/> 3>>3>//>/ >>/ 3>>3>> 3 >> >>/ />>  >>3>>3>/ / >>/>>3 >/3>> / >> >> 3>> 3>> / >> />>/ >>  >>/ >>>  >>/ >>>>(((,(( (,;((;7( 7( ;( 7( ; (;7(;   7(; (;7((;7(;(;7(;(7 ((  ,;(7 ((;(;, (77 ((;,;((7;7(77 (;,;, ;; (7; (; 7 ;,;77 ;;2, ;(77 (; 7 ;7(;(,2# 77; (; 7;,7';#,;2;(7;; (; 77':++:##;,#7;,2 ,;(;;(' : + #;,;#77;,2;;(;7' :++ '#7;7';#2 , #2, 7(::#:+:'7;7;' ##;, #2 7;7(; (: ++"5":'77';##'2#7(; ;:++ +'7';'##+:'27(; ( :: +:+:'7' :#:"5":':(;;(: : (:  # :+'7;':+::1 '+ 7(;( ::5"(:':'7;7 + +:+: ' :'(; +:5?0'77+: :;7 +:' /: '(?':+ : :5':+ ' /77(: ; ';(?!0':+" :'/ 7; 7(6 ( ((?( ;(?!!' +:+''+ '7(; 7( (6 :  ;?!>>>?( ;?0!!06' : '' '7,;(7 , 777 ( ++ ;?0?? ?!?!0?6'+ '',7,77 7 ( :64!4040??0?!0?;' '(7;77 7 (___ 0!!!40??!!0:+ '7 7;77 76(, ?00!???!0?6;: 6; (7>7  > ;60?!0?0?,6 67> /7  >! 67>  />  > /7/>> /> >>> > />>/>  /> >> > /> />>>  />>>/ /> //>/  >>3 >>/ / />  />> 33 >>/  />>/  />>  >>/ />deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/000077500000000000000000000000001512772601700231415ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/blaster_upgraded_lori.aura000066400000000000000000000042161512772601700303520ustar00rootroot00000000000000☄️   AAA@@@U ; >;U5 ; ;> 5 ; >( ;,; (  ; ;,; (U ;,;,; ( ; ;,; ( ;  ;( ;, / ;(/  ; ;/ ;(/  ; ; / ;(/  ; ;(/(/ (;  /(/ ; >(/ ; / (/ ; />/  ;/>>/  ; ( />>/  ; ( />>/  ; ( />>/  ( (7/>>/  ( (7/>>/  ( (7/>>/  ( (7/>>/  (7/>>/  (7/>>/  (7/>>/  (7/>/  />>/  />>/  />>/  />>/ />>/  / />>/  / />>/  />>/ />>/  / />>/  / />>/ />>/>>/  />>/  />>/  />>/>>/ / />>/  />>/  />>/>/ />>/>/ />>/ / />>/ />>/ />>/ />>/>/ />>/ />>/>>/>>/>>/>>>>>BBB ; >( ; >( ; >( ; >( ;,; ( ; ;,; ( ; ;,; (  ; ;, (  ;  ;(/  ; ;(/ ; /  ; ;(/ ;, /  ; ;(/ ; /  ; ;(/ (/ ; / ;/ ; / /  ; /(/ ; />/  ; ( />>/  ; ( />>/  ; (/>>/  ; (/>>/  ( (7/>>/  ( (7/>>/  ( (7/>>/  ;(7/>>/  (7/>>/  (7/>>/ (/>>/   />/  />>/  />>/   / />/   />/  />>/  />>/ /  />>/ /   >>/ /  />>/ /  >>/ / />>/ / / >>/  >>/ />/  >>/ >/>/  />>/ />>/ />>/>>/  />>/ />>/ />>/ />>/ />>/ />>/ />>/ />>/>>/>>/>>/>>>>> ; >( ; >( ;,; >( ;, >( ;, (  ;  ;, (  ; ;  ; ;  ;  ; /  ;(/ ;  ;,;(/ ; ;,;(/ ; / ;,;( (/ ;,; /(/ ;,; /(/  ;,; /(/ ( ; />/  ; ( />>/  ; />>/  ; >>/  ;7>>/  ; / >>/ ;  >>/ (  >>/ ( (7/ >>/    />>/    >>/  (/ />>/  (7/ />/  / />>/  /  />/  / />/  />>/ /  />>/ / />>/ />>/ />>/ / />>/ />>/ />>/ />>/>>/>>/>>/>>/>>/>>/>>/>>/ />>/ />>/ />>/ />>/ />>/ />>/ />>/ />>/>>/>>/>>/>>>>>deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/character_art_difficulty_lori.aura000066400000000000000000001134731512772601700320750ustar00rootroot00000000000000☄️ [/#o;1'1 1''1 S;s/212>#>2 1'1 2>212>*/;G*>o;1'' 2*>212>/>o;1'12>*21 >/ * *>+''o;2>*/ *>2o;_G >/o;''1 2>*/*>2o;1'1G2*/2'1'1o;2>*/ *>2o;1''1 2>*/>'1'1o;2>*/ *>2o;1'1+ 2*/>12>*  *>2o;1''oo+ >*/>o;2>*/*>2o;1''1&>*>'1'1 S;>2 1''+2*2'11&2 1+2*1&>*>''+>* ''1'+> ''2+22'1 2>*>21'>211+>?'1 2>*/ >2*2'+21'12>*  *>2o;''> '12>*/ *>'1'1221o;2>/*>o;?'1 *>'12**> 1> '12>*>12*>11 >21 >/  2 '1o;>*>1 >*/*'1o;>/>*211'1o;>/*>22  *  o;'1>/ >o;''1 2 1 >*>2 >*/ *  * *'1 212>o;1>/*o;11*/*> *  o;*'/ >11>>o;1 */*>o;1'2*/*>* *>  '*' o;'>*> 1>*/*>o;''>*/*>2 > '1*>* >12> >o;1>/ *o;1'2*/ *2 *>o;>/1'2* o;' >*/o;1>/*>o;1' **> / *2?'1o;212>*/>''1>* >  *>2o;11>*/ *2o;/*>o;1 121{WKK712*/Go;'>/  *>2o;1''2/*o;>/*>21o;2121߳sw1>*;/>1'1o;>*/ *>o;1'' >*/ *>o;/*>21o;2>1KK7wKK71*G; '12>/*>o;1'12*/ *2o;*/2'2>*1661G 2''12> >2o;1' >*/*o;1*/*>o;?2>*/1Ӈ{WwKK7;G>o;'12*/*>2o;1''2>* 1*/*>o;1'2>*/ KK7ӇKK7;_+G1>;G >o;''1 >>2 1''2>*21* 1?'>*/ KK7çk:'KK7>G  11'1'o;2>1 >1 1{W'/*KK7KK7=&'=1> G*>*''o;2> 1>1'2KK73K''='1>G/>2''1o;2 '>KK7'/'1>  >1o;'1''>/*'1> * '''2'2 *>1w'1o;/G; >'2*1'2>16w?'1>  *o;'1>/*//#"'1'1{W61o;/G *>1o;'' /1KK7..KK7' *1o;>*G;/*>o;1 ''2/"_Ӈ"; *>o;1 '?'12>2"1kcGKK7?1>G/ *>o;12'GW'1'1 >"1'"/>1'o;*> 1'2'+ 2 11s/*/*o;1>' * ' '>' 2s/2 ''12*/>o;'1/*o;''+'+3>'2 2*s/2 1''3_='?'1'1*> 1'33' s/2 2>*> '/#''?s+s[='?1>/*>1''o;'3' >*/>o;2*/>o;''/#;+3K[O ''12> ' 2 ' 2'' >/* >o;2>*/>o;;+'?s:sO"'1 >2 1 2> ' 2>*/* >2>* >2''/#'[sO '12>2o;1 2>*> >2/  2>* >2''G ';'''.":"[?s'#>21o;>* 2>/*>2   2>* >2''3" '':" '1'+'1 *>1?3?+>2>* >2>   2> 2'1'": .';+'.[G 5=5''o;1'+o'+1 */*>21?[o+12>* *>2*  2* >o;'1 2 1' "'  [" ''>21'+? >>2 '++ *212*/ *>   >  *>1'12*21'G '[;/#'[":sO" '>*21'3o;2?3+**212/ *>*  >  *o;112/*o;';?s " ';+' [:sOG'1>/#1'+?> >?;;3  21> **    *o;12/* *2'. ":" ';+'G":['1> *1';?1  >?;*21> * *> *>o;'12/*> 21'="  '/#'. ''o;1'3?2/?; >1o;/*   >  *o;' >*>2>>1'=?s='' 2>/#'3?12/?+3;+> *o;1>*/*  *  >o;'1>*>212>1''>*/2'33?3;3+o;/*21'> *  **o;'112*>21#G12>11 >*/2'3;3++o;>/*21'> **  * >'o;2>2 11 >*21G #{K 21'>*/2'o;3GW1o;>*/>21'>* *  *  *o;'12*>21 2*>21?GG G'2''2**'o3;GW+1>*>1'>*/ *  21o;>/ *>o;1'2>*21{K oo##G'2'1 *2'o;GW3+1>*/>1'*  *   >1'12*>2>>o;' >*>21G {K ''2/*2';o3GWo;1 >*/>12  * *  >o;' />21o;>*21' >*2 {K#?G3 '12/ *>'+o;;33+o;21 2*/>12 * *  *o;'12*2 G > ''2>21++'1'' / *>o;GW;;+3+ 2 12*/>12  *  *21122G o;2'12>2 1'1 2 1'' / *>2o;'GW3;;++3+' 2 ' >*>'>/* *  >1'o;/>o;? G 2'1>2'1 2>>2 '1' / *>2o;'GW333;++3+ 2> ' 1 >/1'> * *  *112*2{K &?12112>2'o;2>*/ *>2o;11>/ *>2o;''GWo+33+;3+ 2 '2 1 **1'>/* *  *1'1> 2?& 1>1''o;2'2>*/ *>2o;'2/o;' 2'1+GW;+3;3+'+ 2> ' >  **1>*/* *o;'  #&& 1'1' '2>/ *>2''  >21'';;3GW2o;' 2*2'1 /*21> * *o;''2>G &&##1'1'2>**>2'1'12/>21';;3GWo;2 ' 2**' 1 /*212 * *2''2>G &&##?1'o;1'2>**> '1>21'/>21''+3;;3'o;'1 >*' 1>/*212 * *2112>{K#&&?31'21'o;>*/>o;122''' / >o;1' 2>2 +oGW'1 2*>1 '1>/>212 * *>112o;G##&&#{K112>12/*2'o;/'122''o;>*/ o;11>*>/*>o;+'12>/*1o;2 1 >>212*/*  *>1'2 2 #1'1>*12*  21'2 >''2>''o;>*/ 21'> 2> *>2o;' */*>'o;2>'1 *>1'>*/ *21'o;/23?&+1'o;*2>/ * *>1'> *2'12''o;2>/ >o;1' >/2 >2o;'2*/ *>1KK7'o;2>'1>/Go;5=';'>/ #2o;>*> ##&+3''2*2/ *>*  ''>2>2'1> >''1o;2>*/ 2o;>*/>o;/ >o;GW2/*21KK7w'o;>2'1>/;Go;5=.#Gg.)1's// #o;'12*2 &+1''2/>*2>*''>2o;12>2'''1 2>*/ o;12*/o;/ o;GW2>21KK7' >>21 >/;G=.)1'>*>o;'1S;*/2o;?GG& 1'1>/* >2o;'2>''>*>2 12>'12> '1'1 2>/ o;12>*>/*o;GWG?s/1'KK7' >*>21 >/;o;5=.:Ȱ:>)'s/2 '' *G2{K GG&+1'1>/*>2'''* *>21'1221'1'1o;2>*/o;1 */>o;'+o;'çk.'2 s/*>>21 *1';=Ĩ:.' 1' S;;/2o;?+GG+1''1G/#>o;'121'/*>12 >2''2>'G?GW3;'+G?wKK7'o;2s//>*>212>1';=.o[{*'112s/#G2{K GG+12''o;*;/*>2o;''2>*212*/*>2>2''1>o;'GW;;;o'+çk'2 2s//>*>21o;1';=Ȩ/Sso:.'o;2/2'G {K>''2# *2'o;>>2 '';.''2#21/*>2''o;1'GW+;;'+3'>2 2/* *>21';=ʴ;_1?.1S;s/#2{K 32o;''2*/o;'KK7' 2 2 '[{''2/G>212  *2'o;?GW;;;+;;3>2 >>/*>o;1';=51 #.)'12#2{K+G1>''>*/o;KK7_6{W' 2>2'#Gg';''2/G>2> #2'2>o;'o;GW;'+;'+2 2>>/*21)=?oo?.'S; 2' {K>o;'1>*/*>'_Ӈçk'2>>'';''2 ;#>2 2''2*>GW;';'++;++'+o;2>/ *>o;=51 3K''S;/2'{K2o;'1>/*>'6'.'>o;';#Gg[{'KK7' >#/*>211o;';;'o3';'+?2>*/ *o;=7oo+?[)''S;*2'{K2s/ 12*G {WӇ'.[{';'2>#2'';.''12/;#>2''2>1GW;;'+o?;+''+>  **2'5=';'# 1?[=''S;s/ 2'12>2o;#G/'6'[{:'2* >o;';'6'2'12#/;*2''1>o;;'GWo;+'>2>/ * 2'=';'1*=)'1>#>S;'12> G '6'.o';'o;/2''2'12#>2''>21>'+;'++o;'2*  * o;'';=*=''2>>2' 2>/'{W6'*&:;_'> 2'KK7''o;2 12>21''2'12> ';;'+o' 2/ # *2'=2'S;>2> G*o;''[{&'2/; 2'KK7'6Ӈçk'2>2o;12''2>;>'+;;o+'+;o+'2*/ *  o;15=''12>/G*2'ӇKK7'';[{#Gg'o;/2'{W6'o;>#>1''>;G> ';;3+'+3'2/   *21';5)*='1S;>#  >*'66'';'> 2'o;2'KK7{W.' 2#21'>G*> 1'o;;3+'+3;;o' /*  *o;1'5'1';=)'' ' S;>>2>'ӇKK7'>2 2>/#''2o;2/*21';G*>2'1'+;3+3;+2 *  *o;''1 12';=2'' S; 12>#/ >o;'KK766'2 2>G/*'2o;12*2''*>2' ';;oGW'+ +3;'+ /  **2'17###')5x=*''S;>212># *2o;'_Ӈ6'KK7'2*;G/2o;1'22''>2' 'o;3+2 ;o+'>/*  * 2o;'1GG71'+'' >>'2>*/>o;'66KK7'{W' *;G 16'>/o;'2 ';3+'+ 2+32*/ *  *>2o;'1''+'' >*>' 2>*2'KK76Ӈ6.'2*/;G 216'>*21'1';;o+'+o;>2+3;'+ */ ** >2o;'2* 2>*/*2'{W6Ӈ6.{Wo;*/; 21KK7.Ӈ12''+;;o+'+o;2>2+3;;+'+;/*  >2o;1'2*212>*/G *2'KK7.6Ӈ6.o;>*21'' > '+;;3+'+2*> 3'1;/*  >2o;1'''2/212>   >o;' >2 1'G?'+' >21'3;3++'+2*>2+3'1 >*> 1>/ *  >2o;1''+'?'2*>'2>/ *G *>o;' >2 1''+' >21' >2 1'3;3+' *> '+;'1 >/*212 * *>2o;1'''++?'2>*>'2>*/  2o;1''+_GG?'2> '' */*o;1'33+''1*>21+'1>/ 21o;*/* *>o;1';'+3+' >/'o;>**/21''+_GG?_GG?'2>21''2*o;'3+>*2 +'1>*>1 */>2o;''+3+ 2 12>>*21'+_GG?_GG?_GG?'2>21' 2>2'1>/*o;'+'2*>2'1> 1/*2o;''+3'+3+'2*2' 2>>21'+_GG?_GG?_GG?_GG?'> ''2' 2/ *>2''2* >o;''2>*2 ' >**21/*>o;1''?+3'?'2>*/>o;12>/ 2o;1_GG?_GG?_GG?121'2>*2/ *>2''o;>*/>o;''2>*2 12*/2>*>2o;1'' 2*2 >/*2o;1_GG?_GG?121'>**/*2''12**>1''o;>* 1 >*/ >'2>2o;1'+33+' 2>212>*/*>21_GG?{K73'1>2''o;/*  *>2''o;>*2' >*2 12/o;> 1''o'?+?'' 2 ' >/ *2o;1##oo121''2>*/*2''o;*2''+2>*2 1 >/1o;1'+'?3+'++'12*/ *21o;22 ''2>*/*''2 *>112>2o;2 1 / *o;''??'3;;3+'2>*/ *>2>2o;2 ''2>*/*''2*  *>''o;> 2>2 12/ o;''3'?3'+3;+' 2>*/ *2o;21'//#'2>*/ *>''2*1> *>'12>21o;2>>2 1'>*/ >21';33;3+'3;?'o;' 2>*/ *>o;1 2 21'"'kcG_kcGKK7"'2>*/*2''2>1> *2'2>o;1 2>>2 1'/1> >2o;''3;o'+?' 2' 2>*> 1 2>'>2'_kcG'_.6Ӈw6kcG"'2>*/*>2''2o;12;G/2o;21 2>2 1'>*/12>>21'+3;3++'o;2 2'1 2>*>2 1 2>*> '>2'6_KK7'kcG6Ӈw6_kcG'2>>2'' 1 *;G/*21'1'1o;2>2 1'>*/o;2>> ''+3;3'2 2>2 2 '1 2 1 2>*/*>2'2'KK7çkw6_".çkkcG"2>2o;'1 2*G;/*21'o;2> 1'12*/**>1'?3;o'2o;2*> 2>2 '1 2>*/ 2>2'_Ӈ6KK7"kcG.w_KK7'2>2o;' 2*G;/*>o;1'1'+'' 2/*o;''+'2>2 >*/*>2>>2o;2 ' 2>*/ *>2'kcGw._"kcGӇ.6_"''>G;*>o;1' 2/ 21'o;2>>2*/ *>2>*>2'o;2>*/*>  >'kcG6Ӈ6_wçkKK7''1G;*>o;''12>*>1'1 2>*2>*/*>* >2>2o;'2*/**  >'kcG6Ӈ6.Ӈ6'o;*G;*21''1 >*/o;''12>*/*> *>**2>2'o;>*/* *>'wçk_6.wkcG"1> G; *>21'o;2*/212>*  > *>  >2'2* ** *2'_w.çkӇw"'1 G; >o;1'12>*>'2>*/ **  >2'2* *>* 2'Ӈ.6w..w''o;*;/>21'1o;>*/>1'2>* *> *>2'2*  *>* *'KK7çkӇçk6.6'2 G; *2o;1'12>*1' >*/ *> *>2'2* *>2* *2'kcG6.w6Ӈw6Ӈw6_'>; >o;1'' 2/*1''2>* *>* *>o;'2*/*>o;>*/ *2'kcGçkwӇ6w6._.66.''1>; *21'1o;>*/ o;''2>*/ *2 *>2o;'2*/*'>* *2'kcG.6w6_KK7"'"kcG_6çkkcG''o;G;/>o;112*/2'1>>* *>2* *>2o;' */'2>2'KK7.6Ӈ6_"'2o;'"kcG_kcG'12*G;*21'1 >*/>''2> *>2> *>>o;1' / >'o;2>2 '"_6w6KK7"'2>*/*>2o;'kcG'1>*G;/>o;1'12>*'1>2*/ *>2**>2o;'>/ 2'"_.6w6kcG'2> ;G *2'KK7'1>*G *21'12*1'2>* *>2> *>o;''2*/2'KK7_.6wӇ6kcG'2> ;G *>o;'KK7'2>2''1>G/>o;1 >*/1'o;2>*/*>2>**>21''2* *2'KK7_6wӇ.w6kcG'2>*;G 2'2>*>2''o;/*21''12 1o;2*>2>**>21'' /*>2'KK7_6wӇw6kcG'2*;G 2'o;>*/ >2'2*o;11>* ''2>*>2>/*>21''>* >o;'KK7_6kcG'2/G >2'2 *>2'1>* *>1'12 2'12>>2 *>21''2*>1o;2 '"_kcG'>/G*>o;'>*/>o;'>*/211o;>/>112>>2o;2*>2 ''>*/>1o;2 '"'2G>'/ *  >2''1''o;/>o;''12*/*1'o;2>2 12*>2 1''2>*/>12>2o;'>/ >2'o;*/* 2''1o;2>2 1''2/ *21'12  *2''1'o;'1 >2 1'12/>o;2>2 '2/>2'2 *>**2''12>*>21o;2 1''/>o;112>/2o;' 2>2 1'' 2 1'' >*/*>o;'2>/>2o;'2*/>2'2>*>2**>'o;>*2 '1 1>2 1'2/*2o;'1 >*/2'1'1o;2*/>2o;1''1 1'12*/*2'2>* *2'2>  2'o;>*>'2/ >''1>*21'1o;>*/*>2* 21'1o;2>2' > ' */21o;1''1 2>*/*>1o;>/*>'2> *2'2>>2'2*  >'1o;>/21o;>*/ >o;11 2 1 2'>/ 212o;'' 2>/*21o;2>/*>'2 *2'o;>>2'2' /*>'1o;>/21o;/2'1o;21/ 1' >1o; '' 2'1>*/*21'o;2/>'2  *2'o;>>2'2>'2>'2*/21o;/>o;'1 2>>' 1>*' '1>2'1>/*>21'2>'2* *>'o;2>2'2>/ *>''o;>/21o;/*>1o;11 >/>*1o;/o;''1>2'2*/*  >2o;>2'2>*/    *'o;2>2 '>/ *>2'12*o;1>/ 12o;112/1> *1'2'12>'1  >*  *>12o;'2>*/   *'2>2'2* *2'o;/>1o;/*>2>21'2* 1'  2'> 112>'1> >2o;2>>21 '2>*/*o;2>2'2'2**>2'' *21 *2>211>** '*/>1>*1'2>2'2> *>2o;1''2*>2 '2'2/*>2'12 212*  211> o;'/ *121'2o;' 2>2 1'' 2 '22'>>2'12*> 1 / *>2112>* 21'2>*  *>12>1'''2'2''o;>2 1 >*/ **  *2o;1' 2>*/*2o;1 2>*212 1''1 1 2>*/*>2>*>2o;1'1 2>2 1 2>2 1 '''1''1'''?''?+33+?'1'?+3;3+'2>2 1''?+3;3' 2>*>2 1''?+;;+'2>/* >2o;1''?3;;3+'2>*/ >2o;'?+3?''?;;'O/ 2>*/>2o;''?+o;3+'?+3;;'12>*/*2o;''+?o;;'?;;'12>*/ *2o;1'?o;;'?;33;12>/ *2o;11'?o;33;?;3;;o'2>**2 '1'+3;;;?;;;;;'1>*/*2o;''1?+o;;;;;3;o;3'2>***>21'1 2 +3;33;;3'1'3'2>**>2 '' 2> ?3;3+'1'+3;31 2>2 1'2>**>21' 2>2'3+'1 2>2 1'+3;1';;3'1 2>*/*>2o;12>*21' 2>/o;?3'1 2>*/ *>2o;'+ 1;;3' 2>/*>* *2o;1>*/*>2''o;2/2'+3+'o;2>*/*>2o;1'> 3;;'1 >*>2' 2>/>2o;12>**/ *>2o;''' >*> '3+' 2/*>2o;12+;'12>*2'G# GG&+?1 2*/*2o;12>**/**>2''1o;2 1'' 2>>2'GW' >**>2213312>>2'?& '1'2>*2 12**/*** *>21'1o;2>**>2 1' 2>2 '1 >/*2>>o;'33'2>2'?&+?'GW'2>21'2/*21o;2>**/** * *>*>2''o;2>**>2 1'1 >/*>*2o;+3'2>2' ?'GW3+'2>>1 >>1' >**/* * * * *>>2''2>**>*  *>21o;>/ *2o;12>2' 'GW3'2212>*/>1o;2**/* * *>o;1 2'2>**>2 1'1 2>/>o;1 >/ >2> 2' ?'GW33+'>*>o;'>*>12 1' >>*/* * * 2G?_GG?2o;121''12>/*>21''1o;2>>' 2/*> *>2'?'+33+o;>*/*>21>**12>o;1 **>*/*7_G1o;>211 2 2*>2'1 2'1 2>212>*>/* 2' 'GW3;3'2* *>o;1>**12>2 12*/>*27? _Go;>>2'1 212>*>2'1 2>2'1 1 2/  > >21GW3;3+'2 * *21o;>**12*21 >/>2>o;? ##?G?2*>1' 2> 2*>21' 2>/>2o;12>*/>*  >*>2o;'GW3;3+ 2/**212**12*>o;12/ 2_G{K3? 7_G2*2112>12>>1''o;2>/ >2'o;2>/ >*  *>2*>2o;1'GWo' >*/>21> *>12>12  2G?7? ?+?{Ko;*>1' >212**>2112>>*/*>212*>2  *2*>2o;'GWo'2>*/>o;1>>2*>12*/G?7 ?G?*>2'12o;2 '12>*>/*>212>* >  >*  *>2*>2o;'GW3'2>**2 **>2*212*/>_G7 +G? *>1'o;>*>12*21' >*>*>212>*/>*  2>* *2>*>2o;'3'2>*212*2>212/*>{K7++?G?* *2''2*>1>>2112*>*>*2'o;>*/>* >2*  *o;2**>21'2>>*21>*2 21**/>37?31?+G? * *>11>*2'* >211o;>/*2'o;2>*>2*/ 2>* *>o;2*/**>o;'2>* 1>*2 21/* *3?+31??_G>* >1'1*o;>**>2'' >*/**>o;' 2>*2>*/ 2 *>>****>2''2>*1o;>*2> 1*2G?731?+?_G>/* >2''o;*>2>/>2'12* *>21'o;2*>2>*/*>2>* *>2>***>21'2>>1o;>**2>**>1 *>2G?317?+3>*/*>2''2*>2 21''o;>**>2' 2> 2>*/*2>22**>**>1+'2>>12**2>**>12>>o;1G?_G37#+3>/* >21'2* > 21'12*>2'GW'o;2> 2>*/**o;2> 12/>*>o;+'2>2212**2>*>'2>*/12o;1> G?_G3732/ >112* > >2'1>**2'o'1o;2 2>*/*  *o;2 12***/>**2?'2>2>212*2*>'2 * *1o;1>**>2 G?_G{K2/*>2'1>*>**>2''o;>**2'o+'1 2 2>*/*  *12o;1 2>/ * >2**>1'2>2>21>2*2'> **12**>2 *>2'1> >**>21'2**>?3'1o;1 2>*/*  *>1o;12>/ * * >o;2*>1'2>2>**2*2'>*212/21'o;>/>**>2112 *>?3;3+12o;'12>*/*  *>1> *>2o;1>*>1'2>>2>**>**2'>2*/>1'2*>* >2o;'12/*>?3;'o;2>1 2 '12>*/* *212>*>2o;1 >*21? >>2 1>**** '>/ *>''2*>* >o;'12*?+'2>> 2 12>*/*>2'2>*>2o;1 >>21? >>2 1>*>*> >/  *>2''o;>*/>2''2 *?+12*>12>2>2'1> * *>2'>2o;1 2>>21+'2>2 12*>*>1 / * 2'1 >* *>2''2**>?;'o;*>12> > 12>/*21'2'1'_2>>21'2>>o;12>*>>1 /  *2'1> **>2''2 >*>?;+12*212>'o;2' >*>2'1'[{' 2>*> 1+? 2>212>G>>12*/ *211>* * **>2'12 *>?'1>*212*2'12*>21o;2 '6Ӈ6'[{'2>*/;>o;1?'2>2 2*G*>12/  *>1''>* **>2''o;2>/ >*>?'o;>*>21>/*>o;'çk12*>2 '2>'Ӈ'#Gg&'KK7'2*/;G>o;1 2> 12**2'2/*>12''2* * *>112> ***>2?12>*2o;1>/*2'KK71>*>21'*>'ӇKK7''> ; G*>2'2>o;'2>*2'2/ *>o;21'2/ >1'o;2>/*  >2+312>2'o;*2'KK7'2>2 12>2'6Ӈ'6{W *;/G>2>2'2>2'o;2>*2' /  * *2>o;''2>*/ *>112>** >*>2?312>2o;1>*> '1>2 1' 6ӇӇ6{W 2/;>'o;2'>2 12>**21 */* *2>21'o;2*>**>1'12>>*/* *>*>2?12o;>>212 1'2>2 '.6' 2/*>21'o;2> ' 2**2 12/* * 22o;'12>2> '12>*/* *>*>2?'2o;2>*'1' ' '>*>2 ' >/>21'2>2o;1 2**212*/>>2''o;2 1"12>2'"'2>/* >>2?'o;212 1*/*2'.Ӈ.'>* ;G **2>'+?o;2> '12>*21 */* *>*>211'""1>G2""'*>* * >>2+1'1>*  *>'.6' G*>2>*2>o;'? 2 ' 2>*> '/* **>*>21'"kcGKK7"1>G2"KK7"'>** >>2+1o;2 1>/*>* *>'> G*o;*>2>>1 2'? 2 ' 2>/*>2' * * * *>*>21'_kcG"2*2"'2>* *>2>3'2>2'*>*G >'>**o;'*>2 1 21 '?2 2' 2>*>2'*/* **>21'KK7_"'2>*G*>o;"kcG"'>***>2>3+1>2'>*G/*>2o;1 2 '2*>2 1'+'1 ''3?'2 2' 2>*21*/ * * * >o;''"."'2>**>1'"KK7'>*2>2+'2>'> '/** >o;1'2'*>2 1'+3'33? 2'1 2>21/    * * G*>1'KK7_'1>>1'kcG'2* **o;2>2+33+1>' '>' .'2>*>2 1'+3333? 2 '1 2 1*G/; * * ** *>1'.KK7'12>*>21''2>* *12>2'+;'2o;'1> 1 >2'ӇӇ.' >>2 1'+3;3;3?'2>2 '1 /**G* * ** ; *>''.KK7'12>>2o;'1'"'2*>*/> *o;1>2'+;3+'2 1 1>2 '1 >'._' >2 1' 2?3;;+' 2>2 2*/*** * ** G* >2''kcG.KK7''12o;11'"'2/>***212'3+'2o;'>*2'1' ' 2 1' 2>?;;;;;3+'12>*/*** *>* G  *>2''KK7_''1o;1'''>*>/*>G 1o;2+3;3'*2'+'2 '1' 2> +3;;;3++'12>*>*>*2>*/ *>o;''KK7.kcG''_kcG'>*2>*G/*21o;+3;3'+'+'3+'2>2?;;3?++'12>*>>2*/ *>1'".KK7''"'> >2>*G *>1'+33'+?o;2>>'+3;;o?+' 2 '1 2>>**>/  *>1'KK7_kcG''.'1> *>* G >21+3;3+'3'o+?o;2s/*> +;33;o?+3?'2>2 '1 >*/  *>1'_kcGKK7"'"."'>*G   *>1'+3+1'+'+o+?o;>*>*>2?3;;3'+3?'2>2 '1 2 2>**>*/>2''KK7_kcG_kcG'2> G**>2'?+3+1o;2''21'3+? 2s/2'+3;;3+'+' 2>2 ''2>2>/> **2''KK7_kcG"'2>*G* *>2'+1o;2 '+'2>2 '3+?' 2 '?+3;;3?+'2>2 '2>2>*>*/ >o;''""'2*/G **>2?1o;2 2>2' 2 >>2 'o+?+;;;;3+'2>2'+' 2>>2>*/ *>'""'12>* G *>'2>*>>2o;>>2 1o3;;;3'22'+3+''2>>2>*/* >1'12>*   *>212o;2>*>*>2>>2 ';o;;;3' 22'3'12s/*>/* * 2''2>*   *>2o;12>2>*/* *>2*>2o;';33;3+' 2>2'+3;'+12>*>*/ **o;'' >*/ *>212>2>* *  *>2>*>21'o3+?+3;3+'2>2'+3;+''12s/**  **1'o;>*/>212>2>*  * *>2>*2o;1+o+33+?+33'2>2'+33;;?3' 2s/**/* *>112** >2o;2>2>*/ *2>*>2o;'o+33+?+3?'2>* '3+3;;'' 2s/**/*>2'12  *>212>2>*   >2*>21++?+o3+??'o;2>*2'3?+3;;?' 2s/**/*  *2o;''2**/ 2'o;2*2*/  *2> *>2o;'+?+o+?'?' 2>*/>'+3+?+3;;''o;2s/**/ *>21'o;>*/**>212>*2*  *> >2'+?'?++?'o;2>*/2'o?+3;?3'o;2s/*/*>2'12*  *2'o;2*>2*/  *> >21?+?'o;2>*2>*/*>'o+?3;;+'3'o;2*/*  2o;'12s/*/* *212>**>2*  *>*21?+?'o;2>212>*/*2>* *>2'o+?3;;?+3+'2s/* *>21''o;2/ *21o;2/ 2*  > *>o;?+o'1 2>* 12>/>2>*2'+?3;;o?''12s/* *>o;1 '12>/  212* >*  >* *>2?3'1o;2>2 1 2>2 2>>22'+'+;;o+'+'1s/*/*  >21'o;>/  *212>  >  >* >21o3'1o;1 21 1 2>/2o;2 '+'?3;;o?'+12s//*>2o;1' >*/  *212  *>  >* 21o3'o;2 2*/>o;2 '+?;;o?''12s//*>2o;'12/  *>o;>/ *>2* >  *>13;3'o;2>12>*/2o;'+'3;;o?'+'12s/*/*>2o;'' >*/*>12>  >2* *>  *>1;3'o;2>*12>*2 '+'3;;o+'12>*>2112*/ *212  >* ***21;;3'o;2>212 '++'?3;;o+'2>*2 '' 2/ *212*  >* *>* *2';;3+'+?+?+3+?3;;o?2>*>2o;'12/*  *21>* *>2* >  *>2';;+'+333'+3;;;'>**>2o;'' 2/*21>  *>*  >* *>2';+'+o;3+'+3;;;;31>* *>2'12 *>'2  >2*  * *>o;';3+'+3;;3+?+3;;3+'+33+1>**/ *>1''o;>*/>'2* >o;>*/* *>21+;3+'+3;;3+'+33'G?_G{KG3G?''12>*>   *>o;'_.'12/* '2* *>2>* *>* *>o;'+o;+3+'?+33+'3'G?G# +?{K'S;>>/   *2'.6Ӈ6''o;>*;/'o;/*>2o;>*/ > *>21'+o;333GW'+3+''# ?{K'S;>2>*   *21Ӈ6.''o;2*/; * 2> *2o;2>*/ * *>2o;' 2 'GWo3;3+'++''oo##? +{K'_GS;>*/** *21'12>***> 21>**>2o;2>*/* *>212>*2'GWo;3+'+++'ooooGG### +?{K1o;S;**/* *>1''o;>2>*/*>2o;*/>12  *>2o;2 2>*/* *>2o;2>**> +GWo;;+?'#ooGG##? +?G{K1S;**/** *>1''2>2* **2o;1>/>12>* *>2o;2 >*/* *2o;'2>*2'GWo;3++''{K# ?&oo+?++?G'31>*/* *>* >11>>S;* * *2o;' 2* 2/ *>2>2o;>*/ *2o;'2>**> '+o;;3GW''{KG +#&oo##+?+?3'G?31>*/ * **>*S;1'o;>2>*/* *2o;' 2>*>12>*/ >o;>2>2 2>*/ *2o;12>*/*>2'+o;;3''{K#++?+##'3'S;* * * * *>2>21'2>2* * *2'kcG'o;>' >*/2o;2> >*/*>212>* >2'GWo+''{K3# +3?##_G'33'>* * * > * *S;>>o;'1>2 >*/*>o;'kcG"' >*>1 >*/o;2>>2 >*/>22>* *>o;'o3GW''{K'G?3?3'3? ?3'2*/* * 2> * *2>21''o;>2 2*/*>2''"'o;>>12>*1 2>2 >*/>212>* *>2o;GWo+''{K?3'3'3 ?'2>*2>*2 2 '2>21>*>'"KK7'?'"' 2121' *>2 /*>2'2>*G *>2+oGW''{K #2+?3'1S;>*>2>*o;2 112>212*>2''''>S;12>11>*> */>1'>* *>2o;+oGW''_G##oo# ?+?{KG?'2 1 2>*>2 >*>12 '12>212>>'kcG'??'KK7' > 2>'12*2 2/>1'2*G *>2>2'GW''&3?3'##'2>21o;2>>21 >>21''"12>' 2>2'kcG''KK7'>S; '2''2*2 >*/2'2G  *>2o;>2'++''oo''_G''>2' 2>2 2>2 ''' 2>2'1 2'KK7'#''S; ''2''> 1>*/212>* *G* *212o;1''##'&'12'>>2'1 2 1 2>2 1'"' 2>2'1'"'KK7'"' ''1*>1'2>* 2*/ *G* *>211o;''''12>''2*>2 11''KK7'1 2>2'kcG'"'' *>''212>**/* G *>21'''''o;2>*/*>'2* >21'KK7kcG"'1o;2 ''''12 12'>*/* G * *2''oo'''o;2>*/*>  *>21'KK7'''''12'1'>>*G  *21o;'''' 2 >*/* *  *2'''KK7'''''12o;1 >*>*/G *>12o;''1 2>2>/* **  *>1'KK7''''''''1o;'12>**>2*/G*o;>2 '1 2>>2>*/* *> *>21''''KK7'''1'12G *2>* *2*>21'o;2*2>*/*>*  *>21''KK7''2*G 2* >**>o;' 2>*>*/ *>*>o;''KK7'' *G/*2o;2/*> >2112>*>**>2>*>2o;'KK7' >*/* *>2o;*G*/*2112*  *>2>2o;''"'1>*/*21 **21'o;>*/**>2o;' 2 '"''"'' >>21>/  *21'2> **  *>2o;''KK7'''' >2 1>*/G **o;'12>*/*  *>2''KK7'''o;2 1 >*/G * *>112> *  *>2o;'KK7''''1 >*/G *2112>* * *>2o;''KK7'''' 2 >*/* G *>2'12>*  *>2''KK7'''12>** * G *2o;''2>*/ * *>2o;'KK7''''2>*/*G *>21'o;>*/ * *>2o;''''KK7''12>**/ G >21'o;2>*/ * *>2o;''''KK7"'' 2>**/G >21'2>*  * *>2o;''"''' 2>**/*  >o;'' >/*  * *>2o;''KK7''1'12>*/*  *>o;'2>*/*2o;'''12>2 1 2>*/*  *>21'2>*21''12>*>21o;2>*/ *>21''2>2o;'2 1''12>*/* * **>21o;2>*/>212>21o;1'2>*>2 ''12*/ >2>>>21o;2>*/*>*2''o;>2' 2>2 2>*/G*>2o;''2>*>2'1' 2>>21 2>**>1''>2 2>*2 2>/>21''12>*>2'1o;2>2 1' 2>>212>**>2''2 2>**22/*>2o;''12>*>21 2>*/ *>21o;2>**>1''12>*/2o;2>**>21'' 2>**>212>*/*>1'2>*>2''o;2/*>21>**>o;1'12>*>2' 2>*/*>2>21o;''12>*>12/*>212'12>*>2'o;2>*/*>2o;2 12*> 2*/>2'21o;2>**>2' 2>*/*2>2'o;>2 >*/>'>o;''2>/2'o;2**>21'22>*/>'2>o;'2>*/*>212* *>21'2* >'2**>'22'12  2o;1>*/*>21'2*  >'2 >'2*>112 *2o;>*/* *>2o;''2>*/ *>'2*>'2 >o;'12>*/*>212*  * *>21'2>*  >'o;**>'2/2''2>*>212*  * *>2o;' >*/* *>'o;/ *>12*2''o;2>>21>*/ * *>2o;112>*/* *21o;>*/ >12>o;'' 2>2 1>*/*** * * *>2o;1'' 2>*>212>/*  *212>21'1o;2 12>*/** * * * * * *>2o;1'' 2>> 12>*/* * *>21o;2 ''1'1 2>*>2 1'1 2>2 12>*/* * * * *>2'o;''1 2>>2 1''1 2 ' 2>*>2 ''1 2 1''1' 2>2 1''1''1'deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/dim.aura000066400000000000000000000013001512772601700245560ustar00rootroot00000000000000☄️ << )+<qqq$WWW)TTT*nnn%www XXX+<<<3$$$8(((9:::4 < ]]])9995 >CE ?YYY(7< sssAAA/!!!=FK<<<<.nnn RRR"(((3@JONI?$$$1TTT!###2%HLF<%%%/OOO  jjj777.9 BGE%!!!6<<<*iii: NNN')))19> ;!!!5888-aaa"<fff???(!!!.1)999(ZZZ"mmmOOO <<<"GGG ddd  www <<<deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/episode_complete.aura000066400000000000000000000042141512772601700273340ustar00rootroot00000000000000☄️ " ! 7 ,  ! T# !,7 7!,  jL[S 6 h?#, , .%0'%(  BM0#77.''=@3 2 %E#=%#, =hS  {KN'!D; 2 =$  q 2 2 :8C 2 7 )'%#,    0B*Zq  2 v5"'% , ,9{o 2trA98 L^ 2 t )%WC 2wv<1/3 2 E* =%  ,9?  z20fc 2  x4'9 %  /IlzPkp{:$? 2 v2 R !, 4?W fa; 2fES,}2 f! ! )- 8> 2 ~ 2 h3M2-z#' !#),  w7&J 2 5<hm2 x% !;'  E8[  aR65 2 j  2Z> P1+i  1=  cA '  '& 2Y95(jNN 2? ,. 9e ]3<B(C 2 JM $\ o{LFV   ^U*)7 ,.;7  2  2 / % 7 $FG   2h< .,   &5] km^  7 ;=+(,./8# 9 4'09 , 0'2=';#7deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/font_medium.png000066400000000000000000000501431512772601700261600ustar00rootroot00000000000000PNG  IHDRP*IDATx 䶎m~_F]%bpOD]Y@HBOdM{Bo?s0F_d~7,Ri?AǏ}/QHH<_ynϮ3G/m`U G5{n1Og "Ug/J710)#ӆ!.xܿі+L"Qc22/;4_љlEgQY Ge,҆Q2lEW+ڄ _Jˏzles<4-:xsiOvfU/@_{%/}~c{~EϟD Z.u)AV01G{Ꞗ]XYff^k1zOdKЦ_'>US-B^-E:""5.xi|g\LtW\FjG,k.eή#2@r趶 =bOoUˎl3j[n'S|Zet[g,0#_~r% ۡ}ݑ s؆o>YS-F:': ăfs]k}$+ o.}|hOSS /!aRPka{B!B!B!B!B!B!B2%BH|)C`G+y'D˓77pp#?ɡv6ݧmnmcsVwl yڑɑ"Od~OzFp۰W9A [TGCHezfr_@&kǸxChБi#[:dZljW X8ĘAi {ge| e(,ߢqqR2 31hK,y׽L/CƢٕ}?d;Fڸz֝Aάz%܃0ґe8+l[wu/Q+D\Z4c92+Ʊ}c O/Cz|[W92رᬨάґ< XݼP2eR>*}8cپEV̼{+wUKJS^R֠.o9W[]yU+ᴭLLt0V/CD*YF84\#ˌUl`uT,3x/D̪=fX èGج;-piGcȣ:e9"?|z$GWW:lǭekeujKKU:5uGͯhgVړ׬C]n2u? Tatf@42uo0mǪc:^-ۗ8FqrdgCG҃ˆkg7*dghlM3dd,Q1{31~2ejƬ"[WYaÙ={[[mYϼuꌽ13VԾnDD^V6ͳ$O,"N&Sj)*[Wbr;7Lř`tG3U eFʷ5\dLB#u,\,#{4ϙًf LExO~}JVʭqEf3} ;۶[^W;gȺ>yr9A!o_])"BЙYeiB O!B!B!B!B!B!B!jF.[ٗV3S+o̗akm"N/y[ꗬҩmU~!m1j}"8Cڕ/>>ޙIYv3ІPЊEKġlo3"5f2}_ΜK7T\eԛXQRew2k0~㔏U>'mEWY;c,i-Ch9fd'Z6~Vf۹bcVՈKˊjqf>/y /&J ,"23tY)ځzN;:[k9 amѕ/H_*YdbEw; 3tYʰ\DzXF @, mg9]r6s/™͢?Qe# ;fhQzZ3Vj*Qy٧% XXl[vfԒW2MFݔ"l'Kͫe/{a%,YfA% 4#23 Uf;?Y!+ 2NluqD-ך{x5py&"H:j,C1`*V̪ReyߥPQ"vf8f2LPY@#!o92+wfډ 8+)I9AE82۳*y+2H<Yr=#ŨE'^ff83 Zvt{i~8UL'--}#I#ٸLM3|]c;yto3_t#LcZu3Ys~'fdxy2 Grt}ܿͱG}z.4R#7Ģ.b8ӮeE4y-}KeU='i Ԉm%GѦ62itU]癹K?#g+$s^/2e%V(a Y76U;19! b3'SCd& K, nt& w5+̬̚bQ+(g,Rt2Dl6շlBcP|Oc*Ai V5m π>ec}+Q[ʝ,cE[.2Wg=Z^p)vf%ʌ23w^ 3V Y޵ivGwwՉT,^v]^y2L~ӎv3yֳG/ tꢪ9ढި eIHƑY9{Z*YdřIXi_w (g}MOaLm,ͯ\^e|LvaTHtzo&o^r6zNc jr;-?]WB!B!B!B!B!B!B!B!B!xJ0B!T{rcW;e?[=k^JSVkމ/GّXq6:# /7-g3ήݩ>:3ɵHY{k>YifZD˒kG3+T? #q澢׌l߫ג>u'cF^Z9N_c̶=G&g:3ݘSEEPh#y03,J>@2dr0TD٩Y "g0FIGQd?lewW:Yxt~ָȊGʍE~o񡏁#{G83N%ZcS92c^<2 O]=Yick}ct8nC2#Tx68"Ӳ.DߒPlX綠3o9#$z껻%8aEv ( !]1382K6Q(^F^oH}l7EPG 2DLoK.}l18ɞ3>t,G;)V8ʙYFK'v~:ⵣ3+ƎjEW'd YIy*1FNыj<8mfs 4ڙYUϕeYАpgdg39g=@ c윙%2:^1?8 ag* {+ h(-ڸi"DX}IYF\uw7-zICFgh,gjE#hYѝG:lL,^Յ^lJE3ٙs&$tdG7V"2 B^1Rl^_#fxxvPk'~q.%e93$P18YșE,?rfyK 0 @EљO,K5ga $dv<2:s\Ld~rufřY[aJ,8kCU< 3z“ք2f8̬E5YPXrgː#ͣ͵*5fkD,gf9 -G93/L~7($|r9EP:.߾z\0J(>c I}d ^iW#ř!Y (zY,:.7Zv1J/o^wّWf Y2t%t`Vd jG)yFt[YnT)Z=^FLm:T*tETd)*6m]V:3];WVeGGYXg!r t@e< k=1_gۨ|\I`(l%ZY?MMDTs YC380oLcbo̴ۜ4?XYTQyNRQ)sh; BLknj݃.o!~!ĉ{F)JO/,PE^2qRtUg6G=^l vYg+Sbȋ"&-f*!hme5WejZVVZ>tGؕDeAGݱ4sd5uIQ2/ȳKWdZU=Ց<u0#DŴaƏtg!k2$hygy:C#uG7ʪ-xb Ād8n}*hz;|ڎ2+lقUy382:{fhv$Ò;bq֑^z ˱f 0uxvFF#geffEvȊ2(ޢ& z"1TB!B!B!B!B!B!B!BuvdswxlSBȧg2:ꗣ>Św-/+%tnl1z*6z'x2yCzffߨ4+fg.[gF\ }6PNܙ&MAge?7T0jO NtvhڑZY _,ovf o˸Ʒpla &teuCqa;68Wr_sfZqU<=Yvb&ѴgЄAV(t93}8崫Yj\4z^a٧'ڈ.g&ّv$U.q HF#c#B+_ӑOrf 9Z人TUUdf̲@:Z-ĉneɑe3,G {"3E1:"*+e:3z;(cd9튒|u_&ˡљš mY$t9(2sf?]2\2ƍ\%th (Q27FEnW9ܺ,]<gfMj뒹|"3S[FI*y4Z.:21rOxW?rf򛊬wV>a^]|9dvhtf@wX=X,Qf%by٧5XHm*gFd^^˼3˙oJl<m\OVΌO"ΙQpQtW=iӱFD;Y{ oDfVmt%#;kfd3PJC.ٳ9*E93,"(Tm:˙E0JazU3YeƣyPDeضLD=zԶ\_2ۡy'cG|DW$]EFY6{Y b92qґ9˔2U~z-вW0ӡ"%۠/c՝t֒p  bGc[s+zՅYz6DZf=vLVg Px3 Q{۬ϑ@DgcYgeW?###otfHZs*3$Hgx`qFVDu;6]Vo>: ݑ$2fevhZ/]0UP>kd^_OL,Y9f#lV@Աq:s2\u8ذzeԛ03D#}CmzgW!Rqِd,:(;ʼ_1FcNy&-’2 !YWB>e|ixyR#!ad7ʿ6%fS8u_ñJ !*M\1K=ґBHT\ZBNûn;N`{BH^@ûI!`LZ.s3B B!BȧNzB E難xON\B́/~sq!πr[:^;!m)2$rfǧkwMǡ_u3> y ߫3?߶*Qm}xʙu` cwZ2ۺJNW r`ktm)4d0z-L=)v̊í @5P7d{qui(3z`-K~Yka{^tӆUȗOs3h#? 2T8r}?~3>0Oqoh{y5ј];k|b3-3*Ư2T:'"l'A(װJц2YFv\Kq}1k(V <>È{QqfDіQp.<-2~hq\F@ VLdy\9zvm%[A~`yGiG|]Mk{(CgVf#?Ev#[L+#}w|:W;ȴc fGhj第7ʱX" uF":V0h`>bp}V>aLܱ23`9m8ʳZ92>rf  >` NęDͱ#Xt@Eei1ޘ{msoYtΘ93*%эiDK 3CS"QQq%6iypgVUWzWˎ72V\[NRy(3ZcbY࠿r*QYB3 DT̼1sfyffrhӋ=00Dˬ K4SIgrb=9W韖8WjMdk[":Pgf)&s,iKRe|3pހQƞ2Dl5bLK߬<8([exK"~(ĮfMa jC CZIVh~ _kuq;+24 2ntSm= #oE,VpJ0;̲v橎d%>NF $i%FjE'{33=rfVzǎuYxRTdUO/͌ⳝz-cUk@k_Vߝ'e,XxvWy>xv=s JufքGwtsUeF"PRb<@k>)k7h"#ك(e:3cJrWW'JOTF\lν`_zѺb~? 瓬;Sݬ?ʔs:F#/R4i#K'^ƙ N;3Էgfr[pfϞ3csFGG^iԂLT,J˚#̲I6W;2f9n+)J?xX:iU),a؉@WFt(#DA4 e qfY+Ei}h}=')gKGm3bCY^ ks=if/QZsd ggdzuɭbU|>6dRמ+rkUyc5Nk]@H(Ƨ !Xάj[ !(j}x*H݁0,gyYj/!+7"e9adLH櫬<33BK`ˉ2GrɊ>.g&EϗenB'$=XGu{sx=B!apI!wͻJa-m~c6 z3+D4H=MB9󄲑{NFuΑ_hpU'o|݉7f[7Ց_h̑!IX,32 pF+c}f ?p. "VRZ ZPKLOGF͊r/,S$ՍSU⬴QVap!zB"<*U3Ձ`-Qv޵#ʆi:2Bec=[*=gdæZϡs\]NaeF[ċ|u}Nꕤ1 \!=Fp8'+G! !JLN(h!joԉB!B!B!B!B!B!B!B!B![By,etlBnN9;~B6 TԼSCR !UGSx`)!92ɲfuwnB>J;%qv$ NBH=⨴^gւN!|xFiʬߢfh]:BVqd[33\A!˙aVɱZB!32#z2B!X`^!=fjFkVB,Ųʑ B!94 ֛Bf\!2foǷ}Y!rCL:E20[/B!c|!B!B!B!B!B!B!B!B!B!CpO؏!%f{B<GBHbl}< Aȹtx@OY&!7E 1$g`"X'7:+0$L;4ȾcF`O>,!U d椬yBC;O >LB^c 9a|xmSx[IȫM6 ɼX4V9'oGo2 y5^fĚvdҡUV@edo>-sRSj,y-8XAf+8;v{CI+ʞ!R7luc5癸nj{Ww9kKځ!=db}<ߨ˪OփWm%'Z _% Yj^Ҟܗ23!7c 1wd#dZqZxLf[ް":3Bn6ԞS{꽳sc:mAn؇ޠ!A.isY / ~UoЉWཞG:ɦW*guUx *;}!#f-Wv'#!7F kW}q:|eO6o/GVѠӍl}BImrO/B9`֕{83}B9<}^DGF!W>ّ G&BO!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B>ϟ|p/?~(*yGm%_˙]sw!?_یoϽdr!7CwKo"k<ˡ;By #+?ߟj^G|-oyԴ^"kWƿ6h9Bc(]; 6s*~"Uy,O>BfF:[vjVƂ~8q_U򼿿\V̉^Qg0Z!<*S8ܙĢȔ [2-o~nwXK%Uy3ޚK;Xsa^F̗i}Q%[/zQbG$PƟfz@j)s&Sr-=#dBm _ryTt&:+\zO m>ғB^őX|T֋60f3K!mtN#2VK!mt.0rznF4 ="YnF뒉B^\BiuDj)ݬZ^\Biuyk P-[.!a:<EZ^\BiuɴʚUK!mtN#%~JWTK!mt.w#Zl-B0|Fh ȐB˅:Jyr !i3PWfsƑVK!mt;y0<9p[rƑVK!mtN#/BYR8y63-B0|F^2f{nF4Wғu%6: -!B!B!B!S!ܭ +gȐ:^Y{Dt$e6$Gz+|خ#7ZvȖ=6T5JJ_}(-ϳdC~kvt|u~P%_+#!ut"@s2dᡇiWgtڳۑՇOXޗ#.yOf>'yH QdΌ5`wJ_yZޛgm:k]}ߚ7,eǟm7#vRq%]dKTa#M ЕՇ,EWg5̼L,Ñ }ߺ7OZwefeW"i/ԙ_tZg<2r9{+3Y̯=O~F¹0/vdx}N=kY9[shg:5:YTWfQF:Q֤J"ܰ?3۞SzzEnOCF73Y-׫@AY;1Ht{f<L&{FDj$]ч,S_9zDpغ~zjӨ>|o˜<܊da ˆP4|-c$D%dSȳ%3eG:_ vXNM;{Gad^gFW yTo.KGLVv$keT# *eAYFSkfQ-O˭r03iE魪JgJq-kK3ᬌ;IZ3[qu+V靽lC6Vj9zEneq`dEcpQϣW spfN%;dK,ب93әUeJlWQ0KѥM>DʒHtˬ{X)av絧IoaD9Rnh1FԤ5W&۟ ʾ#]+2lKl\)G[i3_`LE{ʎ#fيCŠǕgm%:M:d$|e1Frc/yk^љU_Atd+\p(=d:Шݸe93KW`OWV _]~zr~[K@Z5Ί-=2KSDWҡ#KY)5vhu;+3]̔epH \$[}:\vR!z]Gr܁^7ZB+-㆝3VeJlK3++3f,2sH8oU^tȣŕDxۣj!b vJg;AqgeUeJlkЬ ͕DGR`!6ZH]}辩IdiUZM̜BR4[Q*\)E|ZAtf+-#[^YAte,UFՠ$;hpG}3]s9JL˙-I oĸ̪2lKQi3̂ Dml? P0h2A.ƐNN5NKﲗ ڙ *3l :3Jo*J_jkm["S YǓȹ J֝dgRAtf,tk̳|Kd0A'KX`y B!B!B}L֬1/svi}x#w3o+0Y퇯sɒ%{3ɢ:kyhO5Vꪮ{[̱#c6r"7SfyXq2J"gev}9go{12+8{H>{~z~{W9CoWE!>֦Lc9f8V^A;P=y^ە5{{2kV[F)2ҵ"zU~KF`=VV`D?Y-ge;fYd_71ֱwysQK;G%Tݩ#lfJjDt@dFGceFw_'}ճbg*]-h5fV#32 Gҙj]Vdh> $hdV>O{v*L\^2r?Y;4o,22}1Jʜu7+[ud3CvIJdx3cLI(r~sra{P7%?cchr65q(r##!gzF=x]u$/Zń1JG6fALGU^~+ljb뻪_FQ}.y;gw+NʵtOt&pO;, 4Α`?N(r}\DW,(c+'J/ϯX+FdT_FEγ6BSAIIu4vEm#ѽO>ujm=grm/3c93:Ҍכ'q1fuVJ~+.lܷV |=#3کt'Ƀi ] f4"EǗUnȒ.9KV܌m3&?3Sس>m{ũD,3,wrUI:78Fegՙ:m40G>jR6x{* Ul1иg22v-Z5s˩Ti֙6Rovͳё9}c"k%Zsr$x=̬83AeP3lݴ)B-Ջ2Y:J(ߊv#3Ɋ8Ԏ,Șz>rIs34zFV WLĶi3v"5Od拪&T:]͌ב웩v*z*V3zUb9cGtX%0T:]'{fq턥kDÙa[dL'j'ҁ]=E˩TCtg"$V`kD*s >"jҥ,srdClܮ_jg kC,̜yejy.Rw,c:sdF*sЋVE;Jҭ2%8?3cD==);hgy8l=QvsϡYk~~EVupG jҭd"KQGΚ_QeřV4̗JP\:hV9T瓼gĂv(TgV6j.gvUv(C KgeZ%M3 דVI7V;](>két)ג/CHHf'CՙQΥS{q<=2V0+*2>1Z@uGpBca0B!B!B!#xЦ0ddcKd?/QOLc|XY!rv{*Ufe"eT9ꠤyv&xlg]O%Ew&&70-n]3U ћl:17뼢`XD^k>L{WO%{~ёW!V~^ YZJhd=ώ 3)KV?Y`<:#i7;d_,STGxo],9;Z&R2V]kZ կZ1+gYoē7sYcT"ceP2xA8ߝ摼jހWӌ,V"rZ zޗϭM2=Ii]3yf+]t@DSng Ll?tA":caF>3!3 юN|^>¢guF'NG>ˣy$/yqY2#t2QWJȵDG})Dɝ^L}̱d##(K8mlj.O[$+W }l#*;͞"s"^^f]eh><[>="se_~nGmu[hc9k"=u[;0a mYaaX1 nL] @.3>ʒ{R\fiyM=Dgg6]p,g|.jmN9;)qG~/yKNrW"ҨwɴNy{dfgql+ZX#ߑ%EF*V5[q1ff =n嚑ejUFS=d2DvsW~ڱhGF><ԡ'ʶfsx`4d]F)+Fފ6w;ԲzLfSѲ2p]=P`ڱh3#-jbDO\ayfUh]_0`Q]e;eZ`mXgktՑ*? p,g|FvXA*]e3˨o7^M%¨w켇l-;Vd,ݴ}K[XHgVeViteK;4ϸgox2n2QWYe1S_Cִ }H8$Վ嬑Z_U>9[C&GRgv<2#f- @jR˲;Y#e::ȩ˳#w- F( @$Ӓm;ec`0ZNPW,%̺ˊ4z2wGdͲ+rUYw weWn}T{Z%KV3Z6jw8;3olSd}F2t>P2 ]2EnQ.*webPw[+b#uى2I#Ǘxs锢oc+z\H4B,sB*ґBy$tbB!B!B!B!B-:Bц<ɪz62s<+}Nz;m ')gCv'+Z/k|+_dl7Tɩc%GRݗ:7m*񅴕;~sE|-;F$B>K huhJzg&*9p䌕Rg7m uHe@ ypfg=::dWO>Ǯ&*9pďRї:ڷ;m)&,hx2Ȍ̌ u!8gdF7GPYg7T˩WWN #+ZFRm-g__#kJ0}G/T/б*kHiUN|{"̡w^zֻK?c?ؾQcWұ*kAZf(1ZKepwƯ*s4ǿ%c(y޸+X{pui:K5_ݡcu2+K|XX |gz㮰ƄGQ.}d2pC5̪ N^kIP کw):dwXIk{GݜYs٫Vu;D'et/ұ:kfA ~hc6쇲R}DָJ 6N]:Vg-=Xe-& MjX: |+Ҹ;ؖ׷}AvUY'_oGՋrQ ||^>J>t/ұ2k9w'Co駺䧿c~qwʫз}AvY=dɦñtj^Ó;>Qk Gi ۩dg-_ñtzGJԒAO.F TKc2]12DZX1 %tf.+qJou܄//5e[u%%>—9 %O5> !|w=I!k]TD!w*Bgq[By!4IENDdeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/font_medium.png.font000066400000000000000000000004601512772601700271220ustar00rootroot00000000000000 ^        A∞ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßdeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/font_small.png000066400000000000000000000523251512772601700260140ustar00rootroot00000000000000PNG  IHDRTIDATx}=%;rek%^2{ZlAK ڗ/cqېX1x@Uydf7*p12HDL `0 `0 `0㗿gr2X)w]OwajlY֟}}Qۿ__oeḥw.w<i3YGA~\({p k?u9v!ktrޭŇ9ځ:*)<:r0FϡOy^Eyhv&=@CFG@2解ANr`9p7 .{Ge'㎃vN>u:d?ǿaeENe=\(Id4*#ESh Éi\Jր>8d)*2=7 Iذ3vW!!:^ ٠uA.ĭ4SlN9_ t[RhD Q}_ 8 +᪃3[2!4LĻ 2p;73dfW~%nhS9`*K,3 >4#';[!2TPW :h*ct Nt ΈVrlΡWNtv`dYY% vwgA=K6p]:T%1>>3Q=q ~ bu pg 85({,$+7(ˆL>T+T=~wTuS:nuMfMƙTGEo\(e΄OH.\^C)*G}@ɵ A`BR62v{VF ΌߊO ®z #;y9U n.q.Ɉ_$с u83iq7mT.`3ahL`X9Yk+g\#Nyr:4գ3ua2fνPm#Jܦ+?ȴkz |us':y*ύDEI/L~wfV-7c0*čA K.vr=_p]9kfɬalwn8ب+n=we/=ҶD_8mX~V?|DuQ{˘Β+pП #7@N):fW~ |둙b}kv9`|Ja'F8w|?Fȟ&U'&UF(39fet1vU琥:d'瀖penGmh ntgu (v" +Ȫ5y9Qw7 Dҙ*Z&ĩ##A;cP]?qR>BP;)a<`rt3.[hyַB苣mQGb5tփj4ҝaYA0;٘fYRZܖQ}:v//B]8Us?K\~FHj)GOΖi_o"m TLNP%Sl[ պxTUyPLJJR(wD"hnhv!kݕvęXeY8TdSlRGGS@#GVkRJM``QVA*J F!8a(m#_e+{qrIE50Ue$p?;ݟqYAj%nMf_fH^1uW2tك?Q0t,+7ݶ}W+cGX_efgVkxVcOVr ~Uޝ<#@Tnjs/3q諌Y;EjcnьT!;ۦUpfj O4sf5w˪eCeFR]*{/xܱ>LwFmaF겚9Yt#ّV(o<-X~W6c$D``Yfy*$|V<2mvl&W\^.Ɵml`YU?jSׯ](x ztܧv*]V^ r^Cꐕ-_m ]iF{gwK[TF^]~:|HKol%[.㋘'򟐍x^lc `#`q0 `0 `0 `0 `0 `0 z`0 ?:f|c%_]nlTU~c`lA h=;{iʙ)WG_CtB;.l+=+Džv{GA }6Um#S^6Hu,# gHg [ٺÕ Z_lꔹeT!,b#YWbrV*lxLN徏]o~R$]B辠ǹ&LvFY2`ҭfk֡UX]+h+7ke2{=*,;dq}y5J]U9 Q/n7Wit̅T2'3ˤ֪|% IYWzuF,h`.Uq#,[eVm6lsMi @8(\W{fHxUׇuR 7ۊ{+W²>씗Pmm@}eQ'[r7Uة 2Y!~`M:"dlt"&~WVmu®%}\I\Qq`53}sбy4k( ͽUz;/F(}9W8$ee>dnp~ǽ|Ulc&3^9qd:F\g-Yƿ3> %f;(C.0&%x4dv]`r*aG8":.;UFxY`*8rqqnPd3~vp(}ud=sTvfU]2A a̠rmi-qr欂ʌ7YgFqgL溬3ؑq{Dse%rdMif#j!m(r̙qP[%;+3('GVn~NjCڦev`wjFHh; :%NwڰJquBː|[փ3)UF fɸ 9@ T@[egV{a*_/W,U&mwj}΀fPƇicigե-F_WL^ħ2=m,>ώ+1x4+#Xf1ƭt.)CupTfЫ,unp@uҭ.. COVm6`祵ʆ6?Y.Ⱦ"Mp0\Ce7JF';vVv H޽.;]*kci6Q . +}mfE3ތz9Cv:x팵Ha|nh۞umL%M8,ne;P*̢rFUlVxo: \[v+UrJ,aVڤ-YVu4igT-۽yn{y."lFVFQSFt: F i~Hl`KȬl|N]̸ `\m4Hu_-x@&֙;{HcA+hY%^?b>3?]XLJ^ޭ`hT柂Pό>>(&`0 `0 `0(s*)cGC`>LgଜM.U YOAg )#[ixBxW$~ycbGWrwݫL&l""cU>G8z_Hpṷ}@pv//r/|琍slJ{ ;]WZUuʹ+2*!7g=+G|T%_ʀ>w_TS/XCpL6ӽJ?W\GD`9_#/dꑱSC~|3ygFtaЁlpXӣ*m 2Ν8įy;@+.Qq yLo *wAB6#. r{.j#.rΫD#+l5DG;c_s{ {uk,WAt*VVݱY*G0=Mg m6;ܩuQ9LlDʿկ m 6ٱқ%_SO퉐WuTe4:Kll͜c3r~a?OU:]RvnS6-ݿ:ʨ<\(CeW&HQSۚ]gcPNsgG%n;8ˆcuvsͬQ/#wr~`'fcEjg+Lъ3^N־_TeJ"ֶqfsWՃm3;Ⱦ][+= &%}v 0,9lW*ʤG  @H=3h\GrtjEkC$g=80͒K=Ih?ȩ:c}>ُclFɳ%[4OdJ:??5?TwC|2trgb8XׄƑ4` 9vwm 24Sf0WvDtZz[Noi,Í;>dwgkOA5 2ZqwOoOnWlx%Sҧ!=vcWeuTQyv=XO3}<+E^QX =]uA_'p{Ү>iG{TBeT.{,?w VڣۏU1i'p#;!sdT6}P)2: 7k0l'sf\wY7]QmWG3cWd ~2f4^V:e l>_ D2z[Cu3d; Wե=]yն`]V^c:v&z=:lٯd]c췈>YC9R )x=0g׮n[TwP3˜!cU^4jUd;b,lW> \03QmX _㜱'Ti]]vi_:.`ѣ{3EmA _%WBֳ3:Td> sLMcWQp1&n{tAWڃs*_sC;Bi[&:=VmNF_%+C ~m8x1fET'۫A dRE(ylm]9۶H/-6FEƪ<C 2w:WaYdK6U1ճvՇ38]2pakY)y羪COpw2"zn40뒱LQȘddgTF_@(hY g5> /t7AUɄ2vd;{P͒;dtƕfȐ+띺NN:D'֫>ꒁzp~dOmϙIQrtuJȨavEښ;d^|8H#K萃yBCJ;= L2%j`2ݫ|r[G Ca'*%?IgԷx% ?\S,m4aK謒EU]E0W˸*q% ;k,*S&n;CXEюd8Cn&\n [ T' fOԵ*1(ǿEgsJMC8էWkJqU3U4Ҫ1L.luj!Sv2]e{|.fXO!nYp;dpv3;Qё%?E)fIT3~n{41LɊBոѸQr8+ߙs5껳rΌml2CE82&jmSFęH1FȓȲp gSX#KS* guɌ<(/oӣgi`VJUyAՇefYrN]2툲JNn̲*CUt}E킝O_FYd&8풡%̗͒ r*DVeywfT<2a,$ LniCΪ_!#R>SeYrn9n{#fSvL^R}D%nUQɒ"#>N9l%B٪7/BVF[H3BQ&fɝ2:(7!;c@DUS;v̌dtxot\wz(y% !SN湕 τ݄dtSddq<~]laeoP 4tbtt`0ȁ38O s0= ; `0`0tcԕu8)[ir;d}6 1t.=w|צꃂC PFw]"Wrxg9Ȗ}POvrodIJWʨYP!83+ClRYq3P/`oBAPr+z7Q>|n[OTYph]v9zdUuW'KG ER@dUߡI\&OoWe;Ǿ} N@*#+=>nT".lWc7P#'@]3[O\)zdL<_:.7\h&uڐ: A܁AlLcJ]A@9v{}*>\ `Ep3-_ UVo/PIuTMt:+e=8+u}o:8:V%\cY2X,")"] %.jO!"zdU[ W쓑嬟r>_:dYS6w2tFwqKU|NP{o>`g*Stz2l"U&jVu&#ڶkttu z%#T;fiZ`! NTc^GNዐD˲qg-w22KRgᖪ]ICN= @ m~qǾ!.E"vd~ rn,2o&0QFe}~ :|XN<'Qe̐!g2:TK+e;JZA]uA["Ž*ui5a9̹lB 9Gy:=O#HkE7}gi%~Xo}g frUq.gt\I:dtvhS ?Gç^ *'CHG\!\>LӽJτ\Fl1,m`*COY퇶uu 0U=F^ƝՌ gCFiWLBc@C%*r)^)W?&,C7d r:,]fv X;Y3z ޯg2vVFGPr2KZb2S*'p& qwhȝ rTS]qtqYe(3Yt1QW122P.+ e]2:HCe%18ʴ`0 jiWpWt`0܋'$]`00`0 `0:Pycdrx #$W= oT&)*3HV9 Zhw8峴G*o=ӎ} tz?;;豒A 腲z{~5H]٘S]9C&nqۣ]; ļBw[^EYu+tȐ\U+}*e9`}u$s5(:a*c6;uđm,Z2~%x8m~3mw뾳8yץʗDAeY "[lfQ끵|ѥSD d2GwEA^v]@F5sr9R82 c UiW"q;H!;U :vraN]z'RG%~ .֮~SF:U Py\\aߞxWmsD!}g!\^gF1gNv\ x !+wo_TBmT")x~NYOnD4BJxܖwh w"g n' -Yg_eY-2]m#S,ؗ@%̬R+]9՛9T;C!vnd%9L(" u^9:du҆Us08C .:kM|EN<䴩no+[d홭;FV:p1l=l#iПk39dk}sssLV YsB&SLNJpEsȫtvȡ5bw ;T$*ocW=ؙg!bpZ:уlƿݾ^Urm1h^T>q}6-]et;Qanlxq.Y?~ wv:;k y'nS6Pg*Ze{t JQr\o>QGSQ]#S^-}Dt@]4R2V&}wi5L  Y.IT?C(Z҃ϲlݑqgwn[=X.L#D;:*.1tHq+Q[9VWzT =´zꧺ:e3DǑ;ʽqN<'s,C/sIA/a翇fpmdOGDm߫zGNa g8%jLxEeq82XGf%F!C*u\pA$ {Vv` ӠG*ZQvґ 3Ty1]o89iŃnT Dm., )'A믈WǝkڏLN Xm7 gNLBNc<utPnA jL<ݬ;Ɉ ;6Dj91zp="" ~:mDUA2cfglt̆؏;թkVn]ŢeAԈ."2xj-C㶣Y#24+iEh=&Ql.T Tfww6Q*ɮWrZ|o:mu&>u>/[.4Q;) gWu1g `l_% ! o,NgvrDW%wԙj&ȿ9kY393: 8*ZeRQ`yn`:Dj9Lj<^*3\etVEC-x!3w׵Wgt3cN2u ~sRV^p,SβA]gf`V>ԝeyYn@ywmW]~5vtO >+E0f, ~tJz׹]4̃$%UP֧C֧#;ƳY9 3A۔tZٗc+9Wut7xصUl.؊dvs̖ M]=ޕ 7hH@{U{umjt@;,rpJU)SYG\Yʩ^"VPypeMO-u}ea׷L8Vo lDO&xPҎnl8cc; tj;N3S:{y2v!cg'?y6+x6>}'lev̙2Jʖ>Z]9G8*uȶGShѠR#(Iu3ѿ[!<\ٻoedv9S3u% ;֣KKW%*e;{|Ѿ|^=hvF4u!̆t6_9NLu ݄8Wts(A| `g2:2 1T`0 `0 #Έⴾ߲>*P|>C:_mĦo.t0R;W-+ \>27=UÌ;N͖ݽßN;;߫>+S!+o]rrdf뀲z4귬w֥\i` OgʻuhJ9S?;-atCVry]TNcY+BD]%љ\C|2w2t7s=y3ȩJ?AH%c @]0>vX pHُEPTLO7B _H*/ Fu ?8ŠSܢzI "hYFnPnZb,rY=h52V2N͝- (ݺ`$7iYg " D&)8C3%;\\EhW‰5,*/"UYr`/ďE߹J*{=gp^Q퐡urU:;]%Ce/_}6QUPg%s*a#nY. UFN6P^Ƕr V2,K#Ld`ڐ֥::W|י ҳ}vjP%'*؎32Xn :rt`YJNuů|q|wOB4?#mY`dcӰ.Z(2X;38 T?t<]JfWU=p 2E6Rr ;ҟg2AvUF.zN.v;d@~ x\+Md;w.uXGac?kWU!rvYɸ"N=VS!~6hu5wQH5c'R%~pnaI-dWe'+"zVSg.g9qŸ>rvƾƍYw&3wePT53eeD?Vat7Etw+bN uQ'ӆ` 5Xʹ k4ӮP9wfO@Gt6T*gk sO"2ڧSuB}{5rn}:_%J0Q a[ݹd<M>qg@;ddUYM*c G7Z}g!~8>gqFN]v26*vZw ټڣJ ~ PSԁ\T m,L تWesǮ.`g@u6Pw͑OEYH.;KU<+[wBM9)ˮjn W1Z3kEgg: @"$}8ۊU; "G9:uT+ëm]yU9-[q|8l={Ff< I {jP2P%WԑYvw[lQ3 `wt<ɏ _FG'nG:{^Aԓ SG0 `0 `0HN'`0 nFw2g9k]y[߳g"W໊tȖ:SSA?]wp.}u= gg'}EtzR &w%}I܂G:ot'#se*Ȟ̶ḵëaV{|Uۖ(v{c 8]}[o:vVbe&;Xc(T2Zw/;걚>c1|{_~*g.OY] g䬏s7.گKiuNzՃƹ8uKG*Wҁ ɊmNHh_0A*$ \sӠ+>k><#~;z:6Vs3j.W}g'2Lש;s6K}nN]|"{Yęx& E]XtmU;Y:O6Vy8\p߂Xܾդe5+iglkY:Y.x:L@3hEgVSwOT 6VwWqd2Ǥ 1KvZgsǹ=ءFjO%>Aml7cT7|D\g`߻AhOWW ;=4K뵳v%\sN~2Stܮm?h[..ڑ>G$)Nu~; Ugfi4[nC6twn5E_ԉ$ŷ|as%sײWYmmAmGceN@3y\X%~X%2 7aP=ܾrg-tG\Wfݑ%3}< 067aX5&~ J^9gI/lWSNQ~ \g=2d\Nɲy 6"uF`}kWC jJ8;ڳNv^ M}Zt0ƟێC.UWKN?W>9R2WN!;-." :x]L7ؕ_.*}y:Q*is{8F(h=t߁@C8sf|<9/8r~w Aqb7l!K֐U]2T_gQ"d{ψGBűu֙'RMWz|VowWj 'Ajs+_ |32GW24?9;|oIvT>'xCr_A8eS ]njCes.,iuv[v>'C쏮d6TaMgBl 7#41fVN]Ɣ: >^E0FQg%RYdBc(l\Z+5JL~DpNְz-Y~aǥәg>4ލk{௜*[Gtld+}u҃;vN:䰓Yw;v $Bgk^՝o</Y ;;uJxYٝaeH*ǾʢL3Zg Fd0$JoJq2&vqnlvuɔl%lw6՝-2F2!%*^: ~ }\F?t{r[&OΝ>~\-^*D+U]Ϡ:6 mկ\joPg8LE&jZwfuڕ}R-8+*wwgD]-#)C*tAEy#yt#pB 50u#yMWaHe_'3G•y  3өg3.ɒ W'YHg2]B&}<{0 % ̵YԆ <0 >*rxMRH`0 OD~^q`0 O`0 `0 dQ ~tTkW;6i|S#;|#}1VdI,GS*UdW-}}{|;XL'㴿iP=kg݋O^e+Iqg>ՕG!GِGⴼΑ*=>x'zzKmGVT=@Hq=УlVݭߠ:Ξ@~]JAR6ыv ǯfXуgVz6NKܫo-ƗJd/N{0QeyCIҫ#Z}t"cSH+P,_qJs]>ҹK˯f,N+9\_E]MNy;>+g֔+1H/"e!NoP!]Wߕ`ȵsĈL39idhSF׿JDٟcDV/oո^eœzlչ9R?ꇮJ.$  vLSezpD8qYcގ>m>d{3D5P^į+2Ntu_5{3ڃmiWn7mGWO7r4 rm^ͨ>#CjH{p(9E=ulJyzsڕn%Eٳm*dOxv&>QV3e d3;YcÊ"}*":ٜ(U}vc?lo4n6$Cψ*``>vz"~ɼ/ .ʷ,\5rXCH:̺>Δ !~LS\^ øy},3c|&+ei8eW6UfDΊ~FWY=]u=vzwrx+=VuQg5 Tq˪럕9v鹯X%xפJŽ;pIPeym+4hfo>_3P Ze< }>f6gGSƾ+Qc%LA3!0qu?D.WWsr\>l/"2hyng; ɨ>9=9Gua@g9δ4 ,WH=TE:9m9Ԅ۔=]E孖ʲqu<tv9JǼ3~Vv2,c:2T30"A S=!ķ54Ur9g𮞧N:z^ȼ v0:ݯ1b㙺q""M]:)gc.~:K >/33[v6<+;9~_GG#ŷ5vB::cdv[CΕ<[թb+95JF*rPy]mZ15tNg$@,;K#Lf ⮽dUGS.W<Ojӧ`E˭6;9Z;=OJ9@:uQ$`W'e)!]5gP\?/[ |v0@"$YU9=5o{D^&X= " }9*8(K }~wݗ^wPދ*6?Jp̯ХKN<21 bA{;^U cGQ~ "{F%pP9g? ~I|e?rr`~ =ĻM~Ƿ`0 ` 6"> BvWehn}^G@ԫۭ2&go >^̔Eo\~gϞg#ѻIwpY40cA͠xe)Uo .ߢ7lsw6jd4=x=NSՎ=ڕ;{-]Lƕ/u`6>s%~̾ A|nF%lƏv`y?ҦLf|*F3Nra=043 TQk6>.xįBEo n.OvNeG ܴ<!ԀY2^3W?'L(|/U?O^3⇍_Yf=|v\8+3d_:aWti+G?VцFE|ș+Y_woZOr_s`7?8 ROnʀ %&>@ѩi;w>>@B6335ĭx4466 jkn1z@U54E 556يhjngjn1y7673LLO<4Z\`/14OOR4I\\`@1-/'-1)\]`I,#)1-XZ^-1)#,<416/'/614x<668?1fhl-egk1>9klpeU x,jko9$>1zxy667#M??A;?x%??A GP778"klo768 J'88:SSV]^a9  :M>4!!deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_b.aura000066400000000000000000000016441512772601700263410ustar00rootroot00000000000000☄️  1B0 $556NNPXY\0557"M657jkogjko667 G! I>>@Ҩxx>>@C#445ۖ 1;w43569ijnz~.&yhil~ 667/11x3355OOR1xkmqxPikoҐMNQ2,J\]`n--\]`I,#+--Ξx[\_E,knr"npu1OOQ07791 ¥"1y668 klo;~ yjkn &668ٙ'15576,N??A!w??A FP778klp768 J'88:SSV]^a9$:M>4!!deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_back.aura000066400000000000000000000025461512772601700270220ustar00rootroot00000000000000☄️ ~_N=NET]?PJ8_adegj55D0 '_aciknY]_b_ac^hjmW$%-g~cehRSU_'==- lnq`ad]_a<==jlohjl->=Dglof,=%%8K>KUY@N7%7445^99;GGIGGJDDF657113E:9;_NNQwstxEEG<<=6??ACCEBBDCCEz{??AvxzCCDCCE545l^_cÝPPS667@EFH324qrvy__c545pDDF445ijnxrsw546GGI445YXY\879BBD656/KKN1??AHHICCEEEGAAC1JJM99:5wx{::<xXY\556`CCEFFI667pquxlmp445DDG444j^_b,pqu545FFH7788MMP[\_667nBBE<<>pquAACHHJAAC??A"BBDfgkxuvzHHJ::>@::;BBD889BADFFIDDF;;=<<=7323<<>FGH98: ==?878BBDDDF[\^3deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_down.aura000066400000000000000000000020271512772601700270630ustar00rootroot00000000000000☄️ Hgs-//0\^aoqt#//1'*MMPx@z,MMP* DDGw7 679679'+,-7~74'$'oquZ;*7' ..0HILAAD456))*S346,f*#9HIL-.0I\\`E345FGIEFH0335loq?%[\_#koqt,wAACEEHGGJ@AD,,# }vw{stxw'EFH`bDEG,? }?uwz'AAC'v=?joqu,wABD./0//1.-,*F[]`Dx//1nprv,[\`001MNQGGI::<-.0j/=::<)/01+"LQSU ?46'Duwz&= , M&&z17W&&= a—! ,Cwy|>!, K%%'ABDJKM.=K#deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_guide.aura000066400000000000000000000017741512772601700272210ustar00rootroot00000000000000☄️ ++!2:-++}!!!>$ :(((ggg333D"+ <'''333?+  '''$$'''1+  ><$$VVVF <$$lll 0b$$$$$ZZZ3 "8H""">>>$RRR; $+>DDDHHH{{{FFF.&WWWNNN$rrr$wD,$mmm$IIIggg! !!!MMM~~~iii)$$8 >?" ]]]^^^(((I(  475A!!!R=+ :9;:2-:-/-deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_lb.aura000066400000000000000000000017451512772601700265170ustar00rootroot00000000000000☄️  6 '''$$$"""!!!$!!!%$$$!!!Q| UFFFOOOXXX[[[ZZZUYYYZZZ;;;>4 222WWW[[[@@MMMXaJJJYYY ?88JJJNWLLLVVV%HHH48,GGG?C0JJJ8%III%801!!!'@@@!8%4%-48!???!!!"(((...YYYOOOyyy1 JJJZZZ777&&&KKK ) 8))YYY(((&&&RRR\\\48%?@@@EFFFXXX0!YYYU,0YYY888$$$45= 2)))m/+,,, $$$ .1,$$$++U+ deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_left.aura000066400000000000000000000022751512772601700270530ustar00rootroot00000000000000☄️ Hgs-//0\^aoqt#//1'*MMPx@z,MMP* DDGw7 679679!+,-~74'=ZTKCIoquZ;*'$HpqtrswNOR..1xf346S))*456AADHIL-.0##%Uyy@ADEFH335loqx[\_I<CDF GHJw,oquj !"Uz<=@ABE0446prvxx[\`Ftvx{}UVY,,.w334T-.0::4++,'668nosw;( CDE46 *MMPw' 002]_a]_b2p deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_lt.aura000066400000000000000000000023421512772601700265330ustar00rootroot00000000000000☄️ ,dQJ@BAP`exW60@! :::FFF@AAA@GGG...--- G+++XXX]]]U(U111g'ZZZKKK?:#111 s$$$ (((YYY uuu^^^4:#2223!!!UUUMMM{{{<4?***#736RRR% 8+++#73MMMUUU&;:#73sHHHYYY  #111rpJJJXXX4%4 888#222&&& LLL8...#111"""Xq?NNNWWW88+3333*PPP8///***,QQQVVV8U!?7!!!UUU!8'...-==='''YYYRRR%!+++$$$:$WWWUUU8)?666///G6u???]]],888'444%%%88897000;G6;3=:!!!)nCdeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_misc1.aura000066400000000000000000000017161512772601700271340ustar00rootroot00000000000000☄️ .32$-++03 %+"""/(*111>>>BBB 76222777, 7+++---79+%;,%+;EA###?)**@e>D:  } #A  _kmqU ?DFH\bdg 6::fhl !O:dgjM!05:cfi^ nqt[]`Ԋ&ҐBCF jlp579fhk eWY\PRU _ae]_biloEGJ*,whkn<Rm<hjmdeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_rb.aura000066400000000000000000000017771512772601700265320ustar00rootroot00000000000000☄️  U 6 +$$$"""%!!!$"""###(((  zN"""#***K>>>[[[ZZZYYY[[[XXXTTTFFF---fOOOWWW8VVV///&[,MMMVVV%U;GGGISJJJXXX818JJJJDGGG8___HHH48GGG=.BBB8%9;;;hhh48ZZZ;;;"""$$$08ttt=TTTJJJ4WWW***QQQ,,,ZZZ4)88;EEEDDD\\\%88OOO### :::TTTZZZZZZ,,YYY0WWWCCC$:U369Uh,,,2+U### -1$$$--- 66!!! +U+deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_right.aura000066400000000000000000000023271512772601700272340ustar00rootroot00000000000000☄️ Hgs-//0\^aoqt#//1'*MMPx@z,MMP* DDGw7 679679'+,-7~74!$'oquZ;*ICKTZ=..0HILAAD456))*S346xfBBENORrswpqtHI\\`E345FGI[\_##%koqtwAACEFHCCE../< !"001MNQGGI::<-.0T334x,,.UWY{}tvx+"4mor,i,LGP[ cA '++,>4++,,668nosw( CDE46 *MMP?' 002]_a]_b2p deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_rt.aura000066400000000000000000000023161512772601700265420ustar00rootroot00000000000000☄️ /~l`XDABAEO]O\$UU!6111&&&333222:::EEE@CCCAAA)));.---222111UZZZ;???p---222222;KKK;GGG~)...222222%\\\~~~";???S )'222'%CCC\\\666,()''%{{{XXX"cccZZZ///###*)'111U,%pppMMMrrrXXX)))111,,,U%8< %%%000222 SSS%%XXX !!!+---333 ,YYY'''"""+EEE //////94  !!!222'!4YYY:9994---,,,8[[[&&&%%%YYY\\\.*a111 ===B333(((333 48(777?.m000222---%@(PPP!!! 111DDD"""H6+ ;@-++&oE+deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_start.aura000066400000000000000000000017701512772601700272550ustar00rootroot00000000000000☄️ Kp9%992295922959929<;=BBD4446<>?557AABM223AACAAC@CCEvx{==?suyBBDEEG"CCECCE98:AMMP]^a778{445m``dhqsw445FFHGGJ667rswxjko445DDFBBD:9;WWZ556[CCEDDFAAC"(HHK::;1CCE7783KKN("U>>@KLNCCE444]ZZ]"x778DDF434lmqpqu545 445prv]]a545m667n\\`șhJJL<<=:CCEHHJAACmnr<<=BBD>>@>>?ODDFpquxhim@@BBBD0AAC324@@B=;;>@88: DEF==?BBD[\^EEHdeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_touchpad.aura000066400000000000000000000010531512772601700277210ustar00rootroot00000000000000☄️  ; ! !V$$$z)))+*+**,,,-,,-,+,**+))*(((('(%%&~!!"[445GGIxxkT778|~kU#2}" --.z 0%$%npsU10 !xWXZU&( !x.I@@Bx&), %0/1ǝ-4(')--8'2%%&deh112W>>@1"1 ../DDGx2:/'""#`"!#  n %2 deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_up.aura000066400000000000000000000020441512772601700265370ustar00rootroot00000000000000☄️  I#$%;<>ABD1-# Artx/ ruxA, a# a X&x&yM&y&,Dvx{&#,$IRSV//RSV# ..0HILAAD456))*$*#9-.0I\\`E--/,,/loq?x[\_#koqtw;;=124E1132,,# }vw{stxwABC  @AC0,? }?uwz'EEHY['v=?joqu,wFHJ @ACCDF GHJ-y*F[]`Dx457CCEABE0446prv,[\`001MNQGGI::<-.0T334x)=&MNQ/01+"4mor,i,14=46''++,>4++,' 668nosw;( CDE46 *MMP?' 002]_a]_b2Lp deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_x.aura000066400000000000000000000014751512772601700263710ustar00rootroot00000000000000☄️  1B0 $556NNPXY\0557"M657jkogjko667 G! I>>@Ҩ;w>>@B#445ۗwx3356 ijn{mpue @ijm.667/ۊmpuZ6675OOR414J\]`--).,I)--)#,4-4x<779ڋ/668 klo~Ajko9&668ٙy667N??A!?x%??A GP778klp768 J'88:SSV]^a9 :M>4!!deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/ps_y.aura000066400000000000000000000015511512772601700263650ustar00rootroot00000000000000☄️  1B0 $556NNPXY\0557"M657jkogjko667 G! I>>@Ҩ4w>>@B#445ە~",x3356 ijn4"wy}@4,ijm.667/"fhm-Ty6675OOR411oqu*11414J\]`1-prv9 -1).,I)ikox*)#,oquoqu4x4779"{}ijx/668 kloUƱx,jko &66841;x667N??A!ѩxx%??A GP778klpx768 J'88:SSV]^a9 :M>4!!deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_a.aura000066400000000000000000000015611512772601700270250ustar00rootroot00000000000000☄️ %%% ### TR: = :%%%???WWW\\\-/  ###$$$GGG___&?$$$!!!>>>^^^%?>>>P6-?Uϲ<<<%?WWW!!!888\\\OOO=̒444=#AAA8sss```")"<Ԗ777;=ɝ???4=6;"""WWWXXX0004YYYY!!!//$T'(((%%%GGG4GGG $$$  /XXX\\\>>```>>> P6)))WWWXXXhhhٝIIIXXX!!!888\\\PPP\\\ ]]]=#AAAOOO-Ԙ"#"-?,bbb;=%-""":::___=6"""WWWWWWggg!,[[[XXX&Y!!!???xxxKKK /$T'(((%%%GGGUGGG $$$ /<___U>>===Y777RRR(555???O???&&&UUU0?>,aaa 888deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_guide.aura000066400000000000000000000013671512772601700277060ustar00rootroot00000000000000☄️ +$$$))) f f((( ### t***.w wOOO```)))U88PPP(""".QQQaaaHHH.___)48333bbbGGGmmmeee^^^SSS___444DDD```2ooo9ttt RRRZZZ1<1^^^1www}}}!. 7XXX1444bbbDDD||| www!GGGWWW^^^!!!1RRRaaaCCC"1&"^^^!!!2}OOO>>```00```* >BBB^^^NNN* %XXX8'''777^^^YYY888ppp@@@@QQQ""".""",TTT%===aaa<<<4!]]][[[111999###(%[[[999 XXX^^^CCC5;;;aaa* ___QQQ^^^]]]RRR* jTTTuTTT^^^RRRKKKOOOSSS5Sooo333&&&MMM!o&&& W(((:%%%#&&&:++++Udeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_left.aura000066400000000000000000000021171512772601700275350ustar00rootroot00000000000000☄️ 666!!!!"""&!!!".../LUR6(((===DDD='''  :::888aaa\\\=;;; @@@@@@:::]]]LLL3??? >>>YYYNNNMMM6999rrr ;;;AAA $???7/@@@O%%%###܌777^^^7888+S///ʜdddU=U  /// DDD~~~WWW]]]DDD!!!+1UUU么%U8) LLL>0TTT# -%8) <777AAAVVVaaa!!!)---Û___?8‘!!!BBBJ###щ77777=UQ???*:::YYY>>>UUUtttIII^^^! CCC((( @@@@@@)7U <<<999ZZZ  )))EEEMMMMMMFFF6ZS6!!!->?!!!0 /deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_misc1.aura000066400000000000000000000011371512772601700276200ustar00rootroot00000000000000☄️ +$$$))) f f((( ### t***.w wOOO___UPPP(""".QQQ```@8?PPP4""".333aaa%)%U```2DDD\\\MMM%%—===<<< %%8\\\1<18MMM%%ė*1%%8\\\DDD444-MMM)@U < <)%8!!!1RRR?U8888___!!!2}%]]]< < <%(q0009z '''s%p """+deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_rb.aura000066400000000000000000000017171512772601700272130ustar00rootroot00000000000000☄️ 0"""/j(((Z"""@ ---:::;;;NNN',,,......222III^^^WWW'''&}"""IIIVVVU@ ^^^666"""E*```0U?```???W'''8XXXKKK¢<8^^^ >""".QQQ[[[%<^^^777999111[[[]]]TTT.ssscccVVV""",5 CCCZZZXXXRRR"555<<)))$$$U28222rrr###>>>4(@JJJ%%%'!!!/III8@%zzz䲈;!!!2JJJ8{{{(>###"BBBbbb]]]7 4ttt-&&&***&@ ΢111ggg _@袈444^^^U7888謈$$$筈...===_(((111 333 AAA 777;;;PPPaaa ___ ___ 666]]]^^^:::sss<<<MMM)3...@@@ZZZbbb999<<< >'''EEEMMMFFF)))NY6 !!!/?4/deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_start.aura000066400000000000000000000012601512772601700277360ustar00rootroot00000000000000☄️    NNN888 JJJMMMm  y;;;'''$$$)***:CCC333]]]]]]YYY1*1]]]222???~&&&^^^WWW8/-]]])ȸ-<4)\\\HHH'''SSSVVVNNN<-<%4?&&&VVV%<-<%4HHH)<-<4)-(((___VVV8<-<)?'''<<<555*]]]XXX*1^^^444777 ="/FFF/'''GGGy {BBB777 888 U deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_up.aura000066400000000000000000000021741512772601700272320ustar00rootroot00000000000000☄️ CCC000 &####'''@@@CCC=...DDDQQQIII+++>>>\<<<CCC///@@@UUU222^^^ @@@U(((``` 000 !!!.Ubbb 6:::BBB??? QQQxxx3KXXXlllP''''''|||ZZZ.*U $$$@@@aaa^^^@7""")!!!0III)LLL###!AAA747!!!!,((( =U  !!! U 0007%87===</<<<AAA===333]]]%8^^^???888```>>>@/2^^^%@,>>>777 6U2]]]MMM@]]]&333  !!!222aaa @ccc111 &&&DDDMMMMMMGGG+++333 D!^6 !!!+>?!!!1/deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_x.aura000066400000000000000000000016551512772601700270600ustar00rootroot00000000000000☄️ !!! (((TW  = ;$$$???ZZZ___;[[[/%%% ###FFF```UGGG$$$!!!6T!!!===HHH@|||TTT??? P6WWWYYYuuu%=YYYXXX!!!=\\\OOO,,,@@@%=#AAA8,ΫqqqCCC8)"84,zzz58;=4@LLL="""WWWYYY=oooJJJ999,XXX"""Y$/___vvv @@@$R((( GGG GGG $$$  /ZZZ^^^@,/  !!!+$ \3Z ,,,$# 22deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/steam_y.aura000066400000000000000000000015701512772601700270550ustar00rootroot00000000000000☄️ %%% ### TR: =  :%%%???XXX]]]@>>78(7???P6)))WWWYYY777XXX555<!!!888\\\4mmm]]]666=#AAA8!---2CCC#"84Ь;;;8;=!111HHH=6;"""WWW?8!000HHH%8?WWW&Y!!!/4LLL84???$T'(((%%%GGG0%?GGG $$$  /XXX\\\>>U>>>O-WWW<sssEEEWWW!!!888\\\PPPgggDDD4\\\="AAANNN@U0[[[)) =%2LLL\\\=6"""-WWW0?nnnHHHWWW&Y!!!/___4%4/$T(((%%%GGG) $$$ /XXX]]]U>>^^^}}}@III4>>>OWWW?@@YYY?WWW!!!8884EEE4="AAAnnn4rrrppp=0!8=,"""XXXSSSeeeUhhh <&Y!!!/___8)%/$T(((%%%GGG8 $$$ /<\\\ I K6'''U2> ***000MMMMMM'8 ...2EEE{{{EEE24 6666NNN1+ 6 84 /// OOOJJJ6/66648 H'+6///6+'-'''>2U%U%&62><!!! 2*"%%"*2 >$$$ #1{{{JJJ%% 1*NNN##1~~~III%@0%<1#> 2MMMEEE+*2 >((('''262̩)262<9P'ܣ!!!>""" '? +++4PPPooo ?/4 :546 44+6 444 1/rrr))iii===???L///夈000???P4777^^^ ccc ___ 666deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_guide.aura000066400000000000000000000016761512772601700301010ustar00rootroot00000000000000☄️ MMMaaa```DDD+++F<<>>47MMMCCC"PPP555999 PPP???MMM\\\CCC ???0-CCCYYY...Hjjj 0&+***D===|||?)) >>QQQYYY9;)ZZZ0QQQ [[[IIIDDD///4EEE6%1JJJ?ZZZ ?5CCC GGG<9!"5?.QQQ0!888=%!0PPP>>> {{{,WWW 8 >===,,,Illl*4+)))BdddEEE< U! -CCCWWW???"RRR5559,QQQ???CCC<<<0QQQyyyTTTGGGUUU <.;;;,DDD@@@===DDD ?U? ,DDD+++H???TTT[[[)=(MMM^^^  deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_left.aura000066400000000000000000000022421512772601700277240ustar00rootroot00000000000000☄️ !!! $$$ > QC '''U +++000MMMMMMMMM...555...2EEE}}}@EEE111((( ===]]]OOO///OOO@"'333ooo !!!333OOOIII'???000@*** ===I###OOO@fff 555ę@<<<***===>>>***UUU%@OOO"*MMM2 !!! BBB;;;@@@% 1NNN222$$$ :$)=5%1222>>>4 ***UUU%MMM2>555;;;62@(((===T"""?%8P... &ppp #OOOIII...???000@+++ ===OOO///QQQ-@OOO555...333EEE@DDD((( MMMNNNMMM///(((2U2T?I6 """ ### !!! """ deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_lt.aura000066400000000000000000000021571512772601700274160ustar00rootroot00000000000000☄️ + %*:::777s!!!  _#6L"""!!! Lg000SSSOOO]]]VVVQQQ>>>@ }7{333OOOnnnzzz\\\PPPHHHGGG 888PPP$kkkQQQRRRSSSFFFQQQSSSwww$UPPPIII EEEEEEOOOWWWttt?EEEMMMQQQCCCKKKNNN;sss%0SSS<<<  F&&&OOOOOO4 4JJJ111i |PPP%-\\\-" <<i!!! o Onnn'222%%% 6  deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_misc1.aura000066400000000000000000000010461512772601700300070ustar00rootroot00000000000000☄️ $$$***%%% U<1!!! 111Ì***SSSPPPU"SSS(((1OOOU91<6QQQ1<" ;,"<<9; ;9<-5666 5--5 5-<9; ;9<<"6,; ;,6"<16 6QQQ11*1U"@"#222 ,1<-<1###  2deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_right.aura000066400000000000000000000021231512772601700301050ustar00rootroot00000000000000☄️ !!! ### > I K6'''U2> ***000MMMMMM'8 ...2EEE{{{EEE24%%%666NNN1PPPXXX=== 8 /// OOOJJJ2"""nnn 888J+6///U ???J$$$'''26%Ƙ///!!! 2MMM*%)))333^^^ $$$ #NNN{{{JJJ%ܼ000ccc #NNN~~~III%5='> 2MMMEEE%0*** U)333]]] &&&'''26&%...M///%###4 >>>M+++666/// OOOJJJ# ppp 666666+///OOO6OOO<<< ...2EEE~~~*2)%%% 'MMMNNN' """'''2#2 L@?P6 ###""" ### !!! deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_rt.aura000066400000000000000000000021701512772601700274170ustar00rootroot00000000000000☄️ +&&&--- - !6) f !!! h T ,,,?6! )))NNNWWW\\\OOOSSS [IIINNNLLL\\\XXXPPP///m&IIISSSOOOVVVfffVVVRRR555  IIIpppnnnTTT4:::  GGGPPPRRRSSS?DDD(0PPPAAAQQQKKKYYYbbb!OOO$$$ 777HHHxxxU%OOO)$*+8###HHH-%,4PPP+++Z999MMMEEE%!QQQ.  @@@TTT%QQQBBBTTT<;;;QQQPPPKKK0***"9QQQURRRRRR<<<>>>OOOp-9O<<< W  j$$$ ==='66 deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_start.aura000066400000000000000000000012361512772601700301310ustar00rootroot00000000000000☄️ + +/ lel0--- BBBTTT@@@ ???QQQ>>> + 5+* 5QQQQQQ'7+6,,,KKKPPP5ze.TTT)%U% 888qd. )) = m&5LLL5&z+8 4@@@)&4 @+ @@@>>>#+4&1+ BBBTTT@@@'=+3zq:+ ++deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_up.aura000066400000000000000000000015771512772601700274300ustar00rootroot00000000000000☄️ :::^^^ aaa 1===@@@K---333111-@@@I4vvv"""̛)7%555:&4 4 54& 6 ***PPPooo ?/4 Jܢ!!!=>.:'$$$'''2˳2>!!! 2MMMEEEOOO *"2?>$$$ #NNN{{{JJJ0% 1*NNN##1~~~III%%<1#>2MMMEEENNN%%*2 >&&&'''2%%&62<'''M'///%6+'$ +++666/// 6OOOJJJ6/6664  +///OOO6+  ...2EEE~~~*2) 'MMM1' """ 2#2>6P??L6 !!!""" ### ### deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/switch_x.aura000066400000000000000000000014671512772601700272510ustar00rootroot00000000000000☄️ 6%%% ### TR  = :%%%???XXX\\\U>>7>>>OWWWVVV%<XXX!!!888\\\%www %="AAAMMM4yyy 1488KKK8=44=6,"""-?8448?-&Y!!!/___48))84/$T'(((%%%GGG?? $$$ /XXX\\\>>^^^@U===!!!N!!!)))WWWWWW{{{cccFFFWWW(((!!!=\\\KKK= "AAAMMM%7884uuu48@@@888%ttt$GGG4= XXXVVV-XXX!!!Y!!!???___40/$.(((%%%GGGU ### /YYY]]]U -,,Jg-\8)Q! ,,J !8Hk! ,%= \"18)戡 T1%%= +- PYD@8I3 P- X5Xf-f+4q111 -<o ;0)-)0; UUUddd )-<) eee - /[[[-< y666ݷ*3v4(((vvv洈888> )$.___༈9." =-)= (K""".1@=*1.!!!RĻH...9'7* P% ,,,lllkkk>')))-@@@z@@@BBBBBB@@@>***-deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_guide.aura000066400000000000000000000014231512772601700275460ustar00rootroot00000000000000☄️ =}4= 7 /2R&.&ese@_m`&0&R2HFHQL7<̓z6;KFCAC;121}@V@x=www贻pGGnnCΕrqr4&&&볇y",N$ks/+)))ڿLO))) 55+0+, +/جxƣ ˾/+@sstBEj^ӣxk_HK030?:ĽƟ!xzEA15170@/ɝwi2A0&2R868{z{w:9:5/ E deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_lb.aura000066400000000000000000000010751512772601700270510ustar00rootroot00000000000000☄️   &UgCp $$$52 HQ"""UU;,)))***<;4@yyy(***+(@|||VVV7;(++(9;(++(ƾ9 ?(++7;,,*U@<s   deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_left.aura000066400000000000000000000017041512772601700274050ustar00rootroot00000000000000☄️ ڽyu! 555ZZZ222 ƱF ---PPPJJJ EB!!!777___;&&&!!!B"  mmm<hhh$$$).!!!---1".yyy-www+"'''(+++:::-KKK +!!!#@@@v .- r@@@@<-!,$,Uhhh????999,$JJJ&<&)(70 &<& !!!70 &<& $17))&<&7$, ?@ 0/&,$',!$8 ?0 $!$3OOO//((( $>33% 3*" $$ $$0320=4<deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_lt.aura000066400000000000000000000015571512772601700271000ustar00rootroot00000000000000☄️ 4UU;555ZZZ_(KKK;Ƭ VFFF;%;@O0>>>wwwSSSf777^^^www f___uuu%f---%888UZ,,,^^^%4333P,,,7%04%UĪ– 7=== 7222,=== $9:EEE7555 d ,%"(BBBB; >WWW(,444~U/L$( C?<adeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_misc1.aura000066400000000000000000000014631512772601700274710ustar00rootroot00000000000000☄️   6$$$).-*"""+A , G iAAA,0c ;OOO```TTT@RRR,RRR```NNN<???```LLLoooqqqJJJ)___&'''TTTUUUMMM8&SSS88UUU!&&&$SSSKKK ||| $&&&'''TTTVVVHHHGGG3fffVVV!&&&'''AAA```III,@(-___@@@?QQQTTT(,!PPPBkCCC@0?mE"""$3F+#/-= %+!!!"""6deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_rb.aura000066400000000000000000000011001512772601700270440ustar00rootroot00000000000000☄️  o{W, Q!!!444n>***YYY,;,Q+(~~~(,( ++(;%(++(<;(++( ?(+)))WWW,,;(+@444@UU@&s U<   deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_right.aura000066400000000000000000000017051512772601700275710ustar00rootroot00000000000000☄️ ھcw" 111TTT555 >!ƹJ ---KKKPPP((( FB!!!777;U___%%%.69.hhhUnnn ) "---"+www-444"1 )))-+++)))(r333)-㛈 @@@sYYY$,??999,$JJJ<```<)(70 &<33% 3*" $$ $$0320=4<deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_rt.aura000066400000000000000000000015771512772601700271100ustar00rootroot00000000000000☄️ 4ŝUUUƛ? ;cO;www@___Wf²\\\~~~% ?3f"%|||999f¬^^^ VVVzzz7Z```4%4%7---P74%7,,,4WWW0... ''',k444o!!!;Į:::8$( .9 2\\\;-Q©f RRR;­1I~,))) +1U/3 ($LaGdeathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_start.aura000066400000000000000000000006451512772601700276130ustar00rootroot00000000000000☄️ =}4= 7 /2R***45+&2///TTT !6;)))SSS=AAA @ 4&&&uuu/+YYYU, ,4,+/4!/+@ PPP ***!!+2&6  6&2+;+5// E deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/gamepad/xbox_up.aura000066400000000000000000000016001512772601700270720ustar00rootroot00000000000000☄️ ھ)))$@@@w@@@@@@@@@@@@z///!+++jjjkkk+++ƱF --- EB!!!777U.1!!!J9666)!!!----1^^^߽97= )))vvv嵈xxx444 r2= 2t[[[<<!,ZZZeee- ddd <- WWW;Z@T<U3U=tԟ0c@¥OeU=T<S\ ͕!d9'X;f,T<MT<>'j'=Ci>T<U=fI Ȏ'my/ܤ'ȎgI U=T<uS Ζ#['l'9tR ;'ӌA';1-fI&'o>'&fIQ<T<U'?H'T<X] ;e:'L`'Ζ"*V=vf'V=U3T<Z@u;Wy ;Z@.T;U=^ m<'U=S;T<ZU<jK vT 2T<YT;T< deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/glow.aura000066400000000000000000000007121512772601700247630ustar00rootroot00000000000000☄️   1(?@ABDEFHIJLMNPQRSTUWXYZ[\]^_`abcdefghij4)=2';0%9. 7,! 5 3(<1&:/$-"6  *)(&%$"! # * >  !"#$%&'()*+,-./0123456789:;<= >3(<1&:/$8-" 6+  ?4)=2';0%9.# 7,! !7 #.9%0;'2=)4?  +6 "-88-" 6+  ?4)=2';0%9.# 7,! !7 #.9%0;'2==2';0%9.# 7,! !7 #.99.# 7,! , !! , !! , !7 #.99.# 7,! !7 #.9%0;'2==2';0%9.# 7,! !7 #.9%0;'2=)4?  +6 "-88-" 6+  ?4)=2';0%9.# 7,! !7 #.9%0;'2=)4?  +6 "-8$/:&1<(3>  >3(<1&:/$8-" 6+  ?4)=2';0%9.# 7,! U *5  !"$%&()*+,-/02346789:;=>?@ABDEFHIJLMNPQRSTUWXYZ[\]^_`abcdefghij4)=2';0%9. 7,! 5 3(<1&:/$-"6  *)(&%$"! # * >deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/line_arrow.aura000066400000000000000000000116231512772601700261570ustar00rootroot00000000000000☄️ /b,'6)V}]8\v.2 #4, LNjjKMN;LD>$͆ &5,Es2уfBFJI%%hԁ;"E9,?isY8FGH999999E@c| 4&! 6]rMPPP63<@C###  ### # #B?DDDE_05 -V~ωkGGGB;>A7@7,7,77,7777,EEEEg # %Os}kfffR>!!!!!!!!!!<:qqqEYpsO%(,!Eg݀ceeeK*5*55555**5*5*55*5*55850qqq@Zv(,<`zeN5973JXi_%,4Wwڂk}}}T363>3>3>3>3>3>3>3>3>3>3>3>3>3>3>3>3>43(3>3>3/-DgW-#,6NliYK.231&(/Je N6!, )Eb~eH8!*101<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1<1:#+;Oi'E4,"ZuyaI.ccc%+,-:'OOO)2>ay8Z<' ,%6Pkv\F,aaa+$(:):/:/:/:/:/:/:/:/:/:/:/:/:/:/:$/:/:/:$:/:/:/:/:/$:/:$/:/:/$/:/:/$/$/:/:$& .H]u !,#0Fp\G8{{{'+"%6{{{'6F<#3,(=UhWE8ttt(+-$-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8-8"ooo+9FZkW>),"6J0M<2(                                            +1@P]gN7', %/BVK5& ++6+6+6++6+6+6+6+6 6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+6+ +6+6+6+6+6+6+6+6+6/?JY^E0 ,(8LD8)   ?  ?? ?  ? ??   ?  ?     ? ? ? ? ? ? ?  ?    ? ?  ?  ?  ?   ? ? 1AJUW=)9 ,('0@/'44)4)4))4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4))4)4)4)4)4)4)44)4)4)4)4)4)4)4)4)4)4)4)4)4)4)4)/=BHD' (,(7-' FFF"07=9)*! 3', 2'2'22'2'2'2'2'2'2'2'2'2'2''2'2'2'2'2'2'2'22'2'2'2'2'2'2'2''2'22'2'2'22'2'2'2'2'2'2'2'22'2'2'2'2'22'BBB'+5.! , $,#90 ;;;;;0www '.% <(  (,79   0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0% 0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%0%{{{!$5(577#9 9999999999999,#.##.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.#.##.#.#.#.#.#.#.#                                                      7,deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/loading.aura000066400000000000000000000033761512772601700254410ustar00rootroot00000000000000☄️ 2" fffwww:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:ooo:.:.:.:.6:.:6.6:.6:.:.6:.:6.6:.6:.:.6U6:6:.:6.6:.:6'6:6:.:6'6:.:6'6:6'6:.6'6:.:6:6'6:.:6:.:6:6:.:6:.:6:6:.:6:.:6'6:6:.:6'6:.:6'6:6'6:.:6:.:6:6'6:.:6:.:6:6:.:.:6:.:6:6:.:6:.:6:6:.:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:6.:6.:.:6.:6.:6:.:6.6:.:6.6:6.:6'6:.:6'6:6.:.:.:6'6.:6'6:6'6.:6:.:6:6'6:6:.:6:6:6:.:6:6:6'6:.:6'6:6'6:6:.:6'6:6'6:6:.:6:6:6:.:.:6:6:6:.:6:6'6:6:.:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:.:6:.:.:.:.:6.:6.6:.:.:6.:.:6.6:.:.:6'6.:.:6:.6:6'6:.:6:.6:6'6:.:6'6:6.6:6:.:6'6:6'6:6:.:6:6'6:6:.:6:6:6'6:.:6:6:6'6:.:6'6:6:6:.:6'6:6'6:6:.:6:6'6:6:.:6:6:6:.:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:6:.:.:.:.:.:.:.:.:.:.:.:.:6:.6:.:.:.6:.6'6:6'6:6:6:6'6:6'6:6:6:6'6:6:6:6:6:deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/logo.aura000066400000000000000000000073711512772601700247630ustar00rootroot00000000000000☄️ ' &VVV??@?U!?????????!U0!0?0??!?0!???!?0!???!?0!???0!!00!0??!0!0?0??!0!0?0??!0!0?0??0?0?!0???0!!00!0??0!!00!0??0!!00!0??!0!0!0!?0!0??0?0?!0???0?0?!0???0?0?!0???!00!00!0??!0!0!0!?0!0??!0!0!0!?0!0??!0!0!0!?0!0?00??0!??!00!00!0??!00!00!0??!00!00!0??0?0AAA00??0!?00??0!?00??0!?BBB?!?1""?0?0?0?0?0?0?"1?!?1""?!??!?"!?'U"'!?'"'!?''!?''U0,,,'"' 0+++ ''0 '1"'0 ''  '"' : : '5"'  '"'  '': : '' : :'5' : : '5"' : : '"' :U: ' :+: '5' :+: '5' :+: '6 :+U:66 :+:66 :++ 656 :+: '56 : '66 : '66' : '6556' : '56 : 66 : 66' : '656' : '656' :'6' :'6' :'6' '65' :+: '' :+: '5' :+: '' :+:'"' :+:6' :+:6' :+:66':+: '6' : '' : '' : '6' : 66 : 66 : 66 : 66 : '6 66 66 66 6':'':'':'':'' :'' :'' :'' :'': 6': 6': 6': 6' 6' 6' 6' 66 66 66 66 66:66:66:66:66 66 66 66 6666666666666"????????!?0!???!?0!???!?0!???0??!0!0?0??!0!0?0??!0!0?0??0?!?0????0!!00!0??0!!00!0??0!!00!0??!0!?0?0??0?0?!0???0?0?!0???0?0?!0???!?00!0!??!0!0!0!?0!0??!0!0!0!?0!0??!0!0!0!?0!0??0!!0?0??!00!00!0??!00!00!0??!00!00!0??0!0!0!0!?0!0?00??0!?00??0!?00??0!?0!0!00!0??0?0?0?0?0?00??0!0??!??!??!??0?'!?''!?''!?''?!?''0 ''0 ''0 ''0 ''  ''  ''  ''  '': : '': : '': : '': : '' :+: '' :+: '' :+: '' :+: '6 :+: 6"6 :+:66 :+:66 :+:6"6 : 6"6 : '66 : '66 : '66 : '6"6 : 66 : 66 : 65'6' '65"16' :'"6' :'6' :'5' : '"5' :+: '' :+: '' :+: '5' :+: '' :+: '' :+: '' :+: '6 :++ '' : 6"' : '6"' : 66' : '65"6' :'65"6' : '6' : '6' '65"6' '656' 66' 66''65"' '"55' '':'' :'' : '"5' : '5"' :'"': 66':'6' : '65"' : '' 66' :'65"' : 6"5' 656 66'66' 656' 656:66:66:6"5"6: 656 66 66 66' 66666666'66666???!?0!?????0!0?0??!?0!???0!!00!??!0!0?0?0?0?0?!0?0?0!!00!0??!0!00!?0??0?0?!0???0!?!?!??!0!0!0!?0!0??0!00!??!00!00!0?0??!000??0!??!?0?0?0"??1"?!??5'!?''!?''0 ''0 ''  ''  '': : '': : '' :+: '' :+: '6 :+:66 :+:66 : '66 : '66 : 66 : 66' :'6' :'' :+: '' :+: '' :+:6' :+:6' : '' : '6 : 66 : 66 66 6':'':'' :'' :'': 6': 6' 6': 66 66 6"6 : '"6:'66 :6"6' 6"6' '6"6'6"6""6"deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/rip.aura000066400000000000000000000025151512772601700246100ustar00rootroot00000000000000☄️  #DRN-  [ $ =  s'6R;Q|0Lu#9E #   t=Un`x &zB  h;TKe/Ks0J/<  3'<\Oh f7YY$:} MG`o/Lt{UP (<+ M  [(iԢy; "<0! 0j A[{p(v1LuM</; 1 $+=]<-Ipӓ5S0M-Eld2NK~!/I%q2O*Cg@ƭ=hc :Z  \ F`o`7Wle/&9 =<xx~:9:nnr jjp]_d### ?qi deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/touch_change.aura000066400000000000000000000103701512772601700264430ustar00rootroot00000000000000☄️ (([6Q;)T;CT<XS<`S<UT<AS;&U?H6T9:T;pT;T<~,S;S<hR<0L3N:.T;,4qP d n ›\c nN Z@4,T;}T<3_?Q;!T;{T<W>oN p Ș_m jK V=S<S;lP<f3R:FS;4lL wŊyy)zrgIT<S;T<+f3T;WT;X?b ˆ^߭G]kyœ͕!l\ V>T<T<:UUS<I\Br ŊҜ,_^id¡07Й(njVk Y?T<Q9-R<7S< r lT!0-T@¢j|Wk7MʐV= S="S9S<V>m Й'fm4IA44¢kRm7͕  f U<T;T< \ES<zU<] Ċg7_942ۣ0֟/ќ.;ڢ013=g7ʑByV T<T<mR;BT<hI k`N_73̗-({$s"o!r"x$'Ɠ,2Л.̗-ި7g7ȍ^`DS<S;1N:S;V>tƋO7c92(joS\D`N(oaC^L(XAiN_t"|%v#'֢7j7߮HƋio ,S<|U*S:>,pO Ŋқ+p<1&~^{iHxlTU@YCyVe,&ު?oΖ#VhI T<Q<2T;pU=q 5[rM52(` |`{vil Œ+T7Wƌm ,T;kZ< S<\AOp&31j!pK@~^*8ny˒^V=S;U?S=/,vS HrS4ۣ0'l0ǼUzaS7P=J7@0e]Of]JlP*[7ۨ?ƋpO ,Q;)T;H,g y^^@4١0(]ÿp_zlo6@1(cfKaHoSd|%}%u#r}`o!͘-5oE5x,T;pT;s?{\r52+m)_qRD(K9fT-bLgMyZ~^x]&}W|%Ԟ/4n75y,>?07.;|U+x]&(z[gMaJeS+H6M? k~Zk&+35{5 ,T;sS;mV=x| 2Ι.o!|] tu#}%0eqTbId)14yo@5v,S;mT<_U=qjhw:;0kW.}d2z$+*w#}]rY(y]i%*25m$)n T<S;]R<I,f 5]qA2'aHZTIG9aHgq"dmQo^:½\(١04GrVƋib ,T<FT<+,tR ެE7U3*%g_Le]O@0K8O<aS5y'ۣ04\7ڦ<ƋnN T<T;)[6S<\A^p7_tQj!Ɠ,tMrף58Vu#`jOYB^L(oaCbP*^FoSi(14\k7ݫCo U<S<|??S<B,fHj_7ߩ:͘-Л.2ݤ11y$r"o!s"{$̗-25B-7Xȍ^_CS<Q;0\ET;uU<] Ŋ~mbn@41ۣ0֟/ќ.;2.Fm7ʐFzV T<T;iQ;S<V=m k_dS:A44lX$7UɏZf /T;P5 T<:S<Y?m ŊzLp7UC¢k^ n7ݫBǍFf V>T<Q9S;KS;V=h ŊoЙ'[ridi07!TΕ"` U=S<T:.UUT:LT;4}Y O߮H]?7œ9ݫC̓]tR /S;S:/3S;@T<T<t ~"5i"zo _C,S;S<-S<!T;}T</lM rțl0hJ /S<T;pT8_?S:IT;,zV k vœki wT W>,T;S;:??\ET:7*7 @ģ77>77nnn¿ħAAA77--7 )7nnnģqqq73)*67--73")7FFFŒ(9)537--7#)7ZZZ (ư7')"#7-o76.)#7dddƬ7=)Û67X76)='73ƦFFF7)Û67d,76)727)ě67 7:)7FFF‹97#5)":7>-o7©")7! (Ȱ37'*)57,73)*#7,7)*37B76.).'73nnn8 FFF7)<67B7+)"+7nnn www!7=)+7X7*) =/752-ɳ 7=*5 )7> B7:=.6@7PPP9ʯ(((73/+'.='7 76:>7___ʪ777'#7 7:'+/7 87'#7Bd7+/37 wwwFFF77>7 &(̲đ,7 * 2 ?4 Ȗ!37Mz75,208̣ !37>735 ˜, 7ѩ&<(;  757  )0;-92 7o7* 0˚ ( 92*77FFF29;% ?(<&77ZZZ)(-,(&778-U 8277&;?48-U-8&7773%884,37X77 77 7%%7 7887 7<<7 77 7 ?? 7 7, 84?,77(, 84?;7 7ZZZ(88),7 >7!)0;? 0)‡7>7FFF <& 7d7 ɘҲ<&7d79??927 7©98<2,* 7-739(9 ,>773&• ,>75,)(̰>7d7¥7 * 28( ‚377 7:#'7% ,767B7#'+/37ddd)*7/7-B7''+/7UUU< 7'67->7"7FFF ,&761<*7>-M7:=78(www7.*5)17X7#. ) '7nnn4FFF7) #7-B75)7 ?%7777)*77+1)7,&;ȭ(((7)"+7-76)17FFFnnnˆȲ9 7)Ś67>77 )ĝ7827)ě7Bo73#= )7nnn FFF7) =#37-73#=<)*7 %7)<.#37--73#.)57,9!(((7=)›#37- >7)7 nnn2‰U;97)"7>55>76) 7* 8qqq75) 67 5>7:<)=:7 8FFF7=67>5>7é*67¹48ç7+77å7, 37'#7 7+7 www7#7-d73'>7*nnn&ʍ‡7'7-776:7)ė&AAA7'7B737nnn77B>7!Җ7>M->73,, 7>X-7  7> --B7z 7->7, deathkiller-jazz2-native-2a7ccef/Content/Animations/UI/touch_fire.aura000066400000000000000000000155001512772601700261430ustar00rootroot00000000000000☄️ FF?HE H GF'F6H DK L LFG -FEG `F uF G G F G oGWG >KKUU71.Ÿ8sJ XF iAC F ½h(7AA22oHJJWW7(.Ÿ8z ?E b3?E tG |.88;MM22+ԥH4700.Ÿ(i F EKE HF r.³x;7CC++o88SS7##.% F E'C? ^ 2.SS7`@@))%%$$*55RR7 .J{M 8F vUHE mG Q .xJJ7+FF**%%$""!! $RR7gI JG F <F:G &r.CC7FF$$!!(""5PP7P=.R G mDH F sF S .=xVV7JJ*$*$!!|oj&u""RR7;++Z{I JD :3F6G zj.JJUU7* ?u_K G *o($o::WW7T G F fD UE \G G ...KK// !!( 8|od(+&Zu %%̤XHH7< F H HF w Y }::::.. ;|uoj$%P69 &_""66SS78O E CHE 0G .t|%%y..s,,m''koj*TPG = 9 1$&2o?%%oDDXXo@55.p G xC 3E JF >pv++HӘK 6B &(&j$$''ͩRR7.I JF G"? F f b#w _;( EEXXo''.V FEG F { |#,,s++&Z3$''66XX7@11.Hk Fi?GF .66s,,M64 T|$%%$*SS7Jx F UE G .$$;;11]##>&Po""''$!!?(?""$II7.H HF6 W .11FF88n))K &G d;$$''%%!!;3;8?337"".O C() ] .66II@@00&= 4 +&,o?$$?;|&d_Z*j&3 &&RRYY%%U G9E U k>>NNCC66oK P69 &B *3 |d%&= &6&$3KKUUWW((Z F LE W n.CCRRII<<3oj*T1u.*8&,FF$7(([ F N n.07NNDD 83o**u3|u*&*34/7  .07PP ( ;,6&Ř&K %*d%&%3@@/7  .07`%%$$""$ (d&͘&(1K 1&_34/7 F NE L e.<>x(7@'.Z_ # F D%?FVF t.Īxx7 x.Zb H 3G .UG >F G X .5hx7x'.}T F yHE9F m.95x5:.eM G sH UE +F G &Y pQ # F G SDC FxG ^ .J}V # F EF:EFGF G Y |ȡZh8t% F E |F 1UDG XG F G  ` sؠƟ(8n%# 9F sF IH TE%F KG r %Ɵ(8 F F eF B V eؚ O񦕡~Ul6nҙjĚ,V+T)S/3f)SP+T. S f<4<4n<"4kϜœC2*Ty+Q3L)SL+T= R fˡy~5Z'{zžE-W*So'T*U*U9*S[L eKѢjL] [~ťĤtՓMœF,W*Ti)R(V*S N d!V4~E=kFТ*<*]/œJ*T)Sb3f??*Tl+SH/7^K4)Ԣ`j,›@~+T*SK(T?*S )z&=r Y4f+T+S*T'!X 73 \ gz*8Ң3w1q !)ɤ<=lЛz>{,V+S*S<)S/+Tk bEa<+?)5|ɤ.l*b(^)])^z&zd",V+S*T:3f*S5*S;u eY<E)5|ɤ,f#SCl?0,f>)ɧb)ȡW<6 O+S+Q*T*Ts+T/]?C),f0Cl$$0,f>)ȥ_)zT)ǣ^)#<\!w4f*RE%T+SKL<[ ),f0Cl$&$0,f>)ƥ4<".A*Ri??'W*S W9)_<;j6I)S*U(Qy bZ)X,f0Cl$&$0,f>)ħ6~̣-i0Cl&$04-%)ã<[t Y4e'R9*SUB dCb;)<"x׏? \7l+TN#<1);ݟ2t'[Dn&69)<2:,#<1)9נ0o&X@h&+0)<2:,+T#<1)9נ1q0Aj&60)<2:,+T+SL=x d7ϣ.l%VCm5W/M,G .l >)¥ c-\4M)1q'ZGsqӖl S.[*P (W bY<)8 + 8)Y)_<9iG*S3f.Q +SuE fDb7ϣ.l/8\5W/ D.9z-2J*S(Q'R**S8o cNakH\>9נ)%`+P+T6k ^µZ| a,S[LM)֢ᢖV.ui Y5i+T+S*P$+TXB e¬Hc)=>L$)z.&9z,W+S[(P UU*SN*SjF c¡1<9f[k)Ģ ~UIrԖK˜6l+Su,RT*T^+SJ æ?\<Ҟyz3ki ^9r+U+T+S(T%_*SV*TF [Ī;Ѩ<̝Z[/ld W;u+S*T.*U)S>*S6i O eԩ<ŞykZm 5YDrPSneRGw>h+H0 - , +I ?-.49~28^OW~f#0P5 V.,~.$8) !,m, ~RAmUdԙ=f'A 2 ,-C. $+ C, 9A3UGvVvQ>g+H. ,- d- 33,N,(FtSƦxwPw ROy2h+T#En_|A|cFpe*=g+ En|c,=h+ En.|c,g*{38kNpw\"w#: - - f' ,R- ~DrXHK6r/`(M{ uKf0CkP!Y%~=&7)*2=&7Dlk,48¡^rG @k- -+ +, p!8 ¦l\<}4o.]&kG&:ZAgHsKwgF3&+2 3&+:ZGq[&p.6Ik^$ 5 - C$$+*, 8^XKSG84^'Jv&5U<`@e?c&75U<`@e&:]Ly$v178{Zw}D 4X,x( ?,E, vO h[&4= .>&4Z%m-~48w-'A 7U V9X$&:1N=<>X$&Mg4BiU#k,48Hwg.Gw!8 ,,,6@ lYM6q/\&Hs8Xʼ&q^0M2O2O0L&9,1<2&r_?cP!e*z2Srwd* Dr2 * .S 3iiYQa(M{ <_0L<,&2<,&:\Kx_'u078av`& Am- $$.,EtX?db:{3g*R"@eawL.&3N2O11N.&%<14.&:ZGqZ%o.6lI[! 8],33. , u5YW $RD85l-W$DlB_'Ia2<11N>X$&:<11N>X$&Mg4BiU#j,~48Mpwt;v-K- +a?*,]$< W 2ovQ9i6 c)N} :\ʽ&q^3R7W7W1ʼ&9? 1 &r_?cP!e*z2{`qwg-wR!8 - -=- CnM h_>4p.^'Jv5U<_?d>b&5=4>&$Kx_'u078GleZ Hx ,-, -,7\ LvJ8|3o.]&kG&9Y@fGqgF.& ?).&Gq[&q06{^qx?v3V-o- (, uAW p_=4q/`(Mz &Oj5CjP!W$Y%:&$Oj4Cj/*!:$Oj4CkV%m1;(YHj[! ?2 .E-W, ~?jXHqDQ(6x2g+T"DlhM~gEnS"d*l,g*?!-#R"d)1 ?!-EnS#g+{4:^q{ |C ;a- ,- +*- (C T ^dL5s/a(U#P!Q$R"W$e*v151!U#/5e* #1!U#/5e* 78{TowZ Q,`*,g, RBo lSF8 t0i+b(!d)l, 6 t0#!d)1 < t0#!d)1 <Imwe, 5Y. V-, 0.',n-LPXOpRE86{3t0r/v1{399{3t0) 499{3t0) 49Ilws9f Gw!7 ,k& >- e@kW a@E55 7 #  # OnJ\" ,J. V, ,>+- '@ OXigD9{SoUc) V?h4 +y+72/NR ½b@K;֡Um wi/ fJ{&? -*$U,X2TV ¯Xe[FH_ovd* —,I- - ,=(- sv4X+ «WžAV@RG5j"Ge ›1R. V,G$$ - 3,e6ZVv§KXRV@{1]lwo6w #.MR+[( U,N,v6[T ¢{Bi@UEPeġHWg{Uwe, P&A . 3,+^- e;bTcçPҝ}D fO)F. - -,%,V,7\L~ Ĭ,ww VHx-. V* .*,>,&? @jVvŞwww Ĝ, i4D݅Bu:W+ ©"|=D}>g3mA 7g3E|=- Z6s9D^.( `0ր@s9Z6  i?~>l6K- 'l6D}>k@ L&ׁ@g3 $7D4M& R1~>Df3 $7DI, j5Er8&  - t9D pC!߆B؁@a: b;؁@ h3D_/ _/i4 /?ޅBV3 W4ޅB,)  xH$Cv: w;Ch> P'Da0 b0DQ( 3P' DY, k5Dh> |J%D`0 l5BR1 D _/D) DY, R)Eh> 4D }K%,O' D /C`/ .C) ?, w; j4ޅB ޅBi4 pC!DV3 W4D pC!  _/ ! _/BA&j@L i4؁@ ۃA5n6k5 o7 L-\7 ].=ETr9&}>J |= b:6؁@q|=O؁@2ق@ IL O' =b1^D݄A^|=!> K ?a0 U33yi4 sE" U*ADG=m:,? $L Z6D ?f=x;I>L$ DK- ߅B}K%(ۂ@H=;T=+F $ R)ր@ U*x;wGA<{<>@L$ |=`0 $f2k4IDF=;V=1J L + E(  ڃAL&$׀?I>;s=DDLL W+}> :].w;uGA<:L / ݅B mA  k5 ]-0BG>;x=QJLL$ D)D  L&ր?I?;=`D L L/ `/ {= :Z,yVLL ۃAk@  w; `/-BG=;=jL(L E  ڃAL& ݄AI>:=yJ L& oC!ڂA LU*?G@;=E0LL R)}> Y,ր?GA;rFB< LW+|=mFB;<-5 l6~ L`0x;dEC<;@EL o7Y /$ym=C;;}B8< a0v: :W+>qFB;tFB; w;CBG<;f>/$L {=Z- /p7z:M= J݅BmA  $u: yLL s9g3 Lk5׀?jDD;f;,@ $ X+}> f2݄AG@;O< D + D(  $E t:AޅAH=x:4=K |=^. Lp8؁?lDE;T;"@  R)ր@ c1߆BG@w:4<E DK- LE x;LބAH s9l6`0ׁ?wECk;); @ Z6D ?\.*؁?H>?߀8<H ~?c1sE" ^.DDFMހ:9B lA C 3i4THc݄A!{6= ؂@a0 oB ,x;JG2x; }:J N& L&,D1Br8C ?n6 u: ?~>k5 S2EZ6  a:Fs8x;Ik5ր? ր@w;Y,^/ ߆Ba0 b0߆B oB!D\7 ]8EpC! j4ޅB ޅBh3 w; x;~? 8"D. b0C/ M&DM& O'EuF# ].Df= oC!E3 e2G* Z6D_/ m6B R1B+ f=h>D+ e2D/ P'E =Da07! c1D; M&Dw;, CwG# ?ޅB \7, ]8ޅBL j4D a0, b0Ei4 oB!߆B[Z6, \7؁@pC! k5En6! q8Di4 S2?Ca0  a01|=L- N&؂@(c1 $1O' lA ~?Dl6K- K-m6D4 Z6s9Dր@^.(  .؂@s9Z6  -|=D}>g3mA 7g3~>D{=R)- + X+s9݅BZ-k@  >, i4{=JW+ D)].<ۃAEڂAJz<j-Em6z<ڂAӘ<{=].0 oC!R)a0l6o7l6 j8$  ! ! deathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/000077500000000000000000000000001512772601700240555ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/electro.aura000066400000000000000000000001711512772601700263630ustar00rootroot00000000000000☄️ x <<<deathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/electro_mask.aura000066400000000000000000000001541512772601700273770ustar00rootroot00000000000000☄️  15151515151515151515151515151deathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/pepper.aura000066400000000000000000000001161512772601700262200ustar00rootroot00000000000000☄️ 22EBECEBdeathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/pepper_upgraded.aura000066400000000000000000000002701512772601700300740ustar00rootroot00000000000000☄️  <<<9U9991 5 68 6"619"9?$"$?$"$?9"96"6:::6"685$59?95deathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/thunderbolt1.aura000066400000000000000000000051201512772601700273400ustar00rootroot00000000000000☄️ kJJJ HHH  - - - -- - - - - - - - - - - - - - - -- - - - - - - - - - - --- - -- - -- - -   -- -- - -- - - - -- - --- -- - - - -- - -- -- - - - - - - - -- - - - --- -- - -----$$$U--------------III--------------------------------------------------------------------------------------------<<-<--<<<<-<<<<<<<<<-<<<<<<-<-<<---<<-<<<-<<<<-<--<<-<<<<<-<-<<-<<-<<-<<<<<-<<<-<<<<<<<<LLL) ) ))%%%)) )) )) ) ))) ) ) ))) ) )) )) )) )) ) ) ) )))) )) ) ) ) ) ) )) ))) ) )) ) ) ) ) )) )) ) )) ) )) ))) ) ) )) ) )) ) ) ) ) ) )) ) )) )) )) )) )) ) ))) )) ))))) )) ) ) ) ) )) )) ) ) ) ) ) ) ))) ) ) ) )) )) ) )) ) )) ) ) ) ) )))) )) ) ) )))) )deathkiller-jazz2-native-2a7ccef/Content/Animations/Weapon/thunderbolt2.aura000066400000000000000000000050121512772601700273410ustar00rootroot00000000000000☄️ kHHH- -  - - - - -- - -- - - --- - - ---- - - - - - - -- - -- - - - - - - -- - ---- -- - - - - ---- - --- -- -- - - - - -- -- -- - - -- -  - - - ---  - - - - - -- - -- -- -- - - - - -- --- - - $$$U--------<------------------------------------------------------------------------------------<-<<<<<-<<<-<-<<<<--<<<<<<<--<<<-<<<-<<<--<<-<<<<-<<--<<-<<-<<<-<<<-<<-<<<<<<<<<<<<<-LLL%%%)) ) ) ) ))) )) ) ) ))) ) )) )) )) )) )) ) ) ))) )) ) )) ) ) ) ) ) ))) ) )) ) ) ) ) )) ) )) )) ) ) ))) ) ) ))) ) ) ) ) ) ) ) )) )) )) ))) ) ) ) )) ) )) )) ) )) ) ))) )) ) ) ))) )) )) )) )) ) ) ) ) ))) ) )) ) ) ) ) )) ) )) ) )) )) )) ) ) ) )))) ) )deathkiller-jazz2-native-2a7ccef/Content/Metadata/000077500000000000000000000000001512772601700222425ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/000077500000000000000000000000001512772601700231505ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Bilsy.res000066400000000000000000000017541512772601700247540ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Bilsy/idle.aura", "States": [ 0 ], "FrameRate": 7 }, "Appear": { "Path": "Bilsy/appear.aura", "States": [ 1073741824 ], "FrameRate": 6 }, "Vanish": { "Path": "Bilsy/vanish.aura", "States": [ 1073741825 ], "FrameRate": 6 }, "ThrowFireball": { "Path": "Bilsy/throw_fireball.aura", "States": [ 1073741826 ], "FrameRate": 4, "FrameCount": 15 }, "ThrowFireballEnd": { "Path": "Bilsy/throw_fireball.aura", "States": [ 1073741827 ], "FrameRate": 17, "FrameOffset": 15 }, "Fireball": { "Path": "Bilsy/bullet_fireball.aura", "FrameRate": 20, "States": [ 1073741828 ] } }, "Sounds": { "ThrowFireball": { "Paths": [ "Bilsy/throw_fireball.wav" ] }, "FireStart": { "Paths": [ "Bilsy/fire_start.wav" ] }, "Appear": { "Paths": [ "Bilsy/appear_2.wav" ] }, "Disappear": { "Paths": [ "Bilsy/appear_1.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/BilsyXmas.res000066400000000000000000000020361512772601700255770ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Bilsy/xmas_idle.aura", "States": [ 0 ], "FrameRate": 7 }, "Appear": { "Path": "Bilsy/xmas_appear.aura", "States": [ 1073741824 ], "FrameRate": 6 }, "Vanish": { "Path": "Bilsy/xmas_vanish.aura", "States": [ 1073741825 ], "FrameRate": 6 }, "ThrowFireball": { "Path": "Bilsy/xmas_throw_fireball.aura", "States": [ 1073741826 ], "FrameRate": 4, "FrameCount": 15 }, "ThrowFireballEnd": { "Path": "Bilsy/xmas_throw_fireball.aura", "States": [ 1073741827 ], "FrameRate": 17, "FrameOffset": 15 }, "Fireball": { "Path": "Bilsy/xmas_bullet_fireball.aura", "FrameRate": 20, "States": [ 1073741828 ] } }, "Sounds": { "ThrowFireball": { "Paths": [ "Bilsy/xmas_throw_fireball.wav" ] }, "FireStart": { "Paths": [ "Bilsy/xmas_fire_start.wav" ] }, "Appear": { "Paths": [ "Bilsy/xmas_appear_2.wav" ] }, "Disappear": { "Paths": [ "Bilsy/xmas_appear_1.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Bolly.res000066400000000000000000000014131512772601700247430ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Top": { "Path": "Bolly/top.aura", "States": [ 0 ], "FrameRate": 5 }, "Bottom": { "Path": "Bolly/bottom.aura", "States": [ 1 ] }, "Turret": { "Path": "Bolly/turret.aura", "States": [ 2 ], "FrameRate": 0 }, "Mace": { "Path": "Bolly/mace.aura", "States": [ 3 ] }, "MaceChain": { "Path": "Bolly/mace_chain.aura", "States": [ 4 ] }, "Rocket": { "Path": "Bolly/rocket.aura", "States": [ 5 ] } }, "Sounds": { "Noise": { "Paths": [ "Bolly/noise.wav" ] }, "PreAttack": { "Paths": [ "Bolly/missile_1.wav" ] }, "Attack": { "Paths": [ "Bolly/missile_2.wav" ] }, "PostAttack": { "Paths": [ "Bolly/missile_3.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Bubba.res000066400000000000000000000032561512772601700247040ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Bubba/hop.aura", "States": [ 0 ], "FrameCount": 1 }, "JumpStart": { "Path": "Bubba/hop.aura", "States": [ 1073741825 ], "FrameCount": 4, "FrameRate": 11 }, "Jump": { "Path": "Bubba/hop.aura", "States": [ 4 ], "Flags": 1, "FrameOffset": 4, "FrameCount": 3, "FrameRate": 13 }, "Fall": { "Path": "Bubba/jump.aura", "States": [ 8 ], "Flags": 1, "FrameCount": 3, "FrameRate": 12 }, "FallEnd": { "Path": "Bubba/jump.aura", "States": [ 1073741826 ], "FrameOffset": 3, "FrameCount": 6, "FrameRate": 14 }, "SpewFireball": { "Path": "Bubba/spew_fireball.aura", "FrameCount": 12, "FrameRate": 12, "States": [ 16 ] }, "SpewFireballEnd": { "Path": "Bubba/spew_fireball.aura", "FrameOffset": 12, "FrameCount": 4, "FrameRate": 16, "States": [ 1073741828 ] }, "Corpse": { "Path": "Bubba/corpse.aura", "States": [ 1073741839 ], "FrameRate": 5 }, "TornadoStart": { "Path": "Bubba/tornado_start.aura", "States": [ 1073741830 ] }, "Tornado": { "Path": "Bubba/tornado.aura", "States": [ 1073741831 ] }, "TornadoEnd": { "Path": "Bubba/tornado_end.aura", "States": [ 1073741832 ] }, "TornadoFall": { "Path": "Bubba/jump_fall.aura", "States": [ 1073741833 ] }, "Fireball": { "Path": "Bubba/fireball.aura", "FrameRate": 24, "States": [ 1073741834 ] } }, "Sounds": { "Jump": { "Paths": [ "Bubba/hop_1.wav", "Bubba/hop_2.wav" ] }, "Sneeze": { "Paths": [ "Bubba/sneeze.wav" ] }, "Tornado": { "Paths": [ "Bubba/tornado.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Devan.res000066400000000000000000000065651512772601700247340ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Devan/idle.aura", "States": [ 0 ] }, "Run": { "Path": "Devan/run.aura", "States": [ 2 ] }, "WarpIn": { "Path": "Devan/warp_in.aura", "States": [ 1073741843 ] }, "WarpOut": { "Path": "Devan/warp_out.aura", "States": [ 1073741844 ] }, "RunEnd": { "Path": "Devan/run_end.aura", "FrameRate": 20, "States": [ 1073741824 ] }, "ShootStart": { "Path": "Devan/shoot.aura", "FrameCount": 3, "FrameRate": 20, "States": [ 10 ] }, "ShootInProgress": { "Path": "Devan/shoot.aura", "FrameOffset": 3, "FrameCount": 3, "FrameRate": 40, "States": [ 11 ] }, "ShootEnd": { "Path": "Devan/shoot.aura", "FrameOffset": 6, "FrameCount": 1, "FrameRate": 50, "States": [ 12 ] }, "ShootEnd2": { "Path": "Devan/shoot.aura", "FrameOffset": 7, "FrameRate": 28, "States": [ 13 ] }, "CrouchStart": { "Path": "Devan/crouch.aura", "FrameCount": 4, "FrameRate": 20, "States": [ 14 ] }, "CrouchInProgress": { "Path": "Devan/crouch.aura", "FrameOffset": 4, "FrameCount": 1, "States": [ 15 ] }, "CrouchEnd": { "Path": "Devan/crouch.aura", "FrameOffset": 5, "FrameCount": 4, "FrameRate": 20, "States": [ 16 ] }, "JumpStart": { "Path": "Devan/jump.aura", "FrameCount": 14, "FrameRate": 20, "States": [ 17 ] }, "JumpInProgress": { "Path": "Devan/jump.aura", "FrameOffset": 14, "FrameCount": 1, "States": [ 18 ] }, "JumpEnd": { "Path": "Devan/jump.aura", "FrameOffset": 15, "FrameCount": 11, "FrameRate": 20, "States": [ 19 ] }, "JumpEnd2": { "Path": "Devan/jump_end.aura", "States": [ 20 ] }, "Freefall": { "Path": "Devan/freefall.aura", "FrameRate": 30, "States": [ 65536 ] }, "DisarmedStart": { "Path": "Devan/disarmed.aura", "States": [ 22 ] }, "DisarmedGunDecor": { "Path": "Devan/disarmed_gun.aura", "States": [ 23 ] }, "DisorientedStart": { "Path": "Devan/disoriented_start.aura", "FrameRate": 30, "States": [ 24 ] }, "Disoriented": { "Path": "Devan/disoriented.aura", "States": [ 25 ] }, "DisorientedWarpOut": { "Path": "Devan/disoriented_warp_out.aura", "States": [ 26 ] }, "DevanBullet": { "Path": "Devan/bullet.aura", "States": [ 27 ] }, "DemonFly": { "Path": "Devan/demon_fly.aura", "FrameRate": 5, "States": [ 30 ] }, "DemonTransformStart": { "Path": "Devan/demon_transform_start.aura", "FrameRate": 2, "States": [ 31 ] }, "DemonTransformEnd": { "Path": "Devan/demon_transform_end.aura", "FrameRate": 4, "States": [ 32 ] }, "DemonTurn": { "Path": "Devan/demon_turn.aura", "FrameRate": 16, "States": [ 33 ] }, "DemonSpewFireball": { "Path": "Devan/demon_spew_fireball.aura", "FrameCount": 9, "States": [ 34 ] }, "DemonSpewFireballEnd": { "Path": "Devan/demon_spew_fireball.aura", "FrameOffset": 9, "FrameRate": 20, "States": [ 35 ] }, "DemonFireball": { "Path": "Devan/demon_fireball.aura", "States": [ 36 ] } }, "Sounds": { "Shoot": { "Paths": [ "Devan/shoot.wav" ] }, "SpitFireball": { "Paths": [ "Devan/spit_fireball.wav" ] }, "Flap": { "Paths": [ "Devan/flap.wav" ] }, "WallPoof": { "Paths": [ "Weapon/wall_poof.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/DevanRemote.res000066400000000000000000000005141512772601700260740ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Devan/remote_idle.aura", "States": [ 0 ] }, "WarpOut": { "Path": "Devan/remote_warp_out.aura", "FrameRate": 10, "States": [ 1073741844 ] } }, "Sounds": { "WarpOut": { "Paths": [ "Common/warp_out.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Queen.res000066400000000000000000000023301512772601700247360ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 20, 30 ], "Animations": { "Idle": { "Path": "Queen/idle.aura", "States": [ 0 ], "FrameRate": 6 }, "Fall": { "Path": "Queen/fall.aura", "States": [ 8 ], "FrameRate": 6 }, "Scream": { "Path": "Queen/scream.aura", "States": [ 1073741824 ], "FrameRate": 3 }, "Stomp": { "Path": "Queen/stomp.aura", "FrameRate": 10, "FrameCount": 6, "States": [ 1073741825 ] }, "StompEnd": { "Path": "Queen/stomp.aura", "FrameRate": 20, "FrameOffset": 6, "States": [ 1073741830 ] }, "Backstep": { "Path": "Queen/backstep.aura", "States": [ 1073741826 ], "FrameRate": 8 }, "Ledge": { "Path": "Queen/ledge.aura", "States": [ 1073741827 ], "FrameRate": 4 }, "LedgeRecover": { "Path": "Queen/ledge_recover.aura", "States": [ 1073741828 ], "FrameRate": 6 }, "Brick": { "Path": "Queen/brick.aura", "States": [ 1073741829 ] } }, "Sounds": { "Scream": { "Paths": [ "Queen/scream.wav" ] }, "Spring": { "Paths": [ "Queen/spring.wav" ] }, "Stomp": { "Paths": [ "Common/gunsm1.wav" ] }, "BrickFalling": { "Paths": [ "Common/holyflut.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Robot.res000066400000000000000000000036601512772601700247550ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Robot/idle.aura", "States": [ 0 ] }, "Run": { "Path": "Robot/run.aura", "States": [ 2 ] }, "AttackStart": { "Path": "Robot/attack_start.aura", "States": [ 1073741824 ] }, "Attack": { "Path": "Robot/attack.aura", "States": [ 1073741825 ], "FrameRate": 12 }, "AttackEnd": { "Path": "Robot/attack_end.aura", "States": [ 1073741826 ] }, "Copter": { "Path": "Robot/copter.aura", "States": [ 1073741828 ] }, "CopterEnd": { "Path": "Robot/copter_end.aura", "States": [ 1073741829 ] }, "SpikeBall": { "Path": "Robot/spike_ball.aura", "States": [ 1073741827 ] }, "Shrapnel1": { "Path": "Robot/shrapnel_1.aura", "States": [ 100 ] }, "Shrapnel2": { "Path": "Robot/shrapnel_2.aura", "States": [ 101 ] }, "Shrapnel3": { "Path": "Robot/shrapnel_3.aura", "States": [ 102 ] }, "Shrapnel4": { "Path": "Robot/shrapnel_4.aura", "States": [ 103 ] }, "Shrapnel5": { "Path": "Robot/shrapnel_5.aura", "States": [ 104 ] }, "Shrapnel6": { "Path": "Robot/shrapnel_6.aura", "States": [ 105 ] }, "Shrapnel7": { "Path": "Robot/shrapnel_7.aura", "States": [ 106 ] }, "Shrapnel8": { "Path": "Robot/shrapnel_8.aura", "States": [ 107 ] }, "Shrapnel9": { "Path": "Robot/shrapnel_9.aura", "States": [ 108 ] } }, "Sounds": { "Run": { "Paths": [ "Robot/walk_1.wav", "Robot/walk_2.wav", "Robot/walk_3.wav" ] }, "AttackStart": { "Paths": [ "Robot/attack_start.wav" ] }, "AttackShutter": { "Paths": [ "Robot/attack_start_shutter.wav" ] }, "Attack": { "Paths": [ "Robot/attack.wav" ] }, "AttackEnd": { "Paths": [ "Robot/attack_end.wav" ] }, "Shrapnel": { "Paths": [ "Robot/shrapnel_1.wav", "Robot/shrapnel_2.wav", "Robot/shrapnel_3.wav", "Robot/shrapnel_4.wav", "Robot/shrapnel_5.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/TurtleBoss.res000066400000000000000000000017121512772601700257720ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "TurtleBoss/idle.aura", "States": [ 0 ], "FrameRate": 8 }, "Walk": { "Path": "TurtleBoss/walk.aura", "States": [ 1 ], "FrameRate": 5 }, "AttackStart": { "Path": "TurtleBoss/attack_start.aura", "States": [ 1073741824 ], "FrameRate": 6, "FrameCount": 15 }, "AttackStart2": { "Path": "TurtleBoss/attack_start.aura", "States": [ 1073741825 ], "FrameRate": 10, "FrameOffset": 15, "FrameCount": 4 }, "AttackEnd": { "Path": "TurtleBoss/attack_end.aura", "States": [ 1073741826 ], "FrameRate": 7 }, "Mace": { "Path": "TurtleBoss/mace.aura", "States": [ 1073741827 ], "FrameRate": 12 } }, "Sounds": { "AttackStart": { "Paths": [ "TurtleBoss/attack_start.wav" ] }, "AttackEnd": { "Paths": [ "TurtleBoss/attack_end.wav" ] }, "Mace": { "Paths": [ "TurtleBoss/mace.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/TurtleBossShell.res000066400000000000000000000012301512772601700267550ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Idle": { "Path": "TurtleBoss/shell.aura" } }, "Sounds": { "ImpactGround": { "Paths": [ "Object/shell_noise_1.wav", "Object/shell_noise_2.wav", "Object/shell_noise_3.wav", "Object/shell_noise_4.wav", "Object/shell_noise_5.wav", "Object/shell_noise_6.wav", "Object/shell_noise_7.wav", "Object/shell_noise_8.wav" ] }, "Fly": { "Paths": [ "Common/swish_1.wav", "Common/swish_2.wav", "Common/swish_3.wav", "Common/swish_4.wav", "Common/swish_5.wav", "Common/swish_6.wav", "Common/swish_7.wav", "Common/swish_8.wav" ] }, "ImpactShell": { "Paths": [ "Turtle/shell_collide.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Tweedle.res000066400000000000000000000001751512772601700252570ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Idle": { "Path": "Tweedle/idle.aura", "States": [ 0 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Boss/Uterus.res000066400000000000000000000013121512772601700251470ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Open": { "Path": "Uterus/idle.aura", "States": [ 0 ], "FrameRate": 8 }, "Closing": { "Path": "Uterus/closed_start.aura", "States": [ 1073741824 ], "FrameRate": 8 }, "Closed": { "Path": "Uterus/closed_idle.aura", "States": [ 1073741825 ], "FrameRate": 8 }, "Opening": { "Path": "Uterus/closed_end.aura", "States": [ 1073741826 ], "FrameRate": 8 }, "Shield": { "Path": "Uterus/shield.aura", "States": [ 1073741827 ], "FrameRate": 8 } }, "Sounds": { "Closing": { "Paths": [ "Uterus/closed_start.wav" ] }, "Opening": { "Paths": [ "Uterus/closed_end.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/000077500000000000000000000000001512772601700234365ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/Gem.res000066400000000000000000000002151512772601700246570ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/gem.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/Lab.res000066400000000000000000000002151512772601700246450ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/lab.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/Log.res000066400000000000000000000002151512772601700246700ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/log.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/Rope.res000066400000000000000000000002161512772601700250550ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/rope.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/Stone.res000066400000000000000000000002171512772601700252410ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/stone.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/StoneRed.res000066400000000000000000000002231512772601700256710ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/stone_red.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Bridge/Vine.res000066400000000000000000000002161512772601700250510ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Piece": { "Path": "Bridge/vine.aura", "FrameRate": 0 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/000077500000000000000000000000001512772601700244635ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoBouncer.res000066400000000000000000000003401512772601700274020ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_bouncer.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_bouncer_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoElectro.res000066400000000000000000000003401512772601700274020ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_electro.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_electro_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoFreezer.res000066400000000000000000000003401512772601700274070ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_freezer.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_freezer_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoPepper.res000066400000000000000000000003361512772601700272450ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_pepper.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_pepper_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoRF.res000066400000000000000000000003261512772601700263200ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_rf.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_rf_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoSeeker.res000066400000000000000000000003361512772601700272300ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_seeker.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_seeker_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoTNT.res000066400000000000000000000001761512772601700264610ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Object/tnt.aura", "States": [ 0, 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoThunderbolt.res000066400000000000000000000002131512772601700302760ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_thunderbolt.aura", "States": [ 0, 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/AmmoToaster.res000066400000000000000000000003401512772601700274260ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Ammo": { "Path": "Pickup/ammo_toaster.aura", "States": [ 0 ] }, "AmmoUpgraded": { "Path": "Pickup/ammo_toaster_upgraded.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/Carrot.res000066400000000000000000000001541512772601700264300ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Carrot": { "Path": "Pickup/carrot.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/CarrotFly.res000066400000000000000000000001601512772601700271000ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Carrot": { "Path": "Pickup/carrot_fly.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/CarrotFull.res000066400000000000000000000002261512772601700272530ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Carrot": { "Path": "Pickup/carrot_full.aura", "FrameRate": 8 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/CarrotInvincible.res000066400000000000000000000002371512772601700304350ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Carrot": { "Path": "Pickup/carrot_invincibility.aura", "FrameRate": 8 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/Coins.res000066400000000000000000000004151512772601700262510ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "CoinSilver": { "Path": "Pickup/coin_silver.aura", "FrameRate": 5, "States": [ 0 ] }, "CoinGold": { "Path": "Pickup/coin_gold.aura", "FrameRate": 5, "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FastFireJazz.res000066400000000000000000000001661512772601700275430ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "FastFire": { "Path": "Pickup/fast_fire_jazz.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FastFireLori.res000066400000000000000000000001661512772601700275320ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "FastFire": { "Path": "Pickup/fast_fire_lori.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FastFireSpaz.res000066400000000000000000000001661512772601700275420ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "FastFire": { "Path": "Pickup/fast_fire_spaz.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodApple.res000066400000000000000000000001561512772601700270510ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_apple.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodBanana.res000066400000000000000000000001571512772601700271710ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_banana.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodBurger.res000066400000000000000000000001571512772601700272370ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_burger.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCake.res000066400000000000000000000001561512772601700266530ustar00rootroot00000000000000 { "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_cake.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCandy.res000066400000000000000000000001561512772601700270460ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_candy.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCheese.res000066400000000000000000000001571512772601700272050ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_cheese.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCherry.res000066400000000000000000000001571512772601700272450ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_cherry.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodChickenLeg.res000066400000000000000000000001601512772601700277770ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_chicken.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodChips.res000066400000000000000000000001561512772601700270560ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_chips.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodChocolate.res000066400000000000000000000001621512772601700277060ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_chocolate.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCoke.res000066400000000000000000000001551512772601700266700ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_cola.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCucumber.res000066400000000000000000000001611512772601700275510ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_cucumber.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodCupcake.res000066400000000000000000000001601512772601700273560ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_cupcake.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodDonut.res000066400000000000000000000001561512772601700271010ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_donut.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodEggplant.res000066400000000000000000000001611512772601700275450ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_eggplant.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodFries.res000066400000000000000000000001561512772601700270600ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_fries.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodGrapes.res000066400000000000000000000001571512772601700272320ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_grapes.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodHam.res000066400000000000000000000001541512772601700265130ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_ham.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodHotDog.res000066400000000000000000000001571512772601700271750ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_hotdog.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodIceCream.res000066400000000000000000000001621512772601700274550ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_ice_cream.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodLemon.res000066400000000000000000000001561512772601700270620ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_lemon.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodLettuce.res000066400000000000000000000001601512772601700274100ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_lettuce.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodLime.res000066400000000000000000000001551512772601700266750ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_lime.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodMilk.res000066400000000000000000000001551512772601700267030ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_milk.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodOrange.res000066400000000000000000000001571512772601700272240ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_orange.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodPeach.res000066400000000000000000000001561512772601700270300ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_peach.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodPear.res000066400000000000000000000001551512772601700266760ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_pear.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodPepsi.res000066400000000000000000000001551512772601700270670ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_soda.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodPie.res000066400000000000000000000001541512772601700265230ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_pie.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodPizza.res000066400000000000000000000001561512772601700271050ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_pizza.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodPretzel.res000066400000000000000000000001601512772601700274300ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_pretzel.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodSandwich.res000066400000000000000000000001611512772601700275440ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_sandwich.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodStrawberry.res000066400000000000000000000001631512772601700301520ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_strawberry.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodTaco.res000066400000000000000000000001551512772601700266750ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_taco.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodThing.res000066400000000000000000000001561512772601700270610ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_thing.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/FoodWaterMelon.res000066400000000000000000000001631512772601700300630ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Food": { "Path": "Pickup/food_watermelon.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/Gems.res000066400000000000000000000006631512772601700260760ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "GemRed": { "Path": "Pickup/gem.aura", "PaletteOffset": 128, "States": [ 0 ] }, "GemGreen": { "Path": "Pickup/gem.aura", "PaletteOffset": 256, "States": [ 1 ] }, "GemBlue": { "Path": "Pickup/gem.aura", "PaletteOffset": 384, "States": [ 2 ] }, "GemPurple": { "Path": "Pickup/gem.aura", "PaletteOffset": 512, "States": [ 3 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/OneUp.res000066400000000000000000000001501512772601700262200ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "OneUp": { "Path": "Pickup/1up.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Collectible/Stopwatch.res000066400000000000000000000001621512772601700271510ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Stopwatch": { "Path": "Pickup/stopwatch.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Common/000077500000000000000000000000001512772601700234725ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Common/AmbientBubbles.res000066400000000000000000000001741512772601700270650ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "AmbientBubbles": { "Path": "Common/water_bubble_2.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Common/AmbientSound.res000066400000000000000000000003741512772601700266010ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Sounds": { "AmbientWind": { "Paths": [ "Common/wind.wav" ] }, "AmbientFire": { "Paths": [ "Common/ambient_fire.wav" ] }, "AmbientScienceNoise": { "Paths": [ "Common/science_noise.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Common/Explosions.res000066400000000000000000000035021512772601700263500ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Tiny": { "Path": "Common/explosion_tiny.aura", "FrameRate": 13, "States": [ 0 ] }, "TinyBlue": { "Path": "Common/explosion_freezer_maybe.aura", "FrameRate": 13, "States": [ 1 ] }, "TinyDark": { "Path": "Common/explosion_tiny_black.aura", "FrameRate": 13, "States": [ 2 ] }, "Small": { "Path": "Common/explosion_upwards.aura", "FrameRate": 8, "States": [ 3 ] }, "SmallDark": { "Path": "Common/explosion_small.aura", "FrameRate": 8, "States": [ 4 ] }, "Large": { "Path": "Common/explosion_large.aura", "FrameRate": 8, "States": [ 5 ] }, "SmokeBrown": { "Path": "Common/smoke_circling_brown.aura", "FrameRate": 8, "States": [ 6 ] }, "SmokeGray": { "Path": "Common/smoke_circling_gray.aura", "FrameRate": 8, "States": [ 7 ] }, "SmokeWhite": { "Path": "Common/smoke_circling_white.aura", "FrameRate": 8, "States": [ 8 ] }, "SmokePoof": { "Path": "Common/smoke_poof.aura", "FrameRate": 8, "States": [ 9 ] }, "WaterSplash": { "Path": "Common/water_splash.aura", "FrameRate": 8, "States": [ 10 ] }, "Pepper": { "Path": "Common/explosion_pepper.aura", "FrameRate": 20, "States": [ 11 ] }, "RF": { "Path": "Common/explosion_rf.aura", "FrameRate": 6, "States": [ 12, 13 ] }, "Generator": { "Path": "Common/generator.aura", "States": [ 14 ] }, "IceShrapnel1": { "Path": "Common/ice_break_shrapnel_1.aura", "States": [ 15 ] }, "IceShrapnel2": { "Path": "Common/ice_break_shrapnel_2.aura", "States": [ 16 ] }, "IceShrapnel3": { "Path": "Common/ice_break_shrapnel_3.aura", "States": [ 17 ] }, "IceShrapnel4": { "Path": "Common/ice_break_shrapnel_4.aura", "States": [ 18 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Common/Scenery.res000066400000000000000000000013701512772601700256160ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Snow": { "Path": "Common/snow.aura", "States": [ 0 ] }, "Rain": { "Path": "Common/rain.aura", "States": [ 1 ] } }, "Sounds": { "SceneryDestruct": { "Paths": [ "Common/scenery_destruct.wav" ] }, "SceneryCollapse": { "Paths": [ "Common/scenery_collapse.wav" ] }, "Splat": { "Paths": [ "Common/splat_1.wav", "Common/splat_2.wav", "Common/splat_3.wav", "Common/splat_4.wav", "Common/splat_5.wav", "Common/splat_6.wav" ] }, "Bomb": { "Paths": [ "Common/bomb.wav" ] }, "WaterSplash": { "Paths": [ "Common/water_splash.wav" ] }, "IceBreak": { "Paths": [ "Common/ice_break.wav" ] }, "SugarRush": { "Paths": [ "Common/sugar_rush.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/000077500000000000000000000000001512772601700233175ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Bat.res000066400000000000000000000013551512772601700245440ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Bat/resting.aura", "States": [ 0 ] }, "Walk": { "Path": "Bat/idle.aura", "States": [ 1 ] }, "Death": { "Path": "Bat/roost.aura", "States": [ 1073741839 ] }, "Takeoff": { "Path": "Bat/takeoff_1.aura", "FrameRate": 10, "States": [ 1073741824 ] }, "TakeoffEnd": { "Path": "Bat/takeoff_2.aura", "FrameRate": 20, "States": [ 1073741825 ] }, "Roost": { "Path": "Bat/roost.aura", "FrameRate": 22, "States": [ 1073741826 ] }, "IdleAnim": { "Path": "Bat/takeoff_1.aura", "FrameRate": 6, "States": [ 1073741827 ] } }, "Sounds": { "Noise": { "Paths": [ "Bat/noise.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Bee.res000066400000000000000000000005021512772601700245220ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Bee/bee.aura", "FrameRate": 30, "States": [ 0 ] }, "Turn": { "Path": "Bee/bee_turn.aura", "FrameRate": 30, "States": [ 1073741840 ] } }, "Sounds": { "Noise": { "Paths": [ "Bee/noise.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Cat.res000066400000000000000000000007511512772601700245440ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Doggy/xmas_walk.aura", "FrameRate": 6, "States": [ 1 ] }, "Attack": { "Path": "Doggy/xmas_attack.aura", "States": [ 1325400065 ] } }, "Sounds": { "Attack": { "Paths": [ "Doggy/xmas_attack.wav" ] }, "Noise": { "Paths": [ "Doggy/xmas_noise.wav" ] }, "Woof": { "Paths": [ "Doggy/xmas_woof_1.wav", "Doggy/xmas_woof_2.wav", "Doggy/xmas_woof_3.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Caterpillar.res000066400000000000000000000014731512772601700263010ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Caterpillar/idle.aura", "States": [ 0 ] }, "InhaleStart": { "Path": "Caterpillar/inhale_start.aura", "States": [ 1 ] }, "Inhale": { "Path": "Caterpillar/inhale.aura", "States": [ 2 ] }, "ExhaleStart": { "Path": "Caterpillar/exhale_start.aura", "States": [ 3 ] }, "Exhale": { "Path": "Caterpillar/exhale.aura", "States": [ 4 ] }, "ExhaleEnd": { "Path": "Caterpillar/exhale.aura", "FrameOffset": 2, "States": [ 5 ] }, "Disoriented": { "Path": "Caterpillar/disoriented.aura", "States": [ 6 ] }, "Smoke": { "Path": "Caterpillar/smoke.aura", "FrameRate": 5, "States": [ 7 ] } }, "Sounds": { "Dizzy": { "Paths": [ "Caterpillar/dizzy.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Crab.res000066400000000000000000000011671512772601700247060ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Crab/walk.aura", "FrameRate": 6, "States": [ 1 ] }, "Fall": { "Path": "Crab/fall.aura", "FrameRate": 14, "States": [ 8 ] }, "FallEnd": { "Path": "Crab/fall_end.aura", "FrameRate": 30, "States": [ 1073741826 ] } }, "Sounds": { "Noise": { "Paths": [ "Crab/noise_1.wav", "Crab/noise_2.wav", "Crab/noise_3.wav", "Crab/noise_4.wav", "Crab/noise_5.wav", "Crab/noise_6.wav", "Crab/noise_7.wav", "Crab/noise_8.wav" ] }, "Step": { "Paths": [ "Crab/step_1.wav", "Crab/step_2.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Demon.res000066400000000000000000000007321512772601700250760ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Demon/idle.aura", "FrameRate": 3, "States": [ 0 ] }, "Attack": { "Path": "Demon/attack.aura", "FrameRate": 5, "States": [ 1073741824 ] }, "AttackStart": { "Path": "Demon/attack_start.aura", "FrameRate": 12, "States": [ 1073741825 ] }, "AttackEnd": { "Path": "Demon/attack_end.aura", "FrameRate": 12, "States": [ 1073741826 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Doggy.res000066400000000000000000000007321512772601700251050ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Doggy/walk.aura", "FrameRate": 6, "States": [ 1 ] }, "Attack": { "Path": "Doggy/attack.aura", "FrameRate": 10, "States": [ 1325400065 ] } }, "Sounds": { "Attack": { "Paths": [ "Doggy/attack.wav" ] }, "Noise": { "Paths": [ "Doggy/noise.wav" ] }, "Woof": { "Paths": [ "Doggy/woof_1.wav", "Doggy/woof_2.wav", "Doggy/woof_3.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Dragon.res000066400000000000000000000011641512772601700252460ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Dragon/idle.aura", "States": [ 0 ] }, "AttackStart": { "Path": "Dragon/attack.aura", "FrameCount": 5, "FrameRate": 12, "States": [ 1073741824 ] }, "Attack": { "Path": "Dragon/attack.aura", "FrameOffset": 5, "FrameCount": 2, "FrameRate": 30, "States": [ 1073741825 ] }, "AttackEnd": { "Path": "Dragon/attack.aura", "FrameOffset": 7, "FrameRate": 50, "States": [ 1073741826 ] }, "Turn": { "Path": "Dragon/turn.aura", "FrameRate": 20, "States": [ 1073741840 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Dragonfly.res000066400000000000000000000003561512772601700257630ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Dragonfly/idle.aura", "FrameRate": 20, "States": [ 0 ] } }, "Sounds": { "Noise": { "Paths": [ "Dragonfly/noise.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/FatChick.res000066400000000000000000000006061512772601700255100ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "FatChick/walk.aura", "FrameRate": 4, "States": [ 1 ] }, "Attack": { "Path": "FatChick/attack.aura", "FrameRate": 6, "States": [ 1325400065 ] } }, "Sounds": { "Attack": { "Paths": [ "FatChick/attack_1.wav", "FatChick/attack_2.wav", "FatChick/attack_3.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Fencer.res000066400000000000000000000005141512772601700252340ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Fencer/idle.aura", "FrameRate": 6, "States": [ 0 ] }, "Attack": { "Path": "Fencer/attack.aura", "FrameRate": 8, "States": [ 1325400065 ] } }, "Sounds": { "Attack": { "Paths": [ "Fencer/attack.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Fish.res000066400000000000000000000004001512772601700247150ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Fish/idle.aura", "FrameRate": 8, "States": [ 0 ] }, "Attack": { "Path": "Fish/attack.aura", "FrameRate": 6, "States": [ 1325400065 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Helmut.res000066400000000000000000000004041512772601700252660ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Helmut/idle.aura", "FrameRate": 6, "States": [ 0 ] }, "Walk": { "Path": "Helmut/walk.aura", "FrameRate": 6, "States": [ 1, 2, 8, 9, 10 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/LabRat.res000066400000000000000000000011361512772601700252000ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "LabRat/idle.aura", "FrameRate": 5, "States": [ 0 ] }, "Walk": { "Path": "LabRat/walk.aura", "FrameRate": 5, "States": [ 1, 2, 8, 9, 10 ] }, "Attack": { "Path": "LabRat/attack.aura", "FrameRate": 5, "States": [ 1325400065 ] } }, "Sounds": { "Noise": { "Paths": [ "LabRat/noise_3.wav", "LabRat/noise_4.wav", "LabRat/noise_5.wav" ] }, "Attack": { "Paths": [ "LabRat/attack.wav" ] }, "Idle": { "Paths": [ "LabRat/noise_1.wav", "LabRat/noise_2.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Lizard.res000066400000000000000000000003711512772601700252600ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Lizard/walk.aura", "FrameRate": 4, "States": [ 0, 1, 2, 8, 9, 10 ] } }, "Sounds": { "Noise": { "Paths": [ "Lizard/noise_4.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/LizardFloat.res000066400000000000000000000013001512772601700262370ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Lizard/copter_idle.aura", "States": [ 0 ] }, "Attack": { "Path": "Lizard/copter_attack.aura", "FrameCount": 12, "FrameRate": 6, "States": [ 1325400065 ] }, "AttackEnd": { "Path": "Lizard/copter_attack.aura", "FrameOffset": 12, "FrameRate": 40, "States": [ 1325400066 ] }, "Copter": { "Path": "Lizard/copter.aura", "FrameRate": 20, "States": [ 1 ] }, "Bomb": { "Path": "Lizard/bomb.aura", "States": [ 2 ] } }, "Sounds": { "Copter": { "Paths": [ "Object/copter.wav" ] }, "CopterPre": { "Paths": [ "Common/copter_pre.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/LizardFloatXmas.res000066400000000000000000000013541512772601700271010ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Lizard/xmas_copter_idle.aura", "States": [ 0 ] }, "Attack": { "Path": "Lizard/xmas_copter_attack.aura", "FrameCount": 12, "FrameRate": 6, "States": [ 1325400065 ] }, "AttackEnd": { "Path": "Lizard/xmas_copter_attack.aura", "FrameOffset": 12, "FrameRate": 40, "States": [ 1325400066 ] }, "Copter": { "Path": "Lizard/xmas_copter.aura", "FrameRate": 20, "States": [ 1 ] }, "Bomb": { "Path": "Lizard/xmas_bomb.aura", "FrameRate": 8, "States": [ 2 ] } }, "Sounds": { "Copter": { "Paths": [ "Object/copter.wav" ] }, "CopterPre": { "Paths": [ "Common/copter_pre.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/LizardXmas.res000066400000000000000000000004031512772601700261050ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Lizard/xmas_walk.aura", "FrameRate": 4, "States": [ 0, 1, 2, 8, 9, 10 ] } }, "Sounds": { "Noise": { "Paths": [ "Lizard/xmas_noise_4.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/MadderHatter.res000066400000000000000000000013401512772601700263740ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "MadderHatter/walk.aura", "FrameRate": 4, "States": [ 1 ] }, "AttackStart": { "Path": "MadderHatter/attack.aura", "FrameCount": 12, "FrameRate": 5, "States": [ 1073741824 ] }, "AttackEnd": { "Path": "MadderHatter/attack.aura", "FrameOffset": 12, "FrameRate": 5, "States": [ 1073741825 ] }, "Bullet": { "Path": "MadderHatter/bullet_spit.aura", "States": [ 1073741826 ] }, "Cup": { "Path": "MadderHatter/cup.aura", "States": [ 2 ] }, "Hat": { "Path": "MadderHatter/hat.aura", "States": [ 3 ] } }, "Sounds": { "Spit": { "Paths": [ "MadderHatter/spit.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Monkey.res000066400000000000000000000020361512772601700252750ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Monkey/walk.aura", "FrameRate": 5, "States": [ 1 ] }, "Jump": { "Path": "Monkey/jump.aura", "FrameRate": 5, "States": [ 4 ] }, "WalkStart": { "Path": "Monkey/walk_start.aura", "FrameRate": 17, "States": [ 1073741824 ] }, "WalkEnd": { "Path": "Monkey/walk_end.aura", "FrameRate": 17, "States": [ 1073741825 ] }, "AttackStart": { "Path": "Monkey/attack.aura", "FrameCount": 8, "FrameRate": 8, "States": [ 1073741826 ] }, "AttackEnd": { "Path": "Monkey/attack.aura", "FrameOffset": 8, "FrameRate": 10, "States": [ 1073741827 ] }, "Banana": { "Path": "Monkey/banana.aura", "States": [ 1073741828 ] }, "BananaSplat": { "Path": "Monkey/banana_splat.aura", "FrameRate": 12, "States": [ 1073741829 ] } }, "Sounds": { "BananaThrow": { "Paths": [ "Monkey/banana_throw.wav" ] }, "BananaSplat": { "Paths": [ "Monkey/banana_splat.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Rapier.res000066400000000000000000000014471512772601700252620ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Rapier/idle.aura", "FrameRate": 5, "States": [ 0 ] }, "Attack": { "Path": "Rapier/attack.aura", "FrameRate": 5, "States": [ 1073741824 ] }, "AttackStart": { "Path": "Rapier/attack_start.aura", "FrameRate": 10, "States": [ 1073741825 ] }, "AttackEnd": { "Path": "Rapier/attack_end.aura", "FrameRate": 10, "States": [ 1073741826 ] }, "AttackSwing": { "Path": "Rapier/attack_swing.aura", "FrameRate": 5, "States": [ 1073741827 ] } }, "Sounds": { "Die": { "Paths": [ "Rapier/die.wav" ] }, "Attack": { "Paths": [ "Rapier/clunk.wav" ] }, "Noise": { "Paths": [ "Rapier/noise_1.wav", "Rapier/noise_2.wav", "Rapier/noise_3.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Raven.res000066400000000000000000000006001512772601700251010ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Raven/idle.aura", "FrameRate": 8, "States": [ 0 ] }, "Turn": { "Path": "Raven/turn.aura", "States": [ 1073741840 ] }, "Attack": { "Path": "Raven/attack.aura", "States": [ 1325400065 ] } }, "Sounds": { "Attack": { "Paths": [ "Common/slip.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Skeleton.res000066400000000000000000000004571512772601700256240ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Skeleton/walk.aura", "FrameRate": 3, "States": [ 1 ] }, "Bone": { "Path": "Skeleton/bone.aura", "States": [ 2 ] }, "Skull": { "Path": "Skeleton/skull.aura", "States": [ 3 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Sparks.res000066400000000000000000000001741512772601700252770ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Idle": { "Path": "Sparks/idle.aura", "States": [ 0 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Sucker.res000066400000000000000000000012501512772601700252640ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Sucker/walk.aura", "FrameRate": 5, "States": [ 0, 1, 2, 3 ] }, "Fall": { "Path": "Sucker/fall.aura", "FrameRate": 5, "States": [ 8, 9, 10, 11 ] }, "Deflation": { "Path": "Sucker/inflated_deflate.aura", "FrameRate": 9, "States": [ 1073741824 ] } }, "Sounds": { "Deflate": { "Paths": [ "Sucker/deflate.wav" ] }, "Walk1": { "Paths": [ "Sucker/pinch_1.wav" ] }, "Walk2": { "Paths": [ "Sucker/pinch_3.wav" ] }, "Walk3": { "Paths": [ "Sucker/plop_1.wav", "Sucker/plop_2.wav", "Sucker/plop_3.wav", "Sucker/plop_4.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/SuckerFloat.res000066400000000000000000000002451512772601700262550ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Sucker/inflated.aura", "FrameRate": 5, "States": [ 0 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Turtle.res000066400000000000000000000017151512772601700253150ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Turtle/walk.aura", "FrameRate": 4, "States": [ 0, 1, 2, 8, 9, 10 ] }, "WithdrawStart": { "Path": "Turtle/turn_start.aura", "FrameRate": 7, "States": [ 20 ] }, "WithdrawStartFast": { "Path": "Turtle/turn_start.aura", "FrameRate": 20, "States": [ 21 ] }, "WithdrawInProgress": { "Path": "Turtle/turn_start.aura", "FrameOffset": 6, "FrameCount": 1, "States": [ 22 ] }, "WithdrawEnd": { "Path": "Turtle/turn_end.aura", "FrameRate": 7, "States": [ 23 ] }, "Attack": { "Path": "Turtle/attack.aura", "FrameRate": 7, "States": [ 1325400065 ] } }, "Sounds": { "Withdraw": { "Paths": [ "Turtle/turn_start.wav" ] }, "WithdrawEnd": { "Paths": [ "Turtle/turn_end.wav" ] }, "Attack": { "Paths": [ "Turtle/attack_neck.wav" ] }, "Attack2": { "Paths": [ "Turtle/attack_bite.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/TurtleShell.res000066400000000000000000000012341512772601700263010ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Idle": { "Path": "Turtle/shell_reverse.aura" } }, "Sounds": { "ImpactGround": { "Paths": [ "Object/shell_noise_1.wav", "Object/shell_noise_2.wav", "Object/shell_noise_3.wav", "Object/shell_noise_4.wav", "Object/shell_noise_5.wav", "Object/shell_noise_6.wav", "Object/shell_noise_7.wav", "Object/shell_noise_8.wav" ] }, "Fly": { "Paths": [ "Common/swish_1.wav", "Common/swish_2.wav", "Common/swish_3.wav", "Common/swish_4.wav", "Common/swish_5.wav", "Common/swish_6.wav", "Common/swish_7.wav", "Common/swish_8.wav" ] }, "ImpactShell": { "Paths": [ "Turtle/shell_collide.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/TurtleShellXmas.res000066400000000000000000000012461512772601700271350ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Idle": { "Path": "Turtle/xmas_shell_reverse.aura" } }, "Sounds": { "ImpactGround": { "Paths": [ "Object/shell_noise_1.wav", "Object/shell_noise_2.wav", "Object/shell_noise_3.wav", "Object/shell_noise_4.wav", "Object/shell_noise_5.wav", "Object/shell_noise_6.wav", "Object/shell_noise_7.wav", "Object/shell_noise_8.wav" ] }, "Fly": { "Paths": [ "Common/swish_1.wav", "Common/swish_2.wav", "Common/swish_3.wav", "Common/swish_4.wav", "Common/swish_5.wav", "Common/swish_6.wav", "Common/swish_7.wav", "Common/swish_8.wav" ] }, "ImpactShell": { "Paths": [ "Turtle/xmas_shell_collide.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/TurtleTough.res000066400000000000000000000002461512772601700263220ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "TurtleTough/walk.aura", "FrameRate": 5, "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/TurtleTube.res000066400000000000000000000002451512772601700261320ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "TurtleTube/idle.aura", "FrameRate": 7, "States": [ 0 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/TurtleXmas.res000066400000000000000000000017771512772601700261560ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Walk": { "Path": "Turtle/xmas_walk.aura", "FrameRate": 4, "States": [ 0, 1, 2, 8, 9, 10 ] }, "WithdrawStart": { "Path": "Turtle/xmas_turn_start.aura", "FrameRate": 7, "States": [ 20 ] }, "WithdrawStartFast": { "Path": "Turtle/xmas_turn_start.aura", "FrameRate": 20, "States": [ 21 ] }, "WithdrawInProgress": { "Path": "Turtle/xmas_turn_start.aura", "FrameOffset": 6, "FrameCount": 1, "States": [ 22 ] }, "WithdrawEnd": { "Path": "Turtle/xmas_turn_end.aura", "FrameRate": 7, "States": [ 23 ] }, "Attack": { "Path": "Turtle/xmas_attack.aura", "FrameRate": 7, "States": [ 1325400065 ] } }, "Sounds": { "Withdraw": { "Paths": [ "Turtle/xmas_turn_start.wav" ] }, "WithdrawEnd": { "Paths": [ "Turtle/xmas_turn_end.wav" ] }, "Attack": { "Paths": [ "Turtle/xmas_attack_neck.wav" ] }, "Attack2": { "Paths": [ "Turtle/xmas_attack_bite.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Enemy/Witch.res000066400000000000000000000010701512772601700251060ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Witch/idle.aura", "FrameRate": 8, "States": [ 0 ] }, "Attack": { "Path": "Witch/attack.aura", "FrameRate": 8, "States": [ 1325400065 ] }, "Die": { "Path": "Witch/die.aura", "FrameRate": 8, "States": [ 1073741839 ] }, "MagicBullet": { "Path": "Witch/bullet_magic.aura", "States": [ 1073741828 ] } }, "Sounds": { "MagicFire": { "Paths": [ "Witch/magic.wav" ] }, "Laugh": { "Paths": [ "Witch/laugh.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Interactive/000077500000000000000000000000001512772601700245175ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Interactive/PlayerFrog.res000066400000000000000000000062071512772601700273110ustar00rootroot00000000000000{ "Version": { "Target": "Jazz2² Resurrection" }, "Animations": { "Idle": { "Path": "Frog/idle.aura", "States": [ 0, 64, 128 ], "FrameRate": 8 }, "Walk": { "Path": "Frog/run.aura", "States": [ 1, 2, 3, 17, 18, 19, 129, 145 ], "FrameRate": 12 }, "Jump": { "Path": "Frog/jump_start.aura", "States": [ 4, 5, 6, 7, 262144, 262145, 262146, 262147 ], "Flags": 1, "FrameRate": 30 }, "Fall": { "Path": "Frog/fall.aura", "States": [ 8, 9, 10, 11, 65536, 65537, 65538, 65539 ], "FrameRate": 18 }, "Crouch": { "Path": "Frog/crouch.aura", "Flags": 1, "FrameRate": 40, "States": [ 32 ] }, "Shoot": { "Path": "Frog/tongue_hor.aura", "States": [ 16, 144, 48 ], "FrameRate": 8 }, "LookupShoot": { "Path": "Frog/tongue_ver.aura", "States": [ 80 ], "FrameRate": 8 }, "Hurt": { "Path": "Frog/hurt.aura", "FrameRate": 6, "States": [ 2048, 1073741839 ] }, "FallToIdle": { "Path": "Frog/fall_land.aura", "States": [ 1073741826 ], "FrameRate": 28 }, "Shield": { "Path": "Common/player_shield.aura", "States": [ 536870928 ] }, "ShieldFire": { "Path": "Common/shield_fire.aura", "States": [ 536870929 ] }, "ShieldWater": { "Path": "Common/shield_water.aura", "States": [ 536870930 ] }, "ShieldLightning": { "Path": "Common/shield_lightning.aura", "States": [ 536870931 ] }, "TransformFromJazz": { "Path": "Jazz/transform_frog.aura", "States": [ 1610612736 ], "FrameRate": 3 }, "TransformFromSpaz": { "Path": "Spaz/transform_frog.aura", "States": [ 1610612737 ], "FrameRate": 3 }, "TransformFromLori": { "Path": "Lori/transform_frog.aura", "States": [ 1610612738 ], "FrameRate": 3 }, "TransformToJazz": { "_Path": "Jazz/transform_frog_end.aura", "States": [ 1610612739 ], "FrameRate": 3 }, "TransformToSpaz": { "_Path": "Spaz/transform_frog_end.aura", "States": [ 1610612740 ], "FrameRate": 3 }, "TransformToLori": { "_Path": "Lori/transform_frog_end.aura", "States": [ 1610612741 ], "FrameRate": 3 } }, "Sounds": { "Tongue": { "Paths": [ "Frog/tongue.wav" ] }, "Transform": { "Paths": [ "Frog/transform.wav" ] }, "Jump": { "Paths": [ "Frog/noise_1.wav" ] }, "Land": { "Paths": [ "Frog/noise_3.wav", "Frog/noise_5.wav", "Frog/noise_6.wav" ] }, "Hurt": { "Paths": [ "Frog/noise_4.wav" ] }, "HurtSoft": { "Paths": [ "Frog/noise_4.wav" ] }, "PickupAmmo": { "Paths": [ "Pickup/ammo.wav" ] }, "PickupCoin": { "Paths": [ "Pickup/coin.wav" ] }, "PickupGem": { "Paths": [ "Pickup/gem.wav" ] }, "PickupOneUp": { "Paths": [ "Pickup/1up.wav" ] }, "PickupDrink": { "Paths": [ "Pickup/food_drink_1.wav", "Pickup/food_drink_2.wav", "Pickup/food_drink_3.wav", "Pickup/food_drink_4.wav" ] }, "PickupFood": { "Paths": [ "Pickup/food_edible_1.wav", "Pickup/food_edible_2.wav", "Pickup/food_edible_3.wav", "Pickup/food_edible_4.wav" ] }, "PickupMaxCarrot": { "Paths": [ "Pickup/food_edible_1.wav" ] }, "BonusWarpNotEnoughCoins": { "Paths": [ "Object/bonus_not_enough_coins.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Interactive/PlayerJazz.res000066400000000000000000000225201512772601700273260ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Jazz/idle.aura", "States": [ 0 ] }, "Walk": { "Path": "Jazz/run.aura", "FrameRate": 8, "States": [ 1, 17 ] }, "Run": { "Path": "Jazz/dash_start.aura", "FrameRate": 12, "States": [ 2, 18 ] }, "Jump": { "Path": "Jazz/jump.aura", "States": [ 4 ] }, "RunJump": { "Path": "Jazz/jump_diag.aura", "Flags": 1, "States": [ 5, 6 ] }, "Fall": { "Path": "Jazz/fall.aura", "States": [ 8 ] }, "FallDiag": { "Path": "Jazz/fall_diag.aura", "States": [ 9, 10, 11 ] }, "Freefall": { "Path": "Jazz/freefall.aura", "States": [ 65536, 65537, 65538, 65539 ] }, "Dash": { "Path": "Jazz/dash.aura", "FrameRate": 20, "States": [ 3, 19 ] }, "DashJump": { "Path": "Jazz/ball.aura", "States": [ 7 ] }, "Lookup": { "Path": "Jazz/lookup_start.aura", "Flags": 1, "FrameRate": 34, "States": [ 64 ] }, "Crouch": { "Path": "Jazz/crouch_start.aura", "Flags": 1, "FrameRate": 40, "States": [ 32 ] }, "DizzyIdle": { "Path": "Jazz/dizzy.aura", "FrameRate": 7, "States": [ 128 ] }, "DizzyWalk": { "Path": "Jazz/dizzy_walk.aura", "FrameRate": 6, "States": [ 129, 145 ] }, "Shoot": { "Path": "Jazz/shoot.aura", "States": [ 16, 144 ], "Flags": 1 }, "CrouchShoot": { "Path": "Jazz/crouch_shoot.aura", "States": [ 48 ], "Flags": 1 }, "LookupShoot": { "Path": "Jazz/shoot_ver.aura", "States": [ 80 ], "Flags": 1 }, "Hurt": { "Path": "Jazz/hurt.aura", "FrameRate": 6, "States": [ 2048 ] }, "Uppercut": { "Path": "Jazz/uppercut.aura", "States": [ 512 ] }, "Buttstomp": { "Path": "Jazz/buttstomp.aura", "States": [ 256 ] }, "Hook": { "Path": "Jazz/vine_idle.aura", "States": [ 12 ] }, "HookLookup": { "Path": "Jazz/vine_shoot_up_end.aura", "States": [ 76 ] }, "HookMove": { "Path": "Jazz/vine_walk.aura", "States": [ 13, 14, 15 ] }, "Copter": { "Path": "Jazz/copter.aura", "FrameRate": 8, "States": [ 8192, 8193, 8194, 8195 ] }, "CopterShoot": { "Path": "Jazz/copter_shoot.aura", "FrameRate": 22, "States": [ 8208, 8209, 8210, 8211 ] }, "AerialShoot": { "Path": "Jazz/fall_shoot.aura", "FrameRate": 14, "States": [ 20, 21, 22, 23, 24, 25, 26, 27, 262160, 262161, 262162, 262163 ] }, "HookShoot": { "Path": "Jazz/vine_shoot.aura", "States": [ 28, 29, 30, 31 ], "Flags": 1 }, "HookLookupShoot": { "Path": "Jazz/vine_shoot_up.aura", "States": [ 92 ], "Flags": 1 }, "RunToIdle": { "Path": "Jazz/run_stop.aura", "States": [ 1073741824 ] }, "RunToDash": { "Path": "Jazz/dash_start.aura", "States": [ 1073741825 ] }, "DashToIdle": { "Path": "Jazz/dash_stop.aura", "States": [ 1073741856 ] }, "FallToIdle": { "Path": "Jazz/fall_end.aura", "States": [ 1073741826 ] }, "ShootToIdle": { "Path": "Jazz/shoot_end.aura", "FrameRate": 20, "States": [ 1073741828 ] }, "HookShootToHook": { "Path": "Jazz/vine_shoot_end.aura", "FrameRate": 20, "States": [ 1073741829 ] }, "CopterShootToCopter": { "Path": "Jazz/copter_shoot_start.aura", "FrameRate": 27, "States": [ 1073741830 ] }, "FallShootToFall": { "Path": "Jazz/jump_shoot_end.aura", "FrameRate": 20, "States": [ 1073741872 ] }, "UppercutA": { "Path": "Jazz/uppercut_start.aura", "FrameCount": 3, "FrameRate": 20, "States": [ 1073741831 ] }, "UppercutB": { "Path": "Jazz/uppercut_start.aura", "FrameOffset": 3, "FrameRate": 20, "States": [ 1073741832 ] }, "UppercutC": { "Path": "Jazz/uppercut_end.aura", "FrameRate": 20, "States": [ 1073741833 ] }, "ButtstompStart": { "Path": "Jazz/spring.aura", "States": [ 1073741834 ] }, "ButtstompEnd": { "Path": "Jazz/buttstomp_end.aura", "States": [ 1073741858 ] }, "PoleH": { "Path": "Jazz/pole_h.aura", "FrameRate": 20, "States": [ 1073741835 ] }, "PoleHSlow": { "Path": "Jazz/pole_h.aura", "States": [ 1073741837 ] }, "PoleV": { "Path": "Jazz/pole_v.aura", "FrameRate": 20, "States": [ 1073741836 ] }, "PoleVSlow": { "Path": "Jazz/pole_v.aura", "States": [ 1073741838 ] }, "Death": { "Path": "Jazz/die.aura", "FrameRate": 3, "States": [ 1073741839 ] }, "WarpIn": { "Path": "Jazz/warp_in.aura", "States": [ 1073741843 ] }, "WarpOut": { "Path": "Jazz/warp_out.aura", "States": [ 1073741844 ] }, "WarpInFreefall": { "Path": "Jazz/warp_in_freefall.aura", "States": [ 1073741847 ] }, "WarpOutFreefall": { "Path": "Jazz/warp_out_freefall.aura", "States": [ 1073741848 ] }, "Spring": { "Path": "Jazz/spring.aura", "States": [ 262144, 262145, 262146, 262147 ] }, "Push": { "Path": "Jazz/push.aura", "FrameRate": 6, "States": [ 16384, 16385 ] }, "EndOfLevel": { "Path": "Jazz/eol.aura", "FrameRate": 3, "States": [ 1073741846 ] }, "Swim": { "Path": "Jazz/swim_right.aura", "FrameRate": 6, "States": [ 4096 ] }, "Lift": { "Path": "Jazz/lift.aura", "Flags": 1, "FrameRate": 16, "States": [ 131072 ] }, "LiftStart": { "Path": "Jazz/lift_start.aura", "FrameRate": 16, "States": [ 1073741859 ] }, "LiftEnd": { "Path": "Jazz/lift_end.aura", "States": [ 1073741860 ] }, "Ledge": { "Path": "Jazz/ledge.aura", "FrameRate": 3, "States": [ 1073741861 ] }, "Airboard": { "Path": "Jazz/airboard.aura", "FrameRate": 6, "States": [ 1024 ] }, "LedgeClimb": { "Path": "Jazz/ledge_climb.aura", "FrameRate": 8, "States": [ 1073741862 ] }, "Swing": { "Path": "Jazz/swing.aura", "FrameRate": 4, "States": [ 32768 ], "Flags": 1 }, "IdleBored1": { "Path": "Jazz/idle_flavor_1.aura", "FrameRate": 2, "States": [ 536870944 ] }, "IdleBored2": { "Path": "Jazz/idle_flavor_2.aura", "FrameRate": 1, "States": [ 536870945 ] }, "IdleBored3": { "Path": "Jazz/idle_flavor_3.aura", "FrameRate": 2, "States": [ 536870946 ] }, "IdleBored4": { "Path": "Jazz/idle_flavor_4.aura", "FrameRate": 3, "States": [ 536870947 ] }, "IdleBored5": { "Path": "Jazz/idle_flavor_5.aura", "FrameRate": 3, "States": [ 536870948 ] }, "TransformFromFrog": { "Path": "Jazz/transform_frog_end.aura", "FrameRate": 3, "States": [ 1073741888 ] }, "Corpse": { "Path": "Jazz/corpse.aura", "States": [ 536870912 ] }, "SugarRush": { "Path": "Common/sugar_rush_stars.aura", "States": [ 536870913 ] }, "Shield": { "Path": "Common/player_shield.aura", "States": [ 536870928 ] }, "ShieldFire": { "Path": "Common/shield_fire.aura", "States": [ 536870929 ] }, "ShieldWater": { "Path": "Common/shield_water.aura", "States": [ 536870930 ] }, "ShieldLightning": { "Path": "Common/shield_lightning.aura", "States": [ 536870931 ] }, "WeaponFlare": { "Path": "Weapon/flare_hor_2.aura", "States": [ 536870950 ] } }, "Sounds": { "ChangeWeapon": { "Paths": [ "UI/weapon_change.wav" ] }, "EndOfLevel": { "Paths": [ "Jazz/level_complete.wav" ] }, "WarpIn": { "Paths": [ "Common/warp_in.wav" ] }, "WarpOut": { "Paths": [ "Common/warp_out.wav" ] }, "Jump": { "Paths": [ "Common/char_jump.wav" ] }, "Land": { "Paths": [ "Common/char_land.wav" ] }, "Hurt": { "Paths": [ "Jazz/hurt_1.wav", "Jazz/hurt_2.wav", "Jazz/hurt_3.wav", "Jazz/hurt_4.wav", "Jazz/hurt_5.wav", "Jazz/hurt_6.wav", "Jazz/hurt_7.wav", "Jazz/hurt_8.wav" ] }, "HurtSoft": { "Paths": [ "Jazz/hurt_1.wav", "Jazz/hurt_5.wav", "Jazz/hurt_6.wav", "Jazz/hurt_7.wav", "Jazz/hurt_8.wav" ] }, "Die": { "Paths": [ "Common/gunsm1.wav" ] }, "Copter": { "Paths": [ "Common/copter_noise.wav" ] }, "PickupAmmo": { "Paths": [ "Pickup/ammo.wav" ] }, "PickupCoin": { "Paths": [ "Pickup/coin.wav" ] }, "PickupGem": { "Paths": [ "Pickup/gem.wav" ] }, "PickupOneUp": { "Paths": [ "Pickup/1up.wav" ] }, "PickupDrink": { "Paths": [ "Pickup/food_drink_1.wav", "Pickup/food_drink_2.wav", "Pickup/food_drink_3.wav", "Pickup/food_drink_4.wav" ] }, "PickupFood": { "Paths": [ "Pickup/food_edible_1.wav", "Pickup/food_edible_2.wav", "Pickup/food_edible_3.wav", "Pickup/food_edible_4.wav" ] }, "PickupMaxCarrot": { "Paths": [ "Jazz/carrot.wav" ] }, "WeaponBlaster": { "Paths": [ "Weapon/bullet_blaster_jazz_4.wav" ] }, "WeaponToaster": { "Paths": [ "Weapon/toaster.wav" ] }, "WeaponThunderbolt": { "Paths": [ "Unknown/unknown_bonus1.wav" ] }, "WeaponThunderboltStart": { "Paths": [ "Cinematics/opening_shot.wav" ] }, "WeaponThunderboltEnd": { "Paths": [ "Pickup/shield_lightning_bullet_1.wav" ] }, "HookAttach": { "Paths": [ "Common/swish_9.wav" ] }, "Buttstomp": { "Paths": [ "Common/down.wav" ] }, "Buttstomp2": { }, "EndOfLevel1": { "Paths": [ "Common/char_revup.wav" ] }, "EndOfLevel2": { "Paths": [ "Weapon/ricochet_bullet_3.wav" ] }, "Spring": { }, "Pole": { "Paths": [ "Birdy/fly_1.wav", "Birdy/fly_2.wav" ] }, "Ledge": { "Paths": [ "Jazz/ledge.wav" ] }, "IdleBored3": { "Paths": [ "Jazz/idle_flavor_3.wav" ] }, "IdleBored4": { "Paths": [ "Jazz/idle_flavor_4.wav" ] }, "BonusWarpNotEnoughCoins": { "Paths": [ "Object/bonus_not_enough_coins.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Interactive/PlayerLori.res000066400000000000000000000215621512772601700273220ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Lori/idle.aura", "FrameRate": 5, "States": [ 0 ] }, "Walk": { "Path": "Lori/run.aura", "FrameRate": 8, "States": [ 1, 17 ] }, "Run": { "Path": "Lori/dash_start.aura", "FrameRate": 12, "States": [ 2, 18 ] }, "Jump": { "Path": "Lori/jump.aura", "States": [ 4 ] }, "RunJump": { "Path": "Lori/jump_diag.aura", "Flags": 1, "States": [ 5, 6 ] }, "Fall": { "Path": "Lori/fall.aura", "States": [ 8 ] }, "FallDiag": { "Path": "Lori/fall_diag.aura", "States": [ 9, 10, 11 ] }, "Freefall": { "Path": "Lori/freefall.aura", "States": [ 65536, 65537, 65538, 65539 ] }, "Dash": { "Path": "Lori/dash.aura", "FrameRate": 20, "States": [ 3, 19 ] }, "DashJump": { "Path": "Lori/ball.aura", "States": [ 7 ] }, "Lookup": { "Path": "Lori/lookup_start.aura", "Flags": 1, "FrameRate": 28, "States": [ 64 ] }, "Crouch": { "Path": "Lori/crouch_start.aura", "Flags": 1, "FrameRate": 24, "States": [ 32 ] }, "DizzyIdle": { "Path": "Lori/dizzy.aura", "FrameRate": 6, "States": [ 128 ] }, "DizzyWalk": { "Path": "Lori/dizzy_walk.aura", "FrameRate": 6, "States": [ 129, 145 ] }, "Shoot": { "Path": "Lori/shoot.aura", "FrameRate": 8, "States": [ 16, 144 ], "Flags": 1 }, "CrouchShoot": { "Path": "Lori/crouch_shoot.aura", "FrameRate": 8, "States": [ 48 ], "Flags": 1 }, "LookupShoot": { "Path": "Lori/shoot_ver.aura", "FrameRate": 8, "States": [ 80 ], "Flags": 1 }, "Hurt": { "Path": "Lori/hurt.aura", "FrameRate": 6, "States": [ 2048 ] }, "Sidekick": { "Path": "Lori/sidekick.aura", "FrameOffset": 2, "States": [ 512 ] }, "Buttstomp": { "Path": "Lori/buttstomp.aura", "States": [ 256 ] }, "Hook": { "Path": "Lori/vine_idle.aura", "States": [ 12 ] }, "HookLookup": { "Path": "Lori/vine_shoot_up_end.aura", "States": [ 76 ] }, "HookMove": { "Path": "Lori/vine_walk.aura", "States": [ 13, 14, 15 ] }, "Copter": { "Path": "Lori/copter.aura", "FrameRate": 3, "States": [ 8192, 8193, 8194, 8195 ] }, "CopterShoot": { "Path": "Lori/copter_shoot.aura", "FrameRate": 22, "States": [ 8208, 8209, 8210, 8211 ] }, "AerialShoot": { "Path": "Lori/fall_shoot.aura", "FrameRate": 14, "States": [ 20, 21, 22, 23, 24, 25, 26, 27, 262160, 262161, 262162, 262163 ] }, "HookShoot": { "Path": "Lori/vine_shoot.aura", "States": [ 28, 29, 30, 31 ], "Flags": 1 }, "HookLookupShoot": { "Path": "Lori/vine_shoot_up.aura", "States": [ 92 ], "Flags": 1 }, "RunToIdle": { "Path": "Lori/run_stop.aura", "States": [ 1073741824 ] }, "RunToDash": { "Path": "Lori/dash_start.aura", "States": [ 1073741825 ] }, "DashToIdle": { "Path": "Lori/dash_stop.aura", "States": [ 1073741856 ] }, "FallToIdle": { "Path": "Lori/fall_end.aura", "States": [ 1073741826 ] }, "ShootToIdle": { "Path": "Lori/shoot_start.aura", "FrameRate": 20, "States": [ 1073741828 ] }, "HookShootToHook": { "Path": "Lori/vine_shoot_end.aura", "FrameRate": 24, "States": [ 1073741829 ] }, "CopterShootToCopter": { "Path": "Lori/copter_shoot_start.aura", "FrameRate": 27, "States": [ 1073741830 ] }, "SidekickA": { "Path": "Lori/sidekick.aura", "FrameCount": 2, "FrameRate": 20, "States": [ 1073741831 ] }, "SidekickC": { "Path": "Lori/sidekick.aura", "FrameOffset": 9, "FrameRate": 100, "States": [ 1073741833 ] }, "ButtstompStart": { "Path": "Lori/spring.aura", "States": [ 1073741834 ] }, "ButtstompEnd": { "Path": "Lori/buttstomp_end.aura", "FrameRate": 10, "States": [ 1073741858 ] }, "PoleH": { "Path": "Lori/pole_h.aura", "FrameRate": 20, "States": [ 1073741835 ] }, "PoleHSlow": { "Path": "Lori/pole_h.aura", "States": [ 1073741837 ] }, "PoleV": { "Path": "Lori/pole_v.aura", "FrameRate": 20, "States": [ 1073741836 ] }, "PoleVSlow": { "Path": "Lori/pole_v.aura", "States": [ 1073741838 ] }, "Death": { "Path": "Lori/die.aura", "FrameRate": 3, "States": [ 1073741839 ] }, "WarpIn": { "Path": "Lori/warp_in.aura", "FrameRate": 10, "States": [ 1073741843 ] }, "WarpOut": { "Path": "Lori/warp_out.aura", "FrameRate": 10, "States": [ 1073741844 ] }, "WarpInFreefall": { "Path": "Lori/warp_in_freefall.aura", "States": [ 1073741847 ] }, "WarpOutFreefall": { "Path": "Lori/warp_out_freefall.aura", "States": [ 1073741848 ] }, "Spring": { "Path": "Lori/spring.aura", "States": [ 262144, 262145, 262146, 262147 ] }, "Push": { "Path": "Lori/push.aura", "FrameRate": 6, "States": [ 16384, 16385 ] }, "EndOfLevel": { "Path": "Lori/eol.aura", "States": [ 1073741846 ] }, "Swim": { "Path": "Lori/swim_right.aura", "FrameRate": 6, "States": [ 4096 ] }, "Lift": { "Path": "Lori/lift.aura", "Flags": 1, "FrameRate": 16, "States": [ 131072 ] }, "LiftStart": { "Path": "Lori/lift_start.aura", "FrameRate": 16, "States": [ 1073741859 ] }, "LiftEnd": { "Path": "Lori/lift_end.aura", "States": [ 1073741860 ] }, "Ledge": { "Path": "Lori/ledge.aura", "FrameRate": 5, "FrameOffset": 1, "States": [ 1073741861 ] }, "Airboard": { "Path": "Lori/airboard.aura", "FrameRate": 6, "States": [ 1024 ] }, "Swing": { "Path": "Lori/swing.aura", "FrameRate": 4, "States": [ 32768 ], "Flags": 1, "FrameOffset": 7 }, "IdleBored1": { "Path": "Lori/idle_flavor_2.aura", "FrameRate": 1, "States": [ 536870944 ] }, "IdleBored2": { "Path": "Lori/idle_flavor_3.aura", "FrameRate": 3, "States": [ 536870945 ] }, "IdleBored3": { "Path": "Lori/idle_flavor_4.aura", "FrameRate": 2, "States": [ 536870946 ] }, "TransformFromFrog": { "Path": "Lori/transform_frog_end.aura", "FrameRate": 3, "States": [ 1073741888 ] }, "Corpse": { "Path": "Lori/corpse.aura", "States": [ 536870912 ] }, "SugarRush": { "Path": "Common/sugar_rush_stars.aura", "States": [ 536870913 ] }, "Shield": { "Path": "Common/player_shield.aura", "States": [ 536870928 ] }, "ShieldFire": { "Path": "Common/shield_fire.aura", "States": [ 536870929 ] }, "ShieldWater": { "Path": "Common/shield_water.aura", "States": [ 536870930 ] }, "ShieldLightning": { "Path": "Common/shield_lightning.aura", "States": [ 536870931 ] }, "WeaponFlare": { "Path": "Weapon/flare_hor_2.aura", "States": [ 536870950 ] } }, "Sounds": { "ChangeWeapon": { "Paths": [ "UI/weapon_change.wav" ] }, "EndOfLevel": { "Paths": [ "Lori/level_complete.wav" ] }, "WarpIn": { "Paths": [ "Common/warp_in.wav" ] }, "WarpOut": { "Paths": [ "Common/warp_out.wav" ] }, "Jump": { "Paths": [ "Lori/jump_2.wav", "Lori/jump_3.wav", "Lori/jump_4.wav" ] }, "Land": { "Paths": [ "Common/char_land.wav" ] }, "Hurt": { "Paths": [ "Lori/hurt_2.wav", "Lori/hurt_3.wav", "Lori/hurt_5.wav", "Lori/hurt_6.wav", "Lori/hurt_7.wav", "Lori/hurt_8.wav" ] }, "HurtSoft": { "Paths": [ "Lori/hurt_1.wav", "Lori/hurt_3.wav", "Lori/hurt_4.wav" ] }, "Die": { "Paths": [ "Lori/die.wav" ] }, "Copter": { "Paths": [ "Common/copter_noise.wav" ] }, "PickupAmmo": { "Paths": [ "Pickup/ammo.wav" ] }, "PickupCoin": { "Paths": [ "Pickup/coin.wav" ] }, "PickupGem": { "Paths": [ "Pickup/gem.wav" ] }, "PickupOneUp": { "Paths": [ "Pickup/1up.wav" ] }, "PickupDrink": { "Paths": [ "Pickup/food_drink_1.wav", "Pickup/food_drink_2.wav", "Pickup/food_drink_3.wav", "Pickup/food_drink_4.wav" ] }, "PickupFood": { "Paths": [ "Pickup/food_edible_1.wav", "Pickup/food_edible_2.wav", "Pickup/food_edible_3.wav", "Pickup/food_edible_4.wav" ] }, "PickupMaxCarrot": { "Paths": [ "Pickup/food_edible_1.wav" ] }, "WeaponBlaster": { "Paths": [ "Weapon/bullet_blaster_jazz_2.wav", "Weapon/bullet_blaster_jazz_3.wav" ] }, "WeaponToaster": { "Paths": [ "Weapon/toaster.wav" ] }, "WeaponThunderbolt": { "Paths": [ "Unknown/unknown_bonus1.wav" ] }, "WeaponThunderboltStart": { "Paths": [ "Cinematics/opening_shot.wav" ] }, "WeaponThunderboltEnd": { "Paths": [ "Pickup/shield_lightning_bullet_1.wav" ] }, "HookAttach": { "Paths": [ "Common/swish_9.wav" ] }, "Buttstomp": { "Paths": [ "Common/down.wav" ] }, "Buttstomp2": { "Paths": [ "Lori/fall.wav" ] }, "EndOfLevel1": { "Paths": [ "Common/char_revup.wav" ] }, "EndOfLevel2": { "Paths": [ "Weapon/ricochet_bullet_3.wav" ] }, "Spring": { }, "Pole": { "Paths": [ "Birdy/fly_1.wav", "Birdy/fly_2.wav" ] }, "Ledge": { }, "Sidekick": { }, "BonusWarpNotEnoughCoins": { "Paths": [ "Object/bonus_not_enough_coins.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Interactive/PlayerSpaz.res000066400000000000000000000223141512772601700273260ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Spaz/idle.aura", "States": [ 0 ] }, "Walk": { "Path": "Spaz/run.aura", "States": [ 1, 17 ] }, "Run": { "Path": "Spaz/dash_start.aura", "States": [ 2, 18 ] }, "Jump": { "Path": "Spaz/jump.aura", "States": [ 4 ] }, "RunJump": { "Path": "Spaz/jump_diag.aura", "Flags": 1, "States": [ 5, 6 ] }, "Fall": { "Path": "Spaz/fall.aura", "States": [ 8 ] }, "FallDiag": { "Path": "Spaz/fall_diag.aura", "States": [ 9, 10, 11 ] }, "Freefall": { "Path": "Spaz/freefall.aura", "States": [ 65536, 65537, 65538, 65539 ] }, "Dash": { "Path": "Spaz/dash.aura", "FrameRate": 20, "States": [ 3, 19 ] }, "DashJump": { "Path": "Spaz/ball.aura", "States": [ 7 ] }, "Lookup": { "Path": "Spaz/lookup_start.aura", "Flags": 1, "FrameRate": 34, "States": [ 64 ] }, "Crouch": { "Path": "Spaz/crouch_start.aura", "Flags": 1, "FrameRate": 38, "States": [ 32 ] }, "DizzyIdle": { "Path": "Spaz/dizzy.aura", "FrameRate": 7, "States": [ 128 ] }, "DizzyWalk": { "Path": "Spaz/dizzy_walk.aura", "FrameRate": 6, "States": [ 129, 145 ] }, "Shoot": { "Path": "Spaz/shoot.aura", "States": [ 16, 144 ], "Flags": 1 }, "CrouchShoot": { "Path": "Spaz/crouch_shoot.aura", "States": [ 48 ], "Flags": 1 }, "LookupShoot": { "Path": "Spaz/shoot_ver.aura", "States": [ 80 ], "Flags": 1 }, "Hurt": { "Path": "Spaz/hurt.aura", "FrameRate": 6, "States": [ 2048 ] }, "Sidekick": { "Path": "Spaz/sidekick.aura", "States": [ 512 ] }, "Buttstomp": { "Path": "Spaz/buttstomp.aura", "States": [ 256 ] }, "Hook": { "Path": "Spaz/vine_idle.aura", "States": [ 12 ] }, "HookLookup": { "Path": "Spaz/vine_shoot_up_end.aura", "States": [ 76 ] }, "HookMove": { "Path": "Spaz/vine_walk.aura", "States": [ 13, 14, 15 ] }, "Copter": { "Path": "Spaz/copter.aura", "FrameRate": 8, "States": [ 8192, 8193, 8194, 8195 ] }, "CopterShoot": { "Path": "Spaz/copter_shoot.aura", "States": [ 8208, 8209, 8210, 8211 ] }, "AerialShoot": { "Path": "Spaz/fall_shoot.aura", "FrameRate": 14, "States": [ 20, 21, 22, 23, 24, 25, 26, 27, 262160, 262161, 262162, 262163 ] }, "HookShoot": { "Path": "Spaz/vine_shoot.aura", "States": [ 28, 29, 30, 31 ], "Flags": 1 }, "HookLookupShoot": { "Path": "Spaz/vine_shoot_up.aura", "States": [ 92 ], "Flags": 1 }, "RunToIdle": { "Path": "Spaz/run_stop.aura", "States": [ 1073741824 ] }, "RunToDash": { "Path": "Spaz/dash_start.aura", "States": [ 1073741825 ] }, "DashToIdle": { "Path": "Spaz/dash_stop.aura", "States": [ 1073741856 ] }, "FallToIdle": { "Path": "Spaz/fall_end.aura", "States": [ 1073741826 ] }, "ShootToIdle": { "Path": "Spaz/shoot_start.aura", "FrameRate": 20, "States": [ 1073741828 ] }, "HookShootToHook": { "Path": "Spaz/vine_shoot_end.aura", "FrameRate": 24, "States": [ 1073741829 ] }, "CopterShootToCopter": { "Path": "Spaz/copter_shoot_start.aura", "FrameRate": 24, "States": [ 1073741830 ] }, "FallShootToFall": { "Path": "Spaz/jump_shoot_end.aura", "FrameRate": 20, "States": [ 1073741872 ] }, "SidekickA": { "Path": "Spaz/sidekick_start.aura", "FrameCount": 5, "FrameRate": 20, "States": [ 1073741831 ] }, "SidekickB": { "Path": "Spaz/sidekick_start.aura", "FrameOffset": 5, "FrameRate": 20, "States": [ 1073741832 ] }, "SidekickC": { "Path": "Spaz/sidekick_end.aura", "FrameRate": 30, "States": [ 1073741833 ] }, "ButtstompStart": { "Path": "Spaz/spring.aura", "States": [ 1073741834 ] }, "ButtstompEnd": { "Path": "Spaz/buttstomp_end.aura", "States": [ 1073741858 ] }, "PoleH": { "Path": "Spaz/pole_h.aura", "FrameRate": 20, "States": [ 1073741835 ] }, "PoleHSlow": { "Path": "Spaz/pole_h.aura", "States": [ 1073741837 ] }, "PoleV": { "Path": "Spaz/pole_v.aura", "FrameRate": 20, "States": [ 1073741836 ] }, "PoleVSlow": { "Path": "Spaz/pole_v.aura", "States": [ 1073741838 ] }, "Death": { "Path": "Spaz/die.aura", "FrameRate": 3, "States": [ 1073741839 ] }, "WarpIn": { "Path": "Spaz/warp_in.aura", "States": [ 1073741843 ] }, "WarpOut": { "Path": "Spaz/warp_out.aura", "States": [ 1073741844 ] }, "WarpInFreefall": { "Path": "Spaz/warp_in_freefall.aura", "States": [ 1073741847 ] }, "WarpOutFreefall": { "Path": "Spaz/warp_out_freefall.aura", "States": [ 1073741848 ] }, "Spring": { "Path": "Spaz/spring.aura", "States": [ 262144, 262145, 262146, 262147 ] }, "Push": { "Path": "Spaz/push.aura", "FrameRate": 7, "States": [ 16384, 16385 ] }, "EndOfLevel": { "Path": "Spaz/eol.aura", "FrameRate": 3, "States": [ 1073741846 ] }, "Swim": { "Path": "Spaz/swim_right.aura", "FrameRate": 6, "States": [ 4096 ] }, "Lift": { "Path": "Spaz/lift.aura", "Flags": 1, "FrameRate": 16, "States": [ 131072 ] }, "LiftStart": { "Path": "Spaz/lift_start.aura", "FrameRate": 16, "States": [ 1073741859 ] }, "LiftEnd": { "Path": "Spaz/lift_end.aura", "States": [ 1073741860 ] }, "Ledge": { "Path": "Spaz/ledge.aura", "FrameRate": 3, "States": [ 1073741861 ] }, "Airboard": { "Path": "Spaz/airboard.aura", "FrameRate": 6, "States": [ 1024 ] }, "LedgeClimb": { "Path": "Spaz/ledge_climb.aura", "FrameRate": 8, "States": [ 1073741862 ] }, "Swing": { "Path": "Spaz/swing.aura", "FrameRate": 4, "States": [ 32768 ], "Flags": 1 }, "IdleBored1": { "Path": "Spaz/idle_flavor_2.aura", "FrameRate": 2, "States": [ 536870944 ] }, "IdleBored2": { "Path": "Spaz/idle_flavor_3.aura", "FrameRate": 1, "States": [ 536870945 ] }, "IdleBored3": { "Path": "Spaz/idle_flavor_4.aura", "FrameRate": 3, "States": [ 536870946 ] }, "IdleBored4": { "Path": "Spaz/idle_flavor_5.aura", "FrameRate": 3, "States": [ 536870947 ] }, "TransformFromFrog": { "Path": "Spaz/transform_frog_end.aura", "FrameRate": 3, "States": [ 1073741888 ] }, "Corpse": { "Path": "Spaz/corpse.aura", "States": [ 536870912 ] }, "SugarRush": { "Path": "Common/sugar_rush_stars.aura", "States": [ 536870913 ] }, "Shield": { "Path": "Common/player_shield.aura", "States": [ 536870928 ] }, "ShieldFire": { "Path": "Common/shield_fire.aura", "States": [ 536870929 ] }, "ShieldWater": { "Path": "Common/shield_water.aura", "States": [ 536870930 ] }, "ShieldLightning": { "Path": "Common/shield_lightning.aura", "States": [ 536870931 ] }, "WeaponFlare": { "Path": "Weapon/flare_hor_2.aura", "States": [ 536870950 ] } }, "Sounds": { "ChangeWeapon": { "Paths": [ "UI/weapon_change.wav" ] }, "EndOfLevel": { "Paths": [ "Spaz/level_complete.wav" ] }, "WarpIn": { "Paths": [ "Common/warp_in.wav" ] }, "WarpOut": { "Paths": [ "Common/warp_out.wav" ] }, "Jump": { "Paths": [ "Common/char_jump.wav" ] }, "Land": { "Paths": [ "Common/char_land.wav" ] }, "Hurt": { "Paths": [ "Spaz/hurt_1.wav", "Spaz/hurt_2.wav" ] }, "HurtSoft": { "Paths": [ "Spaz/oooh.wav" ] }, "Die": { "Paths": [ "Spaz/idle_flavor_4.wav" ] }, "Copter": { "Paths": [ "Common/copter_noise.wav" ] }, "PickupAmmo": { "Paths": [ "Pickup/ammo.wav" ] }, "PickupCoin": { "Paths": [ "Pickup/coin.wav" ] }, "PickupGem": { "Paths": [ "Pickup/gem.wav" ] }, "PickupOneUp": { "Paths": [ "Pickup/1up.wav" ] }, "PickupDrink": { "Paths": [ "Pickup/food_drink_1.wav", "Pickup/food_drink_2.wav", "Pickup/food_drink_3.wav", "Pickup/food_drink_4.wav" ] }, "PickupFood": { "Paths": [ "Pickup/food_edible_1.wav", "Pickup/food_edible_2.wav", "Pickup/food_edible_3.wav", "Pickup/food_edible_4.wav" ] }, "PickupMaxCarrot": { "Paths": [ "Pickup/food_edible_1.wav" ] }, "WeaponBlaster": { "Paths": [ "Weapon/bullet_blaster_jazz_2.wav", "Weapon/bullet_blaster_jazz_3.wav" ] }, "WeaponToaster": { "Paths": [ "Weapon/toaster.wav" ] }, "WeaponThunderbolt": { "Paths": [ "Unknown/unknown_bonus1.wav" ] }, "WeaponThunderboltStart": { "Paths": [ "Cinematics/opening_shot.wav" ] }, "WeaponThunderboltEnd": { "Paths": [ "Pickup/shield_lightning_bullet_1.wav" ] }, "HookAttach": { "Paths": [ "Common/swish_9.wav" ] }, "Buttstomp": { "Paths": [ "Common/down.wav" ] }, "Buttstomp2": { "Paths": [ "Spaz/jump_3.wav" ] }, "EndOfLevel1": { "Paths": [ "Common/char_revup.wav" ] }, "EndOfLevel2": { "Paths": [ "Weapon/ricochet_bullet_3.wav" ] }, "Spring": { "Paths": [ "Spaz/spring_1.wav", "Spaz/spring_2.wav" ] }, "Pole": { "Paths": [ "Birdy/fly_1.wav", "Birdy/fly_2.wav" ] }, "Ledge": { "Paths": [ "Spaz/ledge.wav" ] }, "IdleBored1": { "Paths": [ "Spaz/idle_flavor_2.wav" ] }, "DoubleJump": { "Paths": [ "Common/char_double_jump.wav" ] }, "Sidekick": { "Paths": [ "Spaz/sidekick_1.wav", "Spaz/sidekick_2.wav" ] }, "BonusWarpNotEnoughCoins": { "Paths": [ "Object/bonus_not_enough_coins.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/000077500000000000000000000000001512772601700252065ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/Ball.res000066400000000000000000000003161512772601700265730ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Platform/ball.aura", "States": [ 0 ] }, "Chain": { "Path": "Platform/ball_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/CarrotusFruit.res000066400000000000000000000003421512772601700305340ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Platform/carrotus_fruit.aura", "States": [ 0 ] }, "Chain": { "Path": "Platform/carrotus_fruit_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/CarrotusGrass.res000066400000000000000000000003421512772601700305220ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Platform/carrotus_grass.aura", "States": [ 0 ] }, "Chain": { "Path": "Platform/carrotus_grass_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/Lab.res000066400000000000000000000003141512772601700264150ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Platform/lab.aura", "States": [ 0 ] }, "Chain": { "Path": "Platform/lab_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/Sonic.res000066400000000000000000000003201512772601700267670ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Platform/sonic.aura", "States": [ 0 ] }, "Chain": { "Path": "Platform/sonic_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/Spike.res000066400000000000000000000003201512772601700267670ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Platform/spike.aura", "States": [ 0 ] }, "Chain": { "Path": "Platform/spike_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/MovingPlatform/SpikeBall.res000066400000000000000000000003221512772601700275640ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Platform": { "Path": "Object/3d_spike.aura", "States": [ 0 ] }, "Chain": { "Path": "Object/3d_spike_chain.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/000077500000000000000000000000001512772601700234505ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Airboard.res000066400000000000000000000002251512772601700257050ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Airboard": { "Path": "Pickup/airboard.aura", "FrameRate": 6 } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/BarrelContainer.res000066400000000000000000000012411512772601700272330ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "BarrelGeneric": { "Path": "Object/container_barrel.aura", "FrameRate": 0, "States": [ 0 ] }, "BarrelShrapnel1": { "Path": "Object/container_barrel_shrapnel_1.aura", "States": [ 1 ] }, "BarrelShrapnel2": { "Path": "Object/container_barrel_shrapnel_2.aura", "States": [ 2 ] }, "BarrelShrapnel3": { "Path": "Object/container_barrel_shrapnel_3.aura", "States": [ 3 ] }, "BarrelShrapnel4": { "Path": "Object/container_barrel_shrapnel_4.aura", "States": [ 4 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_barrel_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/BirdBirdy.res000066400000000000000000000006351512772601700260410ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Fly": { "Path": "BirdyYellow/fly.aura", "States": [ 0 ], "FrameRate": 7 }, "Idle": { "Path": "BirdyYellow/idle.aura", "States": [ 1073741849 ] }, "Attack": { "Path": "BirdyYellow/charge_ver.aura", "States": [ 16 ] } }, "Sounds": { "Fly": { "Paths": [ "Birdy/fly_1.wav", "Birdy/fly_2.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/BirdCageBirdy.res000066400000000000000000000005151512772601700266160ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "BirdyYellow/caged.aura", "FrameRate": 1, "States": [ 0 ] }, "Activated": { "Path": "BirdyYellow/cage_destroyed.aura", "States": [ 32 ] } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/BirdCageChuck.res000066400000000000000000000005011512772601700265750ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Birdy/caged.aura", "FrameRate": 1, "States": [ 0 ] }, "Activated": { "Path": "Birdy/cage_destroyed.aura", "States": [ 32 ] } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/BirdChuck.res000066400000000000000000000006041512772601700260210ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Fly": { "Path": "Birdy/fly.aura", "States": [ 0 ], "FrameRate": 7 }, "Idle": { "Path": "Birdy/idle.aura", "States": [ 1073741849 ] } }, "Sounds": { "Fly": { "Paths": [ "Birdy/fly_1.wav", "Birdy/fly_2.wav" ] }, "Fire": { "Paths": [ "Weapon/bullet_blaster_jazz_4.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Bomb.res000066400000000000000000000001741512772601700250440ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bomb": { "Path": "Common/bomb.aura", "States": [ 2 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/BonusWarp.res000066400000000000000000000010501512772601700260770ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "BonusGeneric": { "Path": "Object/bonus_active.aura", "FrameRate": 7, "States": [ 0 ] }, "Bonus10": { "Path": "Object/bonus10.aura", "FrameRate": 7, "States": [ 10 ] }, "Bonus20": { "Path": "Object/bonus20.aura", "FrameRate": 7, "States": [ 20 ] }, "Bonus50": { "Path": "Object/bonus50.aura", "FrameRate": 7, "States": [ 50 ] }, "Bonus100": { "Path": "Object/bonus100.aura", "FrameRate": 7, "States": [ 100 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Checkpoint.res000066400000000000000000000010251512772601700262500ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Closed": { "Path": "Object/checkpoint.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 0 ] }, "Opened": { "Path": "Object/checkpoint.aura", "FrameOffset": 13, "FrameCount": 1, "FrameRate": 0, "States": [ 1 ] }, "TransitionActivate": { "Path": "Object/checkpoint.aura", "FrameRate": 6, "States": [ 1325400064 ] } }, "Sounds": { "TransitionActivate": { "Paths": [ "Object/checkpoint_open.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/CheckpointXmas.res000066400000000000000000000010441512772601700271020ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Closed": { "Path": "Object/checkpoint_xmas.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 0 ] }, "Opened": { "Path": "Object/checkpoint_xmas.aura", "FrameOffset": 13, "FrameCount": 1, "FrameRate": 0, "States": [ 1 ] }, "TransitionActivate": { "Path": "Object/checkpoint_xmas.aura", "FrameRate": 6, "States": [ 1325400064 ] } }, "Sounds": { "TransitionActivate": { "Paths": [ "Object/checkpoint_open.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/000077500000000000000000000000001512772601700245065ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoBouncer.res000066400000000000000000000007471512772601700274400ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoBouncer": { "Path": "Object/crate_ammo_bouncer.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoElectro.res000066400000000000000000000007471512772601700274400ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoElectro": { "Path": "Object/crate_ammo_electro.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoFreezer.res000066400000000000000000000007471512772601700274450ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoFreezer": { "Path": "Object/crate_ammo_freezer.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoPepper.res000066400000000000000000000007451512772601700272740ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoPepper": { "Path": "Object/crate_ammo_pepper.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoRF.res000066400000000000000000000007351512772601700263470ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoRF": { "Path": "Object/crate_ammo_rf.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoSeeker.res000066400000000000000000000007451512772601700272570ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoSeeker": { "Path": "Object/crate_ammo_seeker.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoTNT.res000066400000000000000000000007371512772601700265070ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoTNT": { "Path": "Object/crate_ammo_tnt.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/AmmoToaster.res000066400000000000000000000007471512772601700274640ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateAmmoToaster": { "Path": "Object/crate_ammo_toaster.aura", "States": [ 0 ] }, "CrateAmmoShrapnel1": { "Path": "Object/container_ammo_shrapnel_1.aura", "States": [ 1 ] }, "CrateAmmoShrapnel2": { "Path": "Object/container_ammo_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Crate/Generic.res000066400000000000000000000012011512772601700265670ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "BoundingBox": [ 30, 28 ], "Animations": { "CrateGeneric": { "Path": "Object/container_box_crush.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 0 ] }, "CrateGenericCrush": { "Path": "Object/container_box_crush.aura", "FrameRate": 18, "States": [ 1073741839 ] }, "CrateShrapnel1": { "Path": "Object/container_crate_shrapnel_1.aura", "States": [ 1 ] }, "CrateShrapnel2": { "Path": "Object/container_crate_shrapnel_2.aura", "States": [ 2 ] } }, "Sounds": { "Break": { "Paths": [ "Object/container_crate_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Eva.res000066400000000000000000000010501512772601700246720ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Idle": { "Path": "Eva/idle.aura", "States": [ 0 ] }, "Blink": { "Path": "Eva/blink.aura", "States": [ 1073741849 ], "FrameRate": 26 }, "KissStart": { "Path": "Eva/kiss_start.aura", "States": [ 1325400065 ], "FrameRate": 4 }, "KissEnd": { "Path": "Eva/kiss_end.aura", "States": [ 1325400066 ], "FrameRate": 4 } }, "Sounds": { "Kiss": { "Paths": [ "Eva/kiss1.wav", "Eva/kiss2.wav", "Eva/kiss3.wav", "Eva/kiss4.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/GemGiant.res000066400000000000000000000003321512772601700256540ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "GemGiant": { "Path": "Object/gem_giant.aura", "PaletteOffset": 128 } }, "Sounds": { "Break": { "Paths": [ "Object/gem_giant_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/IceBlock.res000066400000000000000000000001611512772601700256340ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "IceBlock": { "Path": "Common/ice_block.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Moth.res000066400000000000000000000006451512772601700250770ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Pink": { "Path": "Moth/pink.aura", "FrameRate": 40, "States": [ 0 ] }, "Gray": { "Path": "Moth/gray.aura", "FrameRate": 40, "States": [ 1 ] }, "Green": { "Path": "Moth/green.aura", "FrameRate": 40, "States": [ 2 ] }, "Purple": { "Path": "Moth/purple.aura", "FrameRate": 40, "States": [ 3 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PinballBumper.res000066400000000000000000000007051512772601700267210ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "500": { "Path": "Pinball/bumper_500.aura", "States": [ 0 ] }, "500Hit": { "Path": "Pinball/bumper_500_hit.aura", "States": [ 512 ] }, "Carrot": { "Path": "Pinball/bumper_carrot.aura", "States": [ 1 ] }, "CarrotHit": { "Path": "Pinball/bumper_carrot_hit.aura", "States": [ 513 ] } }, "Sounds": { "Hit": { "Paths": [ "Pinball/bumper_hit.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PinballPaddle.res000066400000000000000000000006741512772601700266650ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Paddle": { "Path": "Pinball/paddle_left.aura", "FrameRate": 0, "FrameCount": 1, "States": [ 0 ] }, "PaddleActivate": { "Path": "Pinball/paddle_left.aura", "States": [ 1325400064 ] } }, "Sounds": { "Hit": { "Paths": [ "Pinball/paddle_hit_1.wav", "Pinball/paddle_hit_2.wav", "Pinball/paddle_hit_3.wav","Pinball/paddle_hit_4.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/000077500000000000000000000000001512772601700250515ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Bird.res000066400000000000000000000003561512772601700264500ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Bird": { "Path": "Object/powerup_transform_birdy.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Blaster.res000066400000000000000000000007701512772601700271640ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "BlasterJazz": { "Path": "Object/powerup_upgrade_blaster_jazz.aura", "FrameRate": 6, "States": [ 1 ] }, "BlasterSpaz": { "Path": "Object/powerup_upgrade_blaster_spaz.aura", "FrameRate": 6, "States": [ 2 ] }, "BlasterLori": { "Path": "Object/powerup_upgrade_blaster_lori.aura", "FrameRate": 6, "States": [ 3 ] } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Bouncer.res000066400000000000000000000003611512772601700271610ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Bouncer": { "Path": "Object/powerup_upgrade_bouncer.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Electro.res000066400000000000000000000003611512772601700271610ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Electro": { "Path": "Object/powerup_upgrade_electro.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Empty.res000066400000000000000000000003451512772601700266640ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Empty": { "Path": "Object/powerup_empty.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Freezer.res000066400000000000000000000003611512772601700271660ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Freezer": { "Path": "Object/powerup_upgrade_freezer.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Pepper.res000066400000000000000000000003571512772601700270240ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Pepper": { "Path": "Object/powerup_upgrade_pepper.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/RF.res000066400000000000000000000003471512772601700260770ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "RF": { "Path": "Object/powerup_upgrade_rf.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Seeker.res000066400000000000000000000003571512772601700270070ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Seeker": { "Path": "Object/powerup_upgrade_seeker.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/ShieldFire.res000066400000000000000000000003601512772601700276010ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "ShieldFire": { "Path": "Object/powerup_shield_fire.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/ShieldLaser.res000066400000000000000000000003621512772601700277640ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "ShieldLaser": { "Path": "Object/powerup_shield_laser.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/ShieldLightning.res000066400000000000000000000003721512772601700306420ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "ShieldLightning": { "Path": "Object/powerup_shield_lightning.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/ShieldWater.res000066400000000000000000000003621512772601700300000ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "ShieldWater": { "Path": "Object/powerup_shield_water.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Swap2.res000066400000000000000000000003571512772601700265650ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Swap2": { "Path": "Object/powerup_swap_characters.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Swap3.res000066400000000000000000000003641512772601700265640ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Swap3": { "Path": "Object/powerup_swap_characters_lori.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PowerUp/Toaster.res000066400000000000000000000003611512772601700272050ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Toaster": { "Path": "Object/powerup_upgrade_toaster.aura", "FrameRate": 6 } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PushBoxCrate.res000066400000000000000000000001641512772601700265330ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "PushBox": { "Path": "Object/pushbox_crate.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/PushBoxRock.res000066400000000000000000000001631512772601700263720ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "PushBox": { "Path": "Object/pushbox_rock.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/RollingRock.res000066400000000000000000000003171512772601700264110ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Rock": { "Path": "Object/rolling_rock.aura", "States": [ 0 ] } }, "Sounds": { "Hit": { "Paths": [ "Object/rolling_rock.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/SignEol.res000066400000000000000000000001601512772601700255200ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "SignEol": { "Path": "Object/exit_sign.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/Spring.res000066400000000000000000000044031512772601700254260ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Red": { "Path": "Spring/spring_red_ver.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 1024 ] }, "Green": { "Path": "Spring/spring_green_ver.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 2048 ] }, "Blue": { "Path": "Spring/spring_blue_ver.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 3072 ] }, "RedActive": { "Path": "Spring/spring_red_ver.aura", "States": [ 1536 ] }, "GreenActive": { "Path": "Spring/spring_green_ver.aura", "States": [ 2560 ] }, "BlueActive": { "Path": "Spring/spring_blue_ver.aura", "States": [ 3584 ] }, "RedHorizontal": { "Path": "Spring/spring_red_hor.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 5120 ] }, "GreenHorizontal": { "Path": "Spring/spring_green_hor.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 6144 ] }, "BlueHorizontal": { "Path": "Spring/spring_blue_hor.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 7168 ] }, "RedHorizontalActive": { "Path": "Spring/spring_red_hor.aura", "States": [ 5632 ] }, "GreenHorizontalActive": { "Path": "Spring/spring_green_hor.aura", "States": [ 6656 ] }, "BlueHorizontalActive": { "Path": "Spring/spring_blue_hor.aura", "States": [ 7680 ] }, "RedReversed": { "Path": "Spring/spring_red_ver_reverse.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 9216 ] }, "GreenReversed": { "Path": "Spring/spring_green_ver_reverse.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 10240 ] }, "BlueReversed": { "Path": "Spring/spring_blue_ver_reverse.aura", "FrameCount": 1, "FrameRate": 0, "States": [ 11264 ] }, "RedReversedActive": { "Path": "Spring/spring_red_ver_reverse.aura", "States": [ 9728 ] }, "GreenReversedActive": { "Path": "Spring/spring_green_ver_reverse.aura", "States": [ 10752 ] }, "BlueReversedActive": { "Path": "Spring/spring_blue_ver_reverse.aura", "States": [ 11776 ] } }, "Sounds": { "Vertical": { "Paths": [ "Spring/spring_2.wav" ] }, "VerticalReversed": { "Paths": [ "Spring/spring_ver_down.wav" ] }, "Horizontal": { "Paths": [ "Spring/spring.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/SteamNote.res000066400000000000000000000003441512772601700260630ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "SteamNote": { "Path": "Common/steam_note.aura", "FrameRate": 4 } }, "Sounds": { "Appear": { "Paths": [ "Common/steam_note.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/SwingingVine.res000066400000000000000000000001501512772601700265660ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Vine": { "Path": "Object/vine.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Object/TriggerCrate.res000066400000000000000000000003361512772601700265470ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "BoundingBox": [ 30, 28 ], "Animations": { "Crate": { "Path": "Object/trigger_crate.aura" } }, "Sounds": { "Break": { "Paths": [ "Object/powerup_break.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Pole/000077500000000000000000000000001512772601700231415ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Pole/Carrotus.res000066400000000000000000000003571512772601700254630ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Pole": { "Path": "Pole/carrotus.aura" } }, "Sounds": { "FallStart": { "Paths": [ "Pole/fall_start.wav" ] }, "FallEnd": { "Paths": [ "Pole/fall_end.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Pole/Diamondus.res000066400000000000000000000003601512772601700255760ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Pole": { "Path": "Pole/diamondus.aura" } }, "Sounds": { "FallStart": { "Paths": [ "Pole/fall_start.wav" ] }, "FallEnd": { "Paths": [ "Pole/fall_end.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Pole/DiamondusTree.res000066400000000000000000000003651512772601700264230ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Pole": { "Path": "Pole/diamondus_tree.aura" } }, "Sounds": { "FallStart": { "Paths": [ "Pole/fall_start.wav" ] }, "FallEnd": { "Paths": [ "Pole/fall_end.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Pole/Jungle.res000066400000000000000000000003551512772601700251030ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Pole": { "Path": "Pole/Jungle.aura" } }, "Sounds": { "FallStart": { "Paths": [ "Pole/fall_start.wav" ] }, "FallEnd": { "Paths": [ "Pole/fall_end.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Pole/Psych.res000066400000000000000000000003541512772601700247440ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Pole": { "Path": "Pole/Psych.aura" } }, "Sounds": { "FallStart": { "Paths": [ "Pole/fall_start.wav" ] }, "FallEnd": { "Paths": [ "Pole/fall_end.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/UI/000077500000000000000000000000001512772601700225575ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/UI/HUD.res000066400000000000000000000110111512772601700237040ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "WeaponBlasterJazz": { "Path": "Pickup/fast_fire_jazz.aura", "States": [ 0 ] }, "WeaponBlasterSpaz": { "Path": "Pickup/fast_fire_spaz.aura", "States": [ 1 ] }, "WeaponBlasterLori": { "Path": "Pickup/fast_fire_lori.aura", "States": [ 2 ] }, "WeaponBouncer": { "Path": "Pickup/ammo_bouncer.aura", "States": [ 3 ] }, "WeaponFreezer": { "Path": "Pickup/ammo_freezer.aura", "FrameRate": 8, "States": [ 4 ] }, "WeaponSeeker": { "Path": "Pickup/ammo_seeker.aura", "States": [ 5 ] }, "WeaponRF": { "Path": "Pickup/ammo_rf.aura", "States": [ 6 ] }, "WeaponToaster": { "Path": "Pickup/ammo_toaster.aura", "States": [ 7 ] }, "WeaponTNT": { "Path": "Object/tnt.aura", "States": [ 8 ] }, "WeaponPepper": { "Path": "Pickup/ammo_pepper.aura", "States": [ 9 ] }, "WeaponElectro": { "Path": "Pickup/ammo_electro.aura", "States": [ 10 ] }, "WeaponThunderbolt": { "Path": "Pickup/ammo_thunderbolt.aura", "States": [ 11 ] }, "WeaponPowerUpBlasterJazz": { "Path": "UI/blaster_upgraded_jazz.aura", "States": [ 20 ] }, "WeaponPowerUpBlasterSpaz": { "Path": "UI/blaster_upgraded_spaz.aura", "States": [ 21 ] }, "WeaponPowerUpBlasterLori": { "Path": "UI/blaster_upgraded_lori.aura", "States": [ 22 ] }, "WeaponPowerUpBouncer": { "Path": "Pickup/ammo_bouncer_upgraded.aura", "States": [ 23 ] }, "WeaponPowerUpFreezer": { "Path": "Pickup/ammo_freezer_upgraded.aura", "States": [ 24 ] }, "WeaponPowerUpSeeker": { "Path": "Pickup/ammo_seeker_upgraded.aura", "States": [ 25 ] }, "WeaponPowerUpRF": { "Path": "Pickup/ammo_rf_upgraded.aura", "States": [ 26 ] }, "WeaponPowerUpToaster": { "Path": "Pickup/ammo_toaster_upgraded.aura", "States": [ 27 ] }, "WeaponPowerUpTNT": { "Path": "Object/tnt.aura", "States": [ 28 ] }, "WeaponPowerUpPepper": { "Path": "Pickup/ammo_pepper_upgraded.aura", "States": [ 29 ] }, "WeaponPowerUpElectro": { "Path": "Pickup/ammo_electro_upgraded.aura", "States": [ 30 ] }, "WeaponPowerUpThunderbolt": { "Path": "Pickup/ammo_thunderbolt.aura", "States": [ 31 ] }, "WeaponToasterDisabled": { "Path": "Pickup/ammo_toaster_disabled.aura", "States": [ 47 ] }, "CharacterJazz": { "Path": "UI/icon_jazz.aura", "FrameRate": 2, "States": [ 60 ] }, "CharacterSpaz": { "Path": "UI/icon_spaz.aura", "FrameRate": 2, "States": [ 61 ] }, "CharacterLori": { "Path": "UI/icon_lori.aura", "FrameRate": 2, "States": [ 62 ] }, "CharacterFrog": { "Path": "UI/icon_frog.aura", "FrameRate": 2, "States": [ 63 ] }, "Heart": { "Path": "UI/heart.aura", "States": [ 70 ] }, "PickupGemRed": { "Path": "Pickup/gem.aura", "PaletteOffset": 128, "States": [ 71 ] }, "PickupGemGreen": { "Path": "Pickup/gem.aura", "PaletteOffset": 256, "States": [ 72 ] }, "PickupGemBlue": { "Path": "Pickup/gem.aura", "PaletteOffset": 384, "States": [ 73 ] }, "PickupGemPurple": { "Path": "Pickup/gem.aura", "PaletteOffset": 512, "States": [ 74 ] }, "PickupCoin": { "Path": "Pickup/coin_silver.aura", "States": [ 75 ] }, "PickupFood": { "Path": "Pickup/food_pizza.aura", "States": [ 76 ] }, "PickupCarrot": { "Path": "Pickup/carrot_full.aura", "States": [ 77 ] }, "PickupStopwatch": { "Path": "Pickup/stopwatch.aura", "States": [ 78 ] }, "BossHealthBar": { "Path": "UI/boss_health_bar.aura", "States": [ 79 ] }, "WeaponWheel": { "Path": "UI/weapon_wheel.aura", "States": [ 80 ] }, "WeaponWheelInner": { "Path": "UI/weapon_wheel_inner.aura", "States": [ 81 ] }, "WeaponWheelDim": { "Path": "UI/dim.aura", "States": [ 82 ] }, "TouchDpad": { "Path": "UI/touch_dpad.aura", "States": [ 100 ] }, "TouchFire": { "Path": "UI/touch_fire.aura", "States": [ 101 ] }, "TouchJump": { "Path": "UI/touch_jump.aura", "States": [ 102 ] }, "TouchRun": { "Path": "UI/touch_run.aura", "States": [ 103 ] }, "TouchChange": { "Path": "UI/touch_change.aura", "States": [ 104 ] }, "TouchPause": { "Path": "UI/touch_pause.aura", "States": [ 105 ] }, "TouchClose": { "Path": "UI/touch_close.aura", "States": [ 106 ] } }, "Sounds": { "Countdown": { "Paths": [ "Common/powerup_shield_damage_2.wav" ] }, "CountdownEnd": { "Paths": [ "Common/powerup_shield_damage_1.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/UI/Loading.res000066400000000000000000000001741512772601700246510ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Loading": { "Path": "UI/loading.aura" } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/UI/MainMenu.res000066400000000000000000000171731512772601700250140ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "MenuCarrot": { "Path": "UI/logo.aura", "FrameRate": 7, "States": [ 0 ] }, "Snow": { "Path": "Common/snow.aura", "States": [ 1 ] }, "MenuLine": { "Path": "UI/line.aura", "States": [ 2 ] }, "MenuLineArrow": { "Path": "UI/line_arrow.aura", "States": [ 3 ] }, "MenuDim": { "Path": "UI/dim.aura", "States": [ 4 ] }, "MenuGlow": { "Path": "UI/glow.aura", "States": [ 5 ] }, "EpisodeComplete": { "Path": "UI/episode_complete.aura", "States": [ 10 ] }, "MenuDifficultyJazz": { "Path": "UI/character_art_difficulty_jazz.aura", "States": [ 11 ] }, "MenuDifficultySpaz": { "Path": "UI/character_art_difficulty_spaz.aura", "States": [ 12 ] }, "MenuDifficultyLori": { "Path": "UI/character_art_difficulty_lori.aura", "States": [ 13 ] }, "Uac": { "Path": "UI/uac.aura", "States": [ 20 ] }, "Storage": { "Path": "UI/storage.aura", "States": [ 21 ] }, "ShowKeyboard": { "Path": "UI/touch_keyboard.aura", "States": [ 22 ] }, "Rip": { "Path": "UI/rip.aura", "States": [ 23 ] }, "PickupGemRed": { "Path": "Pickup/gem.aura", "PaletteOffset": 128, "States": [ 30 ] }, "PickupGemGreen": { "Path": "Pickup/gem.aura", "PaletteOffset": 256, "States": [ 31 ] }, "PickupGemBlue": { "Path": "Pickup/gem.aura", "PaletteOffset": 384, "States": [ 32 ] }, "PickupGemPurple": { "Path": "Pickup/gem.aura", "PaletteOffset": 512, "States": [ 33 ] }, "PickupStopwatch": { "Path": "Pickup/stopwatch.aura", "States": [ 34 ] }, "GamepadXboxA": { "Path": "UI/gamepad/xbox_a.aura", "States": [ 40 ] }, "GamepadXboxB": { "Path": "UI/gamepad/xbox_b.aura", "States": [ 41 ] }, "GamepadXboxX": { "Path": "UI/gamepad/xbox_x.aura", "States": [ 42 ] }, "GamepadXboxY": { "Path": "UI/gamepad/xbox_y.aura", "States": [ 43 ] }, "GamepadXboxDPadLeft": { "Path": "UI/gamepad/xbox_left.aura", "States": [ 44 ] }, "GamepadXboxDPadRight": { "Path": "UI/gamepad/xbox_right.aura", "States": [ 45 ] }, "GamepadXboxDPadUp": { "Path": "UI/gamepad/xbox_up.aura", "States": [ 46 ] }, "GamepadXboxDPadDown": { "Path": "UI/gamepad/xbox_down.aura", "States": [ 47 ] }, "GamepadXboxGuide": { "Path": "UI/gamepad/xbox_guide.aura", "States": [ 48 ] }, "GamepadXboxBack": { "Path": "UI/gamepad/xbox_back.aura", "States": [ 49 ] }, "GamepadXboxStart": { "Path": "UI/gamepad/xbox_start.aura", "States": [ 50 ] }, "GamepadXboxLeftShoulder": { "Path": "UI/gamepad/xbox_lb.aura", "States": [ 51 ] }, "GamepadXboxLeftStick": { "Path": "UI/gamepad/xbox_ls.aura", "States": [ 52 ] }, "GamepadXboxLeftTrigger": { "Path": "UI/gamepad/xbox_lt.aura", "States": [ 53 ] }, "GamepadXboxRightShoulder": { "Path": "UI/gamepad/xbox_rb.aura", "States": [ 54 ] }, "GamepadXboxRightStick": { "Path": "UI/gamepad/xbox_rs.aura", "States": [ 55 ] }, "GamepadXboxRightTrigger": { "Path": "UI/gamepad/xbox_rt.aura", "States": [ 56 ] }, "GamepadXboxMisc1": { "Path": "UI/gamepad/xbox_misc1.aura", "States": [ 57 ] }, "GamepadPsA": { "Path": "UI/gamepad/ps_a.aura", "States": [ 58 ] }, "GamepadPsB": { "Path": "UI/gamepad/ps_b.aura", "States": [ 59 ] }, "GamepadPsX": { "Path": "UI/gamepad/ps_x.aura", "States": [ 60 ] }, "GamepadPsY": { "Path": "UI/gamepad/ps_y.aura", "States": [ 61 ] }, "GamepadPsDPadLeft": { "Path": "UI/gamepad/ps_left.aura", "States": [ 62 ] }, "GamepadPsDPadRight": { "Path": "UI/gamepad/ps_right.aura", "States": [ 63 ] }, "GamepadPsDPadUp": { "Path": "UI/gamepad/ps_up.aura", "States": [ 64 ] }, "GamepadPsDPadDown": { "Path": "UI/gamepad/ps_down.aura", "States": [ 65 ] }, "GamepadPsGuide": { "Path": "UI/gamepad/ps_guide.aura", "States": [ 66 ] }, "GamepadPsBack": { "Path": "UI/gamepad/ps_back.aura", "States": [ 67 ] }, "GamepadPsStart": { "Path": "UI/gamepad/ps_start.aura", "States": [ 68 ] }, "GamepadPsLeftShoulder": { "Path": "UI/gamepad/ps_lb.aura", "States": [ 69 ] }, "GamepadPsLeftTrigger": { "Path": "UI/gamepad/ps_lt.aura", "States": [ 70 ] }, "GamepadPsRightShoulder": { "Path": "UI/gamepad/ps_rb.aura", "States": [ 71 ] }, "GamepadPsRightTrigger": { "Path": "UI/gamepad/ps_rt.aura", "States": [ 72 ] }, "GamepadPsMisc1": { "Path": "UI/gamepad/ps_misc1.aura", "States": [ 73 ] }, "GamepadPsTouchpad": { "Path": "UI/gamepad/ps_touchpad.aura", "States": [ 74 ] }, "GamepadSwitchA": { "Path": "UI/gamepad/switch_a.aura", "States": [ 75 ] }, "GamepadSwitchB": { "Path": "UI/gamepad/switch_b.aura", "States": [ 76 ] }, "GamepadSwitchX": { "Path": "UI/gamepad/switch_x.aura", "States": [ 77 ] }, "GamepadSwitchY": { "Path": "UI/gamepad/switch_y.aura", "States": [ 78 ] }, "GamepadSwitchDPadLeft": { "Path": "UI/gamepad/switch_left.aura", "States": [ 79 ] }, "GamepadSwitchDPadRight": { "Path": "UI/gamepad/switch_right.aura", "States": [ 80 ] }, "GamepadSwitchDPadUp": { "Path": "UI/gamepad/switch_up.aura", "States": [ 81 ] }, "GamepadSwitchDPadDown": { "Path": "UI/gamepad/switch_down.aura", "States": [ 82 ] }, "GamepadSwitchGuide": { "Path": "UI/gamepad/switch_guide.aura", "States": [ 83 ] }, "GamepadSwitchBack": { "Path": "UI/gamepad/switch_back.aura", "States": [ 84 ] }, "GamepadSwitchStart": { "Path": "UI/gamepad/switch_start.aura", "States": [ 85 ] }, "GamepadSwitchLeftTrigger": { "Path": "UI/gamepad/switch_lt.aura", "States": [ 86 ] }, "GamepadSwitchRightTrigger": { "Path": "UI/gamepad/switch_rt.aura", "States": [ 87 ] }, "GamepadSwitchMisc1": { "Path": "UI/gamepad/switch_misc1.aura", "States": [ 88 ] }, "GamepadSteamA": { "Path": "UI/gamepad/steam_a.aura", "States": [ 89 ] }, "GamepadSteamB": { "Path": "UI/gamepad/steam_b.aura", "States": [ 90 ] }, "GamepadSteamX": { "Path": "UI/gamepad/steam_x.aura", "States": [ 91 ] }, "GamepadSteamY": { "Path": "UI/gamepad/steam_y.aura", "States": [ 92 ] }, "GamepadSteamDPadLeft": { "Path": "UI/gamepad/steam_left.aura", "States": [ 93 ] }, "GamepadSteamDPadRight": { "Path": "UI/gamepad/steam_right.aura", "States": [ 94 ] }, "GamepadSteamDPadUp": { "Path": "UI/gamepad/steam_up.aura", "States": [ 95 ] }, "GamepadSteamDPadDown": { "Path": "UI/gamepad/steam_down.aura", "States": [ 96 ] }, "GamepadSteamGuide": { "Path": "UI/gamepad/steam_guide.aura", "States": [ 97 ] }, "GamepadSteamBack": { "Path": "UI/gamepad/steam_back.aura", "States": [ 98 ] }, "GamepadSteamStart": { "Path": "UI/gamepad/steam_start.aura", "States": [ 99 ] }, "GamepadSteamLeftShoulder": { "Path": "UI/gamepad/steam_lb.aura", "States": [ 100 ] }, "GamepadSteamRightShoulder": { "Path": "UI/gamepad/steam_rb.aura", "States": [ 101 ] }, "GamepadSteamMisc1": { "Path": "UI/gamepad/steam_misc1.aura", "States": [ 102 ] }, "Menu16": { "Path": "UI/menu16.aura", "States": [ 110 ] }, "Menu32": { "Path": "UI/menu32.aura", "States": [ 111 ] }, "Menu128": { "Path": "UI/menu128.aura", "States": [ 112 ] }, "LoriExistsCheck": { "Path": "Lori/corpse.aura", "States": [ 120 ] } }, "Sounds": { "MenuSelect": { "Paths": [ "UI/select_1.wav", "UI/select_2.wav", "UI/select_3.wav", "UI/select_4.wav", "UI/select_5.wav", "UI/select_6.wav", "UI/select_7.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/000077500000000000000000000000001512772601700234735ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Blaster.res000066400000000000000000000006711512772601700256060ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/bullet_blaster.aura", "States": [ 0 ] }, "BulletUpgraded": { "Path": "Weapon/bullet_blaster_upgraded.aura", "States": [ 1 ] } }, "Sounds": { "Ricochet": { "Paths": [ "Weapon/ricochet_bullet_1.wav", "Weapon/ricochet_bullet_2.wav", "Weapon/ricochet_bullet_3.wav" ] }, "WallPoof": { "Paths": [ "Weapon/wall_poof.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Bouncer.res000066400000000000000000000015021512772601700256010ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/bullet_bouncer.aura", "States": [ 0 ] }, "BulletUpgraded": { "Path": "Weapon/bullet_bouncer_upgraded.aura", "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/bullet_bouncer_1.wav", "Weapon/bullet_bouncer_2.wav", "Weapon/bullet_bouncer_3.wav", "Weapon/bullet_bouncer_4.wav", "Weapon/bullet_bouncer_5.wav", "Weapon/bullet_bouncer_6.wav", "Weapon/bullet_bouncer_7.wav" ] }, "FireUpgraded": { "Paths": [ "Weapon/bullet_bouncer_upgraded_1.wav", "Weapon/bullet_bouncer_upgraded_2.wav", "Weapon/bullet_bouncer_upgraded_3.wav", "Weapon/bullet_bouncer_upgraded_4.wav", "Weapon/bullet_bouncer_upgraded_5.wav", "Weapon/bullet_bouncer_upgraded_6.wav" ] }, "Bounce": { "Paths": [ "Weapon/bouncer.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Electro.res000066400000000000000000000004751512772601700256110ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Mask": { "Path": "Weapon/electro_mask.aura", "States": [ 0 ] }, "Particle": { "Path": "Weapon/electro.aura", "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/bullet_electro_2.wav", "Weapon/bullet_electro_3.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Freezer.res000066400000000000000000000013051512772601700256070ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Bullet": { "Path": "Weapon/bullet_freezer_hor.aura", "FrameRate": 26, "States": [ 0 ] }, "BulletUpgraded": { "Path": "Weapon/bullet_freezer_upgraded_hor.aura", "FrameRate": 26, "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/bullet_freezer_1.wav", "Weapon/bullet_freezer_2.wav" ] }, "FireUpgraded": { "Paths": [ "Weapon/bullet_freezer_upgraded_1.wav", "Weapon/bullet_freezer_upgraded_2.wav", "Weapon/bullet_freezer_upgraded_3.wav", "Weapon/bullet_freezer_upgraded_4.wav", "Weapon/bullet_freezer_upgraded_5.wav" ] }, "WallPoof": { "Paths": [ "Weapon/wall_poof.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Pepper.res000066400000000000000000000004451512772601700254440ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/pepper.aura", "States": [ 0 ] }, "BulletUpgraded": { "Path": "Weapon/pepper_upgraded.aura", "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/bullet_pepper.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/RF.res000066400000000000000000000005561512772601700245230ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/bullet_rf_hor.aura", "States": [ 0 ] }, "BulletUpgraded": { "Path": "Weapon/bullet_rf_upgraded_hor.aura", "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/bullet_rf.wav" ] }, "Explode": { "Paths": [ "Weapon/tnt_explosion.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Seeker.res000066400000000000000000000004731512772601700254300ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/bullet_seeker_hor.aura", "States": [ 0 ] }, "BulletUpgraded": { "Path": "Weapon/bullet_seeker_upgraded_hor.aura", "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/bullet_seeker.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/ShieldFire.res000066400000000000000000000003271512772601700262260ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/shield_fire.aura", "States": [ 0 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/shield_fire_bullet.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/ShieldLightning.res000066400000000000000000000004701512772601700272630ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/shield_lightning.aura", "States": [ 0 ] }, "Trail": { "Path": "Weapon/shield_lightning_trail.aura", "States": [ 1 ] } }, "Sounds": { "Fire": { "Paths": [ "Weapon/shield_lightning_noise_1.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/ShieldWater.res000066400000000000000000000004001512772601700264130ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Bullet": { "Path": "Weapon/shield_water.aura", "States": [ 0 ], "Flags": 1 } }, "Sounds": { "Fire": { "Paths": [ "Weapon/shield_water_1.wav", "Weapon/shield_water_2.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/TNT.res000066400000000000000000000007021512772601700246520ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "TNT": { "Path": "Weapon/bullet_tnt.aura", "States": [ 0 ] }, "Explosion": { "Path": "Weapon/bullet_tnt_explosion.aura", "States": [ 1325400064 ] } }, "Sounds": { "Explosion": { "Paths": [ "Weapon/tnt_explosion.wav" ] }, "Bell1": { "Paths": [ "Common/powerup_shield_damage_1.wav" ] }, "Bell2": { "Paths": [ "Common/powerup_shield_damage_2.wav" ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Thunderbolt.res000066400000000000000000000003211512772601700264740ustar00rootroot00000000000000{ "Target": "Jazz² Resurrection", "Animations": { "Fire1": { "Path": "Weapon/thunderbolt1.aura", "States": [ 0 ] }, "Fire2": { "Path": "Weapon/thunderbolt2.aura", "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Metadata/Weapon/Toaster.res000066400000000000000000000004341512772601700256300ustar00rootroot00000000000000{ "Version": { "Target": "Jazz² Resurrection" }, "Animations": { "Fire": { "Path": "Weapon/bullet_toaster.aura", "FrameRate": 5, "States": [ 0 ] }, "FireUpgraded": { "Path": "Weapon/bullet_toaster_upgraded.aura", "FrameRate": 5, "States": [ 1 ] } } }deathkiller-jazz2-native-2a7ccef/Content/Translations/000077500000000000000000000000001512772601700232035ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Content/Translations/cs.mo000066400000000000000000000723141512772601700241540ustar00rootroot00000000000000,|  !@_OnlX 6#.Z,    9C T b)l   8 A LW`r'  #1(/Z   )"9 \.g+  ! & - D @H   7 !!!*!e+%+(+6+9*,d,&,<,A,'*-DR-(-+-@-/-.)].6.L.? /FK/,/)/S/F=0(020(0L 1NV1E101,2(I2>r24202937Q3.3A3*3<%4'b4I4D4.53H5D|565C5d<616C6<7FT7I7+7E8FW8J8C8:-96h9@9;95:DR:.:F:( ;@6;}w;;:<8M<3<A<0<8-=Af= =E=C>$S>6x>H>A>E:?I???6 @IA@@@D@rAA^AFA0?B1pBQB+B CvCZ!D'|D@DQD;7E&sE=EBE,F3HF4|F@FFHIu IIJ^J K#K<BKK K.KK)KL $L1L7LLLRLmL LLL-LL LL M M &M0MKMfMwM M M M MMMMMM,N2N LNXN/mNNNGN;O[[ \'\8\+U\\#\\!\ \!]?]Y]6u]1]]]*^12^d^x^.^B^_<#_`_~_B___2`OE`6`C`!a2aGNa1aa#ab@bM]bDb!b)cn7n0+o>\o8o>o[popCp4pq%q<Bqq~qcrYr"r3r<2s*oss9s5s""t&Et+lt3t:.a2l/AH&,m+^EG M!t>j!u` 5s0RV#,dP6T[ '_%" ;' *X7NJ1K$W+3hwS9B#e=YgbI)L Z@D"q|x&%f yr8c* Uzk- O\p~?4Fn$]  )o(viC}{Q(< The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContinuous JumpContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: 2025-11-09 15:56+0100 Last-Translator: Language-Team: Dan R. Language: cs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Hra brzy začne! Vítězem je {} [c:#337233]Restartujte hru, aby bylo možné správně přečíst soubory [c:#9e7056]Jazz Jackrabbit 2 [c:#337233]. [c:#704a4a]Nelze načíst zadanou úroveň! [/c] Ujistěte se, že všechny potřebné soubory jsou přístupné, a zkuste to znovu. [c:#704a4a]Nelze obnovit uložený stav! [/c] Ujistěte se, že všechny potřebné soubory jsou přístupné, a zkuste to znovu. [c:#704a4a]Tato hra vyžaduje originální soubory [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]! [c:#d0705d]{} [/c] se připojil [c:#d0705d]{} [/c] se odpojil [c:#d0705d]{} [/c] byl upečen hráčem [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] se upeklO aplikaciPřístup k externímu uložišti byl povolen!Povolit podvádění (cheaty)Povolit přístup k externímu uložištiVždyAntialiasingZpětDithering na pozadíBitvaOtevřít složku "Source"Prorazit ze vzduchuBoj o vlajkuZměnit zbraňPostavaCheaty nejsou v současném kontextu povolenyPřipojit k serveruPokračovatNepřetržitě skákatPřispěvateléOvládáníKooperaceVytvořit soukromý serverVytvořit veřejný serverVytvořit serverVytvořit server z playlistuPodrobnýVývojářiObtížnostZakázánoOdpojit & UkončitIntegrace aplikace DiscordDolůLehkáPovolenoPovoleno [c:#d0705d](Experimentální) [/c]Povoleno s počtem municeVylepšeníEpizoda je zamčena!Rozšířená podpora ovladačů PlayStation™Najděte východ!StřelbaPro více informací můžete navštívit oficiální webové stránky:Pro více informací můžete navštívit {} a  Discord!Celá obrazovkaHerní režimHra začne za {}Popisky tlačítek na gamepaduVibrace ovladačeHraGrafikaTěžkáVysokáPouze při vyšším skóreSíň slávySíň slávy pro [c:#d0705d]Základní hru [/c]Síň slávy pro [c:#d0705d]{} [/c]HorizontálníChci hrát hru takovou, jaká byla.Chci hrát hru s něčím novým.Importovat epizodyDiagnostika ovládáníSkokZachovat poměr stran u filmůJazykPovolit šplháníDolevaPůvodníÚroveň "{}" inicializovánaNízkáUjistěte se, že soubory Jazz Jackrabbit 2 jsou přístupné v tomto umístění:Hlavní hlasitostStředníMonochromeHlasitost hudbyNativní tlačítko ZpětNově přidané úrovně a epizody budou brzy k dispozici.NePouze bez podváděníNebyly nalezeny žádné vlastní úrovně!Nebyly nalezeny žádné epizody!Nebyly vybrány žádné soubory!Nebyly detekovány žádné gamepady!Nebyly importovány žádné nové epizody!Nebyly nalezeny žádné servery, ale stále hledám!Žádný / Pixel-perfectPočet lokálních hráčůNastaveníPřepisovat dokončení epizodyMetrika výkonuHrát vlastní úrovněHrát po sítiHrát Shareware DemoHrát příběhJméno hráčeBody: {}Preferovat oddáleníPreferované rozdělení obrazovkyPro pokračování stiskněte tlačítko [c:#d0705d]Střelba [/c]Stiskněte tlačítko nebo klávesu pro přiřazeníZpracování souborů ve složce [c:#9e7056]"Source" [/c]...Zpracování {} souboru...Zpracování {} souborů...Zpracování {} souborů...UkončitZávodRazer Chroma™NovéNová hratelnostNový HUDNové hlavní menuObnovit cacheJazz² Resurrection je reimplementace hry [c:#9e7056]Jazz Jackrabbit 2 [/c] z roku 1998. Umožňuje hrát úrovně z různých verzí původní hry (Shareware Demo, Holiday Hare '98, The Secret Files a Christmas Chronicles). Dále také obsahuje některé nové funkce.Přenastavit ovládáníPřenastavit ovládání pro hráče {}Režim škálováníObnovit výchozíRozlišeníSmazat postupPokračovatDopravaBěhHlasitost zvukových efektůUložit & UkončitSkriptyVybrat herní režimVybrat režim škálováníVyberte soubory vaší původní hry pro odemknutí dalších epizodKrátkýZobrazit stopy hráčůZvukySledovatSpustit hruSilnéPřepnout na novou zbraňTýmová bitvaTýmový závodTýmoví lovci pokladůTento projekt využívá upravený herní engine [c:#9e7056]nCine [/c] a tyto knihovny:Zobrazit konzoliPřepínat běháníDotykové ovládací prvkyPřekladateléLovci pokladůNezarovnaný pohledUnikátní ID hráčeNeznámý příkazNepojmenovaný serverNahoruUživatelský profilVertikálníČekání na soubory...Čekání na {} dalšího hráčeČekání na {} další hráčeČekání na {} dalších hráčůKvalita vodySlabéKolečko zbraníZbraň {}Vítejte v reimplementaci hry [c:#9e7056]Jazz Jackrabbit 2 [/c]!AnoPozice zón dotyku můžete upravit přetažením.Můžete si zvolit preferovaný styl hraní. Tuto možnost můžete kdykoliv změnit v [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Pro více informací můžete navštívit {} a  Discord!Zde můžete zapnout jednotlivá vylepšení přidaná do hry.Nejprve musíte dokončit "{}"! Draci žijí v Burbanku. Najděte sysla. Mark nosí slipy. Hoo Hah! Nick miluje lesklé. Vždycky to tak bylo! Spaz sežral dopefish. Pozor na řetězovou pilu Schmalz. Nedávejte Markovi burrito. Pošlete Nigelovi zelenou kartu. Pošlete Timovi nové ponožky. Pozor na padající nepřátele. Craig je stále hlupák! Čas na tajnou úroveň!!! Rozšlápněte stříbrnou bednu a uvolněte si cestu Díky bednám se také mohou objevit plošiny... Leh je táborník Roztavte pružinu... Proti broukům dobře funguje plamenomet. Padající balvany mohou způsobit bolest hlavy. Dobrou noc, Bubbo! Ať žije ledová úroveň. Co to sakra je? Aaaah! Ne! Tohle NENÍ konec! Sbírejte mince a aktivujte bonusová teleportační zařízení. Tady není nic k vidění. Tyče vás roztočí tak, že poběžíte ještě rychleji. Tajná místnost s pokladem. Našli jste tajnou oblast. Rozšlápněte kovovou krabici pro otevření zamčených bloků! V úterý je sýr zelený. Craig je král pitomec. Dobrá práce! Teď rychle pro Devanův krunýř! Stisknete-li tlačítko dolů a skočíte pod těmito bloky, dojde k rozbití! Chcete-li porazit královnu, sestřelte ji z římsy. Pro proražení těchto bloků stiskněte tlačítko dolů a skok! Pro odchod si dupněte na zadek. Tato pružina je zamrzlá. Štíty vám na krátkou dobu poskytnou neomezenou speciální munici. Stopky přidají trochu času navíc do štítu. Super tajná místnost. Tenhle Schwartzenguard je v háji! Sakra! Mizím odsud! Nemůžeš mě porazit, Jazzi! Připrav se na mého superbota! Tyto bloky jsou rychlostní bloky. Rozběhněte se do nich plnou rychlostí! Po skoku znovu stiskněte skok pro provedení speciálního pohybu. Pozor na ostré věci. Bolí to. Modrý drahokam se počítá jako deset. Mrkev vám dodá zdraví. Kontrolní body vám uloží pozici, pokud ztratíte život. Sbíráním mincí odemknete bonusové místnosti. Sbírejte drahokamy pro životy navíc. Sbírejte dobroty pro body a překvapení. Dobrá práce. Nezapomeňte hledat tajemství. Zelený drahokam se počítá jako pět. Nyní jste připraveni hrát. Hodně štěstí a zábavy. Červený drahokam se počítá jako jeden. Ve hře je mnoho tajemství. Zkontrolujte stěny. Některé stěny lze rozstřílet. Vítejte ve hře Jazz Jackrabbit 2. Toto je tréninková úroveň. Když jste ve vzduchu, stiskněte tlačítko dolů pro proražení bloků zadkem směrem dolu. Prorazte poklop kanálu! Z kouřových mraků se vám bude točit hlava. Neporážejte Nigela v kulečníku. Byli jste varováni. :) Najděte bednu pro uvolnění cesty. Žádné odměny pro ty, které svědí prsty na spoušti. Pouze Spaz se může dostat do místnosti vlevo nahoře. Možná bude potřebovat něco, na čem by mohl stát. Dnešní předpověď: Silný vítr! Vítejte v Jazz Jackrabbit 2: The Secret Files! Tohle nejde prorazit, je to potřeba obejít!Když sníte příliš mnoho čokoládových vajíček, může vám být špatně :p Jedna cesta vede k bohatství. Druhá cesta vede do boje. Šikmý vjezd do tunelu Chcete-li se dostat do horních tunelů, najděte přístupový teleport. Najděte bednu, aby se objevily lezecké bloky K dobrotám se dostane jen ten, kdo zvládne dvojitý skok! Rozšlápnutím této bedny také osvobodíte nějaké nepřátele :) Ale potřebuješ se tam nějak dostat... Dbejte zvýšené opatrnosti..... Stříbrné bedny nelze rozbít pod vodou... Rozbíjení beden může být dobré i špatné... Bedna pro regulaci hladiny vody umístěná výše. Michelle, Budu tě milovat vždy a navždy :) Nezvládli jste skočit, co? :) Skočte co nejdále k doprava, jak jen to půjde... Vydejte se na střechy! Nebe nad vámi odmění ty, kteří si dupnou... Použijte své speciální pohyby, abyste se dostali do klimatizace! S Jazzem stiskněte dolů a skok. Se Spazem stiskněte dvakrát skok! Dobrá práce! Sebrání 20 mincí se vyplatí... Najděte bednu a drahokamy jsou vaše! Pružiny při zamrznutí nefungují... POZOR! Hejna krkavců mohou být velmi nebezpečná. Vyberte si kryt a vyrazte! Najděte bednu pro uvolnění cesty.... Prosím navštivte www.project2.com a zadejte heslo: BUNNYLOVER Ahoj, GeoBunny :) Chcete-li bloky odstranit, podívejte se na nejvyšší budovu. Mince umožní přístup k teleportům, které se objeví později. Střelte do těchto bloků! Některé bedny obsahují bomby nebo záškodníky! Dupněte na správné místo a možná vás čeká překvapení! Chcete-li projít touto oblastí, rozšlápněte tajnou kovovou bednu. Když jste ve vzduchu, stiskněte tlačítko dolů pro proražení bloků směrem dolu. Pro průchod touto tajnou bránou potřebujete dvacet mincí! Proti nepříjemnému hmyzu dobře funguje plamenomet. Z kouřových mraků se vám bude točit hlava! Pro průchod touto tajnou bránou potřebujete dvacet mincí! Pozor na čarodějnici! Může vás proměnit v žábu. Pokud jste se proměnili v žábu, Eva Earlong vám pomůže! Zvládli jste to! Toto je konec sharewarové verze. Podívejte na informace o plné verzi!pro odstranění přiřazení Zdraví vás Epic MegaGames, Orange Games a Project 2 Interactive! Některé bloky lze rozbít pouze určitou zbraní. Pozor na hroty dole! Vítejte ve vánočních letopisech! Některá slabá místa na cestách můžete prošlápnout! Ahoj, prasátko! - Hovínko Kassi Nicole: Milion věcí, které ti toužím říct Ale má slova vždy vedou ke stejnému konci. Miluji tě, miluji tě. Michelle: Změnil jsi můj život v mnoha ohledech. Tento projekt ti věnuji. Mám tě moc ráda. Vstup na stezku drahokamů. Vyšplhej do korun stromů, najdi a rozdupej vstupní bednu. Vstupní bedna stezky drahokamů. Údery do bloků nad vámi mohou být přínosné. Některá slabá místa na cestách můžete prošlápnout! Na některých stromech můžete i stát! Neztrácejte kontrolu! Nyní opouštíte Burrowsville Děkujeme za návštěvu. Heslo: xmasbunny Prosím navštivte www.project2.com Používejte prosím TNT moudře. Robert a Craig: Pružiny kralují! :) Ten most nevypadá příliš bezpečně... Vítejte v Burrowsville Jezděte prosím opatrně.deathkiller-jazz2-native-2a7ccef/Content/Translations/cs.po000066400000000000000000001744571512772601700241720ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:55+0100\n" "PO-Revision-Date: 2025-11-09 15:56+0100\n" "Last-Translator: \n" "Language-Team: Dan R.\n" "Language: cs\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz sežral dopefish." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Najděte sysla." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Draci žijí v Burbanku." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark nosí slipy. \n" "Hoo Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick miluje lesklé.\n" "Vždycky to tak bylo!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Pošlete Timovi nové ponožky." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Pošlete Nigelovi zelenou kartu." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Pozor na řetězovou pilu Schmalz." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Nedávejte Markovi burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Pozor na padající nepřátele." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig je stále hlupák!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Čas na tajnou úroveň!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Rozšlápněte stříbrnou bednu\n" "a uvolněte si cestu" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Díky bednám se také mohou objevit plošiny..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Roztavte pružinu..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh je táborník" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Padající balvany mohou\n" "způsobit bolest hlavy." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Proti broukům dobře\n" "funguje plamenomet." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Ať žije ledová úroveň." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Dobrou noc, Bubbo!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Co to sakra je? Aaaah! Ne!\n" "Tohle NENÍ konec!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Tyče vás roztočí tak,\n" "že poběžíte ještě rychleji." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Našli jste tajnou oblast." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Tajná místnost s pokladem." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Tady není nic k vidění." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Sbírejte mince a aktivujte\n" "bonusová teleportační zařízení." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "V úterý je sýr zelený." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig je král pitomec." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Chcete-li porazit královnu,\n" "sestřelte ji z římsy." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Dobrá práce!\n" "Teď rychle pro Devanův krunýř!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Pro proražení těchto bloků\n" "stiskněte tlačítko dolů a skok!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Stisknete-li tlačítko dolů a skočíte\n" "pod těmito bloky, dojde k rozbití!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Rozšlápněte kovovou krabici\n" "pro otevření zamčených bloků!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Pro odchod si dupněte na zadek." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Tato pružina je zamrzlá." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Super tajná místnost." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Štíty vám na krátkou dobu poskytnou\n" "neomezenou speciální munici." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Stopky přidají trochu času\n" "navíc do štítu." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Tenhle Schwartzenguard je v háji!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Nemůžeš mě porazit, Jazzi!\n" "Připrav se na mého superbota!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Sakra! Mizím odsud!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Tyto bloky jsou rychlostní bloky.\n" "Rozběhněte se do nich plnou rychlostí!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Vítejte ve hře Jazz Jackrabbit 2. \n" "Toto je tréninková úroveň." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Sbírejte dobroty pro\n" "body a překvapení." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Po skoku znovu stiskněte skok\n" "pro provedení speciálního pohybu." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Některé stěny lze rozstřílet." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Když jste ve vzduchu, stiskněte tlačítko dolů\n" "pro proražení bloků zadkem směrem dolu." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Ve hře je mnoho tajemství.\n" "Zkontrolujte stěny." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Dobrá práce. Nezapomeňte\n" "hledat tajemství." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Sbírejte drahokamy\n" "pro životy navíc." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Červený drahokam\n" "se počítá jako jeden." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Zelený drahokam\n" "se počítá jako pět." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Modrý drahokam\n" "se počítá jako deset." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Mrkev vám dodá zdraví." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Kontrolní body vám uloží pozici,\n" "pokud ztratíte život." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Sbíráním mincí odemknete\n" "bonusové místnosti." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Pozor na ostré věci.\n" "Bolí to." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Nyní jste připraveni hrát.\n" "Hodně štěstí a zábavy." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Prorazte poklop kanálu!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Z kouřových mraků se\n" "vám bude točit hlava." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Tohle nejde prorazit,\n" "je to potřeba obejít!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Žádné odměny pro ty,\n" "které svědí prsty na spoušti." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Dnešní předpověď: Silný vítr!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Najděte bednu\n" "pro uvolnění cesty." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Vítejte v\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Pouze Spaz se může dostat\n" "do místnosti vlevo nahoře.\n" "Možná bude potřebovat něco,\n" "na čem by mohl stát." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Neporážejte Nigela v kulečníku.\n" "Byli jste varováni. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Když sníte příliš mnoho čokoládových\n" "vajíček, může vám být špatně :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Šikmý vjezd do tunelu" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Chcete-li se dostat do horních tunelů,\n" "najděte přístupový teleport." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Jedna cesta vede k bohatství.\n" "Druhá cesta vede do boje." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "K dobrotám se dostane jen ten,\n" "kdo zvládne dvojitý skok!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Najděte bednu, aby se\n" "objevily lezecké bloky" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Rozšlápnutím této bedny také\n" "osvobodíte nějaké nepřátele :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Dbejte zvýšené opatrnosti....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Stříbrné bedny nelze rozbít pod vodou..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Bedna pro regulaci hladiny vody umístěná výše." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Rozbíjení beden může být dobré i špatné..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Ale potřebuješ se tam nějak dostat..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Budu tě milovat vždy a navždy :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Vydejte se na střechy!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Nebe nad vámi odmění ty,\n" "kteří si dupnou..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Skočte co nejdále k doprava,\n" "jak jen to půjde..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Nezvládli jste skočit, co? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Dobrá práce!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Použijte své speciální pohyby,\n" "abyste se dostali do klimatizace!\n" "S Jazzem stiskněte dolů a skok.\n" "Se Spazem stiskněte dvakrát skok!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Najděte bednu a drahokamy jsou vaše!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Pružiny při zamrznutí nefungují..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Sebrání 20 mincí se vyplatí..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Najděte bednu pro uvolnění cesty...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "POZOR! Hejna krkavců mohou\n" "být velmi nebezpečná." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Chcete-li bloky odstranit,\n" "podívejte se na nejvyšší budovu." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Vyberte si kryt a vyrazte!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Ahoj, GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Prosím navštivte\n" "www.project2.com\n" "a zadejte heslo: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Střelte do těchto bloků!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Když jste ve vzduchu, stiskněte tlačítko dolů\n" "pro proražení bloků směrem dolu." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Chcete-li projít touto oblastí,\n" "rozšlápněte tajnou kovovou bednu." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Pro průchod touto tajnou bránou\n" "potřebujete dvacet mincí!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Mince umožní přístup k teleportům,\n" "které se objeví později." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Dupněte na správné místo\n" "a možná vás čeká překvapení!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Některé bedny obsahují\n" "bomby nebo záškodníky!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Pro průchod touto tajnou bránou\n" "potřebujete dvacet mincí!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Proti nepříjemnému hmyzu\n" "dobře funguje plamenomet." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Z kouřových mraků se\n" "vám bude točit hlava!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Pozor na čarodějnici!\n" "Může vás proměnit v žábu." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Pokud jste se proměnili v žábu,\n" "Eva Earlong vám pomůže!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Zvládli jste to! Toto je konec\n" "sharewarové verze. Podívejte\n" "na informace o plné verzi!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Pozor na hroty dole!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Některá slabá místa\n" "na cestách můžete prošlápnout!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Některé bloky lze rozbít\n" "pouze určitou zbraní." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Vítejte ve vánočních letopisech!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Zdraví vás\n" "Epic MegaGames,\n" "Orange Games\n" "a Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Na některých stromech\n" "můžete i stát!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Některá slabá místa\n" "na cestách můžete prošlápnout!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Údery do bloků nad vámi\n" "mohou být přínosné." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Vstupní bedna stezky drahokamů." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Vstup na stezku drahokamů.\n" "Vyšplhej do korun stromů,\n" "najdi a rozdupej vstupní bednu." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Ahoj, prasátko!\n" "\n" "- Hovínko" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Změnil jsi můj život v mnoha ohledech.\n" "Tento projekt ti věnuji.\n" "Mám tě moc ráda." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Milion věcí, které ti toužím říct\n" "Ale má slova vždy vedou ke stejnému konci.\n" "Miluji tě, miluji tě." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Používejte prosím TNT moudře." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Neztrácejte kontrolu!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Vítejte v Burrowsville\n" "Jezděte prosím opatrně." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Ten most nevypadá\n" "příliš bezpečně..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert a Craig:\n" "Pružiny kralují! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Nyní opouštíte Burrowsville\n" "Děkujeme za návštěvu." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Heslo: xmasbunny\n" "Prosím navštivte\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Úroveň \"{}\" inicializována" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Pro více informací můžete navštívit oficiální webové stránky:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Cheaty nejsou v současném kontextu povoleny" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Hra brzy začne!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] byl upečen hráčem \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] se upekl" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] se odpojil" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] se připojil" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "Vítězem je {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Neznámý příkaz" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Jazz² Resurrection je reimplementace hry \f[c:#9e7056]Jazz Jackrabbit 2\f[/" "c] z roku 1998. Umožňuje hrát úrovně z různých verzí původní hry (Shareware " "Demo, Holiday Hare '98, The Secret Files a Christmas Chronicles). Dále také " "obsahuje některé nové funkce." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Vývojáři" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Přispěvatelé" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Překladatelé" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Tento projekt využívá upravený herní engine \f[c:#9e7056]nCine\f[/c] a tyto " "knihovny:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Hrát příběh" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Hrát Shareware Demo" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importovat epizody" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Pokračovat" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Hrát po síti" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Síň slávy" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Nastavení" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "O aplikaci" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Ukončit" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Pro více informací můžete navštívit {} a  Discord!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Přístup k externímu uložišti byl povolen!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Restartujte hru, aby bylo možné správně přečíst soubory " "\f[c:#9e7056]Jazz Jackrabbit 2\f[c:#337233]." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Tato hra vyžaduje originální soubory \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Ujistěte se, že soubory Jazz Jackrabbit 2 jsou přístupné v tomto umístění:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Povolit přístup k externímu uložišti" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Přenastavit ovládání pro hráče {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Přenastavit ovládání" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Dotykové ovládací prvky" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Přepínat běhání" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Popisky tlačítek na gamepadu" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibrace ovladače" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Rozšířená podpora ovladačů PlayStation™" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Nativní tlačítko Zpět" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnostika ovládání" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Obnovit výchozí" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Ovládání" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Silné" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Slabé" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Zakázáno" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Povoleno" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Postava" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Herní režim" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Vytvořit server" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Bitva" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Týmová bitva" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Závod" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Týmový závod" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Lovci pokladů" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Týmoví lovci pokladů" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Boj o vlajku" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Kooperace" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Hrát vlastní úrovně" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nebyly nalezeny žádné vlastní úrovně!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Vytvořit server z playlistu" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Vytvořit soukromý server" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Vytvořit veřejný server" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nebyly nalezeny žádné epizody!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Smazat postup" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Nejprve musíte dokončit \"{}\"!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Epizoda je zamčena!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Původní" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Chci hrát hru takovou, jaká byla." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Nové" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Chci hrát hru s něčím novým." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Vítejte v reimplementaci hry \f[c:#9e7056]Jazz Jackrabbit 2\f[/c]!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Hra" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Vylepšení" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Můžete si zvolit preferovaný styl hraní.\n" "Tuto možnost můžete kdykoliv změnit v \f[c:#707070]{}\f[/c] > \f[c:#707070]{}" "\f[/c] > \f[c:#707070]{}\f[/c].\n" "Pro více informací můžete navštívit {} a  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Nová hratelnost" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Nový HUD" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Nové hlavní menu" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Povolit šplhání" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Kolečko zbraní" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Zde můžete zapnout jednotlivá vylepšení přidaná do hry." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Povoleno s počtem munice" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Jazyk" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Skripty" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 msgid "Continuous Jump" msgstr "Nepřetržitě skákat" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Přepnout na novou zbraň" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Povolit podvádění (cheaty)" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Přepisovat dokončení epizody" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Otevřít složku \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Obnovit cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Ano" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Ne" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Pouze bez podvádění" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Pouze při vyšším skóre" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Vždy" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Režim škálování" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Rozlišení" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Celá obrazovka" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antialiasing" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Dithering na pozadí" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Kvalita vody" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Zobrazit stopy hráčů" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Preferované rozdělení obrazovky" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Preferovat oddálení" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Zachovat poměr stran u filmů" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Nezarovnaný pohled" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Metrika výkonu" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafika" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Nízká" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Vysoká" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertikální" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horizontální" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Povoleno \f[c:#d0705d](Experimentální)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Krátký" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Podrobný" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Síň slávy pro \f[c:#d0705d]Základní hru\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Síň slávy pro \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "Vyberte soubory vaší původní hry pro odemknutí dalších epizod" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Zpracování {} souboru..." msgstr[1] "Zpracování {} souborů..." msgstr[2] "Zpracování {} souborů..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Čekání na soubory..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Nebyly vybrány žádné soubory!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Nebyly importovány žádné nové epizody!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Nebyly detekovány žádné gamepady!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Vybrat herní režim" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Zvuky" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Uživatelský profil" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Pokračovat" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Sledovat" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Uložit & Ukončit" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Odpojit & Ukončit" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Připojit k serveru" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Zpracování souborů ve složce \f[c:#9e7056]\"Source\"\f[/c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Nově přidané úrovně a epizody budou brzy k dispozici." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Doleva" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Doprava" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Nahoru" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Dolů" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Prorazit ze vzduchu" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Střelba" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Skok" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Běh" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Změnit zbraň" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Zpět" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Zobrazit konzoli" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Zbraň {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Stiskněte tlačítko nebo klávesu pro přiřazení" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "pro odstranění přiřazení" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Žádný / Pixel-perfect" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monochrome" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Vybrat režim škálování" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Nebyly nalezeny žádné servery, ale stále hledám!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Pro pokračování stiskněte tlačítko \f[c:#d0705d]Střelba\f[/c]" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Hlavní hlasitost" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Hlasitost zvukových efektů" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Hlasitost hudby" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Počet lokálních hráčů" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Obtížnost" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Spustit hru" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Lehká" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Střední" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Těžká" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Pozice zón dotyku můžete upravit přetažením." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integrace aplikace Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Jméno hráče" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Unikátní ID hráče" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Body: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Hra začne za {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "Čekání na {} dalšího hráče" msgstr[1] "Čekání na {} další hráče" msgstr[2] "Čekání na {} dalších hráčů" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Najděte východ!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nelze načíst zadanou úroveň!\f[/c]\n" "\n" "\n" "Ujistěte se, že všechny potřebné soubory\n" "jsou přístupné, a zkuste to znovu." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nelze obnovit uložený stav!\f[/c]\n" "\n" "\n" "Ujistěte se, že všechny potřebné soubory\n" "jsou přístupné, a zkuste to znovu." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Nepojmenovaný server" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" #~ msgid "Unknown" #~ msgstr "Neznámý" #~ msgid "Server Name" #~ msgstr "Název serveru" #~ msgid "Server Port" #~ msgstr "Port serveru" #~ msgid "or" #~ msgstr "nebo" deathkiller-jazz2-native-2a7ccef/Content/Translations/de.mo000066400000000000000000001074431512772601700241410ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@SSUVl$VVY/WXWWktXXYoQZZo[\r\>]]t^ _A__q``pahbb$ccd^d&d/d;e4Vee-ee&eee ff*f 3fTf]fnf}fff f.ff f g g g(gBgagrg g g g ggggg g*g*hIhXh(nhhh:h6h#i ,i7iKihiziiiii i,i$i j5j.Pjjjj2jjjj j k"kS*k~kk kkkAk ll)lIlal|l'l2llmm #mDm Vmwmm m m mmm.m:!n>\n/nnnn nno%o@o!Tovp%ppp pp pqqq)q =qGqZqQsqqqq qqqq r $r/rh@rrrr r rrs*s=sPsUsdsms:sss ssEs.t@1trtKMu"uuuu&v!5v%Wv}vvv v"vw8)w5bwww1w9w/xDx6\x6xxBx,y&By?iyy"y-yGz:^z=zzz@{7S{{${{\{J9|B|+|.|"}CB}/}(}0}9~0J~>{~+~?~)&AP@,8-W$`" 4.8c56ҁ ;"7^5;̂(#1<U1 ă2 19k5~ C&Q1x67)1C;u;@>1ZNDۇ@ Fa:0FC[EpVKrH!%)FOsAg4;Fp+A3?s(-9?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: 2025-11-09 15:56+0100 Last-Translator: Language-Team: Language: de MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SourceCharset: UTF-8 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Das Spiel beginnt in Kürze! Gewinner ist {} [c:#337233]Starte das Spiel neu, um die [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] Dateien korrekt zu lesen. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Authentifizierung fehlgeschlagen. Kontaktiere die Server-Administratoren für weitere Informationen. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Ungültiger Parameter angegeben. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Ungültiges Passwort angegeben. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Ungültiger Spielername angegeben. Bitte überprüfe dein Profil und versuche es erneut. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Server ist voll. Bitte versuche es später erneut. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Server ist nicht in einem Zustand, in dem er deine Anfrage verarbeiten kann. Bitte versuche es in einigen Sekunden erneut. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Server benötigt einen Drittanbieter für die Authentifizierung. Kontaktiere die Server-Administratoren für weitere Informationen. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Der Server antwortet nicht auf die Verbindungsanfrage. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Dieser Client ist nicht auf der Server-Whitelist. Kontaktiere die Server-Administratoren für weitere Informationen. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Dein Client enthält das Level "{}" nicht. Bitte lade die benötigten Dateien herunter und versuche es erneut. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Dein Client enthält nicht die erforderlichen Dateien. Bitte lade die benötigten Dateien herunter und versuche es erneut. [c:#704a4a]Verbindung zum Server nicht möglich! [/c] Deine Client-Version ist nicht mit dem Server kompatibel. [c:#704a4a]Server kann nicht erstellt werden! [/c] Die Playlist ist nicht richtig konfiguriert. Bitte überprüfe die Serverkonfiguration und versuche es erneut. [c:#704a4a]Server kann nicht erstellt werden! [/c] Bitte überprüfe, dass kein anderer Server auf diesem Port läuft und versuche es erneut. [c:#704a4a]Angegebenes Level kann nicht geladen werden! [/c] Stelle sicher, dass alle notwendigen Dateien zugänglich sind und versuche es erneut. [c:#704a4a]Gespeicherter Zustand kann nicht fortgesetzt werden! [/c] Stelle sicher, dass alle notwendigen Dateien zugänglich sind und versuche es erneut. [c:#704a4a]Verbindung wurde geschlossen! [/c] Cheaten erkannt. [c:#704a4a]Verbindung wurde geschlossen! [/c] Server wird für Wartungsarbeiten heruntergefahren. Bitte versuche es später erneut. [c:#704a4a]Verbindung wurde geschlossen! [/c] Server wird für Neukonfiguration heruntergefahren. Bitte versuche es später erneut. [c:#704a4a]Verbindung wurde geschlossen! [/c] Server wird für ein Update heruntergefahren. Bitte überprüfe deine Client-Version und versuche es in einer Minute erneut. [c:#704a4a]Verbindung wurde geschlossen! [/c] Server wird heruntergefahren. Bitte versuche es später erneut. [c:#704a4a]Verbindung wurde geschlossen! [/c] Der Server hat dich aufgrund von Inaktivität getrennt. [c:#704a4a]Verbindung wurde geschlossen! [/c] Du wurdest vom Server [c:#725040]gebannt [/c]. Kontaktiere die Server-Administratoren für weitere Informationen. [c:#704a4a]Verbindung wurde geschlossen! [/c] Du wurdest vom Server [c:#907050]gekickt [/c]. Kontaktiere die Server-Administratoren für weitere Informationen. [c:#704a4a]Verbindung wurde verloren! [/c] Bitte versuche es erneut und falls das Problem weiterhin besteht, überprüfe deine Netzwerkverbindung. [c:#704a4a]Dieses Spiel benötigt die originalen [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] Dateien! [c:#d0705d]{} [/c] hat sich verbunden [c:#d0705d]{} [/c] hat die Verbindung getrennt [c:#d0705d]{} [/c] wurde von [c:#d0705d]{} [/c] geröstet [c:#d0705d]{} [/c] wurde von der Umgebung geröstetÜberZugriff auf externen Speicher wurde gewährt!Cheats erlaubenZugriff auf externen Speicher erlaubenImmerKantenglättungZurückHintergrund-DitheringSchlacht"Source"-Verzeichnis durchsuchenStampfenCRT-AperturmaskeCRT-ScanlinienCRT-SchattenmaskeFlagge erobernWaffe wechselnCharakterCheats sind im aktuellen Kontext nicht erlaubtMit Server verbindenFortsetzenMitwirkendeSteuerungKooperationPrivaten Server erstellenÖffentlichen Server erstellenServer erstellenServer aus Playlist erstellenDetailliertEntwicklerSchwierigkeitDeaktiviertTrennen & BeendenDiscord-IntegrationRunterLeichtAktiviertAktiviert [c:#d0705d](Experimentell) [/c]Aktiviert mit MunitionsanzeigeVerbesserungenEpisode ist gesperrt!Erweiterte PlayStation™ UnterstützungFinde den Ausgang!FeuernFür weitere Informationen besuche die offizielle Website:Für weitere Informationen besuche {} und  Discord!VollbildSpielmodusSpiel startet in {}Gamepad-TastenbeschriftungenGamepad-VibrationGameplayGrafikSchwerHochNur bei höherer PunktzahlBestenlisteBestenliste für [c:#d0705d]Basisspiel [/c]Bestenliste für [c:#d0705d]{} [/c]HorizontalIch möchte das Spiel so spielen, wie es früher war.Ich möchte das Spiel mit etwas Neuem spielen.Episoden importierenEingabe-DiagnoseSpringenSeitenverhältnis in Zwischensequenzen beibehaltenSpracheKanten kletternLinksKlassischLevel "{}" initialisiertNiedrigStelle sicher, dass die Jazz Jackrabbit 2 Dateien im folgenden Pfad vorhanden sind:GesamtlautstärkeMittelMonochromMusik-LautstärkeNative Zurück-TasteNeu hinzugefügte Level und Episoden werden bald verfügbar sein.NeinNur ohne CheatsKeine benutzerdefinierten Level gefunden!Keine Episode gefunden!Keine Dateien ausgewählt!Keine Gamepads erkannt!Keine neuen Episoden wurden importiert!Keine Server gefunden, aber die Suche läuft noch!Keine / Pixel-genauAnzahl lokaler SpielerOptionenEpisodenabschluss überschreibenLeistungsmetrikenBenutzerdefinierte Level spielenOnline spielenShareware-Demo spielenStory spielenSpielernamePunkte: {}Herauszoomen bevorzugenBevorzugter SplitscreenDrücke [c:#d0705d]Feuern [/c] zum FortfahrenDrücke eine beliebige Taste oder einen Knopf zum ZuweisenVerarbeite Dateien im [c:#9e7056]"Source" [/c] Verzeichnis...Verarbeite {} Datei...Verarbeite {} Dateien...BeendenRennenRazer Chroma™ÜberarbeitetÜberarbeitetes GameplayÜberarbeitetes HUDÜberarbeitetes HauptmenüCache aktualisierenNeuimplementierung des 1998 erschienenen Spiels [c:#9e7056]Jazz Jackrabbit 2 [/c]. Unterstützt verschiedene Versionen des Spiels (Shareware Demo, Holiday Hare '98, The Secret Files und Christmas Chronicles). Außerdem werden teilweise einige Funktionen der JJ2+ Erweiterung unterstützt.Steuerung neu belegenSteuerung für Spieler {} neu belegenSkalierungsmodusAuf Standard zurücksetzenAuflösungEpisode neu startenFortsetzenRechtsLaufenEffekt-LautstärkeSpeichern & BeendenSkriptingSpielmodus wählenSkalierungsmodus wählenWähle Dateien deines Originalspiels aus, um zusätzliche Episoden freizuschaltenKurzSpielerspuren anzeigenSoundZuschauenStartenStarkZu neuer Waffe wechselnTeamschlachtTeamrennenTeam-SchatzsucheDieses Projekt verwendet die modifizierte [c:#9e7056]nCine [/c] Spiel-Engine und folgende Bibliotheken:Konsole umschaltenLaufen umschaltenTouch-SteuerungÜbersetzerSchatzsucheNicht ausgerichteter ViewportEindeutige Spieler-IDUnbekannter BefehlUnbenannter ServerHochBenutzerprofilVertikalWarte auf Dateien...Warte auf {} weiteren SpielerWarte auf {} weitere SpielerWasserqualitätSchwachWaffenradWaffe {}Willkommen zur [c:#9e7056]Jazz Jackrabbit 2 [/c] Neuimplementierung!JaDu kannst die Position der Touch-Zonen per Drag & Drop anpassen.Du kannst deinen bevorzugten Spielstil wählen. Diese Option kann jederzeit unter [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c] geändert werden. Für weitere Informationen besuche {} und  Discord!Du kannst Verbesserungen aktivieren, die diesem Remake hinzugefügt wurden.Du musst zuerst "{}" abschließen! Drachen leben in Burbank. Finde das Erdhörnchen. Mark trägt Slips. Huh Hah! Nick liebt Glänzendes. Schon immer! Spaz hat den Dopefish gefressen. Hüte dich vor Kettensägen-Schmalz. Gib Mark keinen Burrito. Schick Nigel eine Green Card. Schick Tim neue Socken. Vorsicht vor fallenden Gegnern. Craig ist immer noch ein Trottel! Geheim-Level Zeit!!! Stampfe auf eine Silberkiste um deinen Weg freizumachen Kisten können auch Plattformen erscheinen lassen... Leh ist ein Camper Schmelze die Feder... Ein Flammenwerfer funktioniert gut gegen Käfer. Fallende Felsbrocken können Kopfschmerzen verursachen. Gute Nacht, Kumpel! Es lebe das Eis-Level. Was zum...? Aaaah! Nein! Das ist NOCH NICHT vorbei! Sammle Münzen, um Bonus-Warp-Geräte zu aktivieren. Hier gibt es nichts zu sehen. Stangen wirbeln dich herum, damit du noch schneller sein kannst. Geheimer Schatzraum. Du hast einen Geheimbereich gefunden. Stampfe auf die Metallkiste, um Schlüsselblöcke zu öffnen! Käse ist dienstags grün. Craig ist der König der Trottel. Gute Arbeit! Jetzt schnapp dir Devan Shell! Drücke runter und springe unter diese Blöcke, um sie zu zerbrechen! Um die Königin zu besiegen, schieß sie von ihrem Sims. Um diese Blöcke zu durchtreten, drücke runter und springe! Stampfe, um hinauszukommen. Diese Feder ist eingefroren. Schilde geben dir für kurze Zeit unbegrenzte Spezialmunition. Stoppuhren verlängern die Lebensdauer eines Schildes. Super-duper Geheimnis. Dieser Schwarzgardist ist erledigt! Ach! Ich bin weg! Du kannst mich nicht besiegen, Jazz! Bereite dich darauf vor, meinem Superbot zu begegnen! Diese Blöcke sind Tempo-Blöcke. Renn mit voller Geschwindigkeit hinein! Drücke nach dem Springen erneut Springen für einen Spezialmove. Vorsicht vor scharfen Sachen. Sie tun weh. Blaue Edelsteine zählen als zehn Edelsteine. Karotten geben dir Gesundheit. Checkpoints speichern deine Position, wenn du ein Leben verlierst. Sammle Münzen, um Bonusräume freizuschalten. Sammle Edelsteine für ein Extraleben. Sammle Goodies für Punkte und Überraschungen. Gute Arbeit. Vergiss nicht, nach Geheimnissen zu suchen. Grüne Edelsteine zählen als fünf Edelsteine. Jetzt bist du bereit zu spielen. Viel Glück und viel Spaß. Rote Edelsteine zählen als ein Edelstein. Geheimnisse gibt es zuhauf in Jazz 2. Untersuche die Wände. Manche Wände können beschossen werden. Willkommen bei Jazz Jackrabbit 2. Dies ist ein Trainingslevel. Drücke in der Luft nach unten, um mit dem Hintern zu stampfen. Stampfe auf den Kanaldeckel! Rauchringe werden dich schwindelig machen. Schlag Nigel nicht beim Billard. Du wurdest gewarnt. :) Finde die Kiste, um deinen Weg freizumachen. Keine Belohnungen für Ungeduldige. Nur Spaz kann zum Raum oben links gelangen. Er braucht vielleicht etwas, worauf er stehen kann. Heutige Vorhersage: Starke Winde! Willkommen bei Jazz Jackrabbit 2: The Secret Files! Du kannst hier nicht stampfen, also geh hoch und herum!Zu viele Schokoladeneier können dich krank machen :p Ein Weg führt zu Reichtum. Ein Weg führt zum Kampf. Schräger Tunneleingang Um die oberen Tunnel zu erreichen, finde den Zugangs-Warp. Finde die Kiste, damit deine Kletterblöcke erscheinen Nur wer doppelt springen kann, kommt an die Goodies! Diese Kiste zu zerstampfen befreit auch ein paar Gegner :) Aber du brauchst einen Weg nach oben... Betritt das Haus mit Vorsicht..... Silberkisten können unter Wasser nicht zerstört werden... Kisten zerstampfen kann gut und schlecht sein... Wasserstand-Kontrollkiste oben. Michelle, Ich werde dich immer und ewig lieben :) Sprung nicht geschafft, was? :) Spring so weit nach rechts, wie du nur kannst... Nimm die Dächer! Der Himmel wird diejenigen belohnen, die stampfen... Nutze deine Spezialbewegungen, um die Klimaanlagen hochzukommen! Für Jazz: drücke Ducken und Springen. Für Spaz: drücke zweimal Springen! Gut gemacht! 20 Münzen zusammeln ist lohnender... Finde die Kiste und die Edelsteine gehören dir! Federn funktionieren nicht, wenn sie gefroren sind... ACHTUNG! Rabenschwärme können sehr gefährlich sein. Wähle einen Deckel und stampf drauflos! Finde die Kiste, um die Blöcke zu entfernen.... Gehe zu www.project2.com benutze das Passwort: BUNNYLOVER Hi GeoBunny :) Um die Blöcke zu entfernen, schau zum höchsten Gebäude. Münzen gewähren dir Zugang zu Warps, die später erscheinen. Schieß auf diese Blöcke! Manche Kisten enthalten Bomben oder Bösewichte! Stampfe an der richtigen Stelle und du findest vielleicht eine Überraschung! Um diesen Bereich zu passieren, zerstampfe die geheime Metallkiste. Drücke in der Luft nach unten, um mit dem Hintern zu stampfen. Du brauchst zwanzig Münzen, um durch diesen geheimen Warp zu gehen! Ein Flammenwerfer funktioniert gut gegen gemeine Käfer. Rauchringe werden dich sehr schwindelig machen! Du brauchst zwanzig Münzen, um durch diesen geheimen Warp zu gehen! Hüte dich vor der Hexe! Sie kann dich in einen Frosch verwandeln. Wenn du in einen Frosch verwandelt wurdest, kann Eva Earlong helfen! Du hast es geschafft! Das ist das Ende der Shareware-Version. Schau dir jetzt die Bestellinfo an für M E H R!zum Entfernen der Zuweisung Frohe Festtage von Epic MegaGames, Orange Games und Project 2 Interactive! Manche Blöcke können nur mit einer bestimmten Waffe zerstört werden. Pass auf die Stacheln unten auf! Willkommen bei Christmas Chronicles! Du kannst durch einige der Schwachstellen in den Wegen durchstampfen! Hallo, Schweinchen! - Poopy Kassi Nicole: Eine Million Dinge möchte ich dir sagen, aber meine Worte führen immer zum gleichen Ende: Ich liebe dich, ich liebe dich. Michelle: Du hast mein Leben auf so viele Arten verändert. Ich widme dir dieses Projekt. Ich liebe dich so sehr. Edelsteinpfad-Eingang. Klettere auf die Baumwipfel, um die Eingangskiste zu finden und zu zerstampfen. Edelsteinpfad-Eingangskiste. Die Blöcke über dir zu schlagen kann sich lohnen. Du kannst durch einige der Schwachstellen in den Wegen durchstampfen! Du kannst auf einigen Bäumen oben stehen! Verliere nicht den Halt! Sie verlassen jetzt Burrowsville. Bitte besuchen Sie uns wieder. Passwort: xmasbunny Bitte besuche www.project2.com Bitte benutze dein TNT weise. Robert und Craig: Federn sind SUPER! :) Diese Brücke sieht nicht sehr sicher aus... Willkommen in Burrowsville. Bitte fahren Sie vorsichtig.deathkiller-jazz2-native-2a7ccef/Content/Translations/de.po000066400000000000000000002036651512772601700241470ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:56+0100\n" "PO-Revision-Date: 2025-11-09 15:56+0100\n" "Last-Translator: \n" "Language-Team: \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SourceCharset: UTF-8\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz hat den Dopefish gefressen." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Finde das Erdhörnchen." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Drachen leben in Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark trägt Slips. \n" "Huh Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick liebt Glänzendes. \n" "Schon immer!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Schick Tim neue Socken." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Schick Nigel eine Green Card." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Hüte dich vor Kettensägen-Schmalz." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Gib Mark keinen Burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Vorsicht vor fallenden Gegnern." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig ist immer noch ein Trottel!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Geheim-Level Zeit!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Stampfe auf eine Silberkiste\n" "um deinen Weg freizumachen" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Kisten können auch Plattformen erscheinen lassen..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Schmelze die Feder..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh ist ein Camper" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Fallende Felsbrocken können \n" "Kopfschmerzen verursachen." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Ein Flammenwerfer funktioniert\n" "gut gegen Käfer." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Es lebe das Eis-Level." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Gute Nacht, Kumpel!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Was zum...? Aaaah! Nein! \n" "Das ist NOCH NICHT vorbei!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Stangen wirbeln dich herum, damit\n" " du noch schneller sein kannst." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Du hast einen Geheimbereich gefunden." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Geheimer Schatzraum." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Hier gibt es nichts zu sehen." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Sammle Münzen, um \n" "Bonus-Warp-Geräte zu aktivieren." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "Käse ist dienstags grün." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig ist der König der Trottel." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Um die Königin zu besiegen, \n" "schieß sie von ihrem Sims." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Gute Arbeit! \n" "Jetzt schnapp dir Devan Shell!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Um diese Blöcke zu durchtreten,\n" "drücke runter und springe!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Drücke runter und springe unter \n" "diese Blöcke, um sie zu zerbrechen!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Stampfe auf die Metallkiste, \n" "um Schlüsselblöcke zu öffnen!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Stampfe, um hinauszukommen." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Diese Feder ist eingefroren." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Super-duper Geheimnis." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Schilde geben dir für kurze Zeit \n" "unbegrenzte Spezialmunition." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Stoppuhren verlängern\n" "die Lebensdauer eines Schildes." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Dieser Schwarzgardist ist erledigt!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Du kannst mich nicht besiegen, Jazz!\n" "Bereite dich darauf vor, meinem Superbot zu begegnen!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Ach! Ich bin weg!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Diese Blöcke sind Tempo-Blöcke.\n" "Renn mit voller Geschwindigkeit hinein!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Willkommen bei Jazz Jackrabbit 2. \n" " Dies ist ein Trainingslevel." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Sammle Goodies für\n" "Punkte und Überraschungen." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Drücke nach dem Springen erneut\n" "Springen für einen Spezialmove." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Manche Wände können beschossen werden." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Drücke in der Luft nach unten,\n" "um mit dem Hintern zu stampfen." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Geheimnisse gibt es zuhauf in Jazz 2. \n" " Untersuche die Wände." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Gute Arbeit. Vergiss nicht,\n" "nach Geheimnissen zu suchen." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Sammle Edelsteine für \n" "ein Extraleben." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Rote Edelsteine zählen\n" "als ein Edelstein." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Grüne Edelsteine zählen\n" "als fünf Edelsteine." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Blaue Edelsteine zählen\n" "als zehn Edelsteine." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Karotten geben dir Gesundheit." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Checkpoints speichern deine\n" "Position, wenn du ein Leben verlierst." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Sammle Münzen, um\n" "Bonusräume freizuschalten." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Vorsicht vor scharfen Sachen.\n" "Sie tun weh." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Jetzt bist du bereit zu spielen.\n" " Viel Glück und viel Spaß." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Stampfe auf den Kanaldeckel!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Rauchringe werden \n" "dich schwindelig machen." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Du kannst hier nicht stampfen,\n" "also geh hoch und herum!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Keine Belohnungen für\n" "Ungeduldige." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Heutige Vorhersage: Starke Winde!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Finde die Kiste,\n" "um deinen Weg freizumachen." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Willkommen bei\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Nur Spaz kann zum\n" "Raum oben links gelangen.\n" "Er braucht vielleicht etwas,\n" "worauf er stehen kann." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Schlag Nigel nicht beim Billard.\n" "Du wurdest gewarnt. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Zu viele Schokoladeneier\n" "können dich krank machen :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Schräger Tunneleingang" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Um die oberen Tunnel zu erreichen,\n" "finde den Zugangs-Warp." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Ein Weg führt zu Reichtum.\n" "Ein Weg führt zum Kampf." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Nur wer doppelt springen kann,\n" "kommt an die Goodies!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Finde die Kiste, damit\n" "deine Kletterblöcke erscheinen" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Diese Kiste zu zerstampfen\n" "befreit auch ein paar Gegner :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Betritt das Haus mit Vorsicht....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Silberkisten können unter Wasser nicht zerstört werden..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Wasserstand-Kontrollkiste oben." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Kisten zerstampfen kann gut und schlecht sein..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Aber du brauchst einen Weg nach oben..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Ich werde dich immer und ewig lieben :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Nimm die Dächer!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Der Himmel wird diejenigen belohnen,\n" "die stampfen..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Spring so weit nach rechts,\n" "wie du nur kannst..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Sprung nicht geschafft, was? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Gut gemacht!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Nutze deine Spezialbewegungen, um die Klimaanlagen hochzukommen!\n" "Für Jazz: drücke Ducken und Springen.\n" "Für Spaz: drücke zweimal Springen!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Finde die Kiste und die Edelsteine gehören dir!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Federn funktionieren nicht, wenn sie gefroren sind..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "20 Münzen zusammeln ist lohnender..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Finde die Kiste, um die Blöcke zu entfernen...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "ACHTUNG! Rabenschwärme können\n" "sehr gefährlich sein." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Um die Blöcke zu entfernen,\n" "schau zum höchsten Gebäude." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Wähle einen Deckel und stampf drauflos!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Hi GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Gehe zu www.project2.com\n" "benutze das\n" "Passwort: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Schieß auf diese Blöcke!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Drücke in der Luft nach unten,\n" "um mit dem Hintern zu stampfen." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Um diesen Bereich zu passieren,\n" "zerstampfe die geheime Metallkiste." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Du brauchst zwanzig Münzen, um \n" "durch diesen geheimen Warp zu gehen!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Münzen gewähren dir Zugang zu \n" "Warps, die später erscheinen." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Stampfe an der richtigen Stelle und\n" "du findest vielleicht eine Überraschung!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Manche Kisten enthalten\n" "Bomben oder Bösewichte!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Du brauchst zwanzig Münzen, um \n" "durch diesen geheimen Warp zu gehen!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Ein Flammenwerfer funktioniert gut \n" "gegen gemeine Käfer." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Rauchringe werden dich\n" "sehr schwindelig machen!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Hüte dich vor der Hexe! Sie kann\n" "dich in einen Frosch verwandeln." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Wenn du in einen Frosch verwandelt wurdest,\n" "kann Eva Earlong helfen!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Du hast es geschafft! Das ist das Ende\n" " der Shareware-Version.\n" "Schau dir jetzt die Bestellinfo an\n" "für M E H R!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Pass auf die Stacheln unten auf!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Du kannst durch einige der\n" "Schwachstellen in den Wegen\n" "durchstampfen!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Manche Blöcke können nur\n" "mit einer bestimmten\n" "Waffe zerstört werden." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Willkommen bei Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Frohe Festtage von\n" "Epic MegaGames,\n" "Orange Games und\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Du kannst auf einigen\n" "Bäumen oben stehen!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Du kannst durch einige der\n" "Schwachstellen in den Wegen\n" "durchstampfen!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Die Blöcke über dir zu schlagen\n" "kann sich lohnen." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Edelsteinpfad-Eingangskiste." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Edelsteinpfad-Eingang.\n" "Klettere auf die Baumwipfel, um die\n" "Eingangskiste zu finden und zu zerstampfen." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Hallo, Schweinchen!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Du hast mein Leben auf so viele Arten verändert.\n" "Ich widme dir dieses Projekt.\n" "Ich liebe dich so sehr." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Eine Million Dinge möchte ich dir sagen,\n" "aber meine Worte führen immer zum gleichen Ende:\n" "Ich liebe dich, ich liebe dich." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Bitte benutze dein TNT weise." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Verliere nicht den Halt!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Willkommen in Burrowsville.\n" "Bitte fahren Sie vorsichtig." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Diese Brücke sieht nicht\n" "sehr sicher aus..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert und Craig:\n" "Federn sind SUPER! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Sie verlassen jetzt Burrowsville.\n" "Bitte besuchen Sie uns wieder." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Passwort: xmasbunny\n" "Bitte besuche\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Level \"{}\" initialisiert" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Für weitere Informationen besuche die offizielle Website:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Cheats sind im aktuellen Kontext nicht erlaubt" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Das Spiel beginnt in Kürze!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] wurde von \f[c:#d0705d]{}\f[/c] geröstet" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] wurde von der Umgebung geröstet" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] hat die Verbindung getrennt" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] hat sich verbunden" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "Gewinner ist {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Unbekannter Befehl" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Neuimplementierung des 1998 erschienenen Spiels \f[c:#9e7056]Jazz Jackrabbit " "2\f[/c]. Unterstützt verschiedene Versionen des Spiels (Shareware Demo, " "Holiday Hare '98, The Secret Files und Christmas Chronicles). Außerdem " "werden teilweise einige Funktionen der JJ2+ Erweiterung unterstützt." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Entwickler" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Mitwirkende" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Übersetzer" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Dieses Projekt verwendet die modifizierte \f[c:#9e7056]nCine\f[/c] Spiel-" "Engine und folgende Bibliotheken:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Story spielen" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Shareware-Demo spielen" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Episoden importieren" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Fortsetzen" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Online spielen" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Bestenliste" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Optionen" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Über" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Beenden" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Für weitere Informationen besuche {} und  Discord!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Zugriff auf externen Speicher wurde gewährt!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Starte das Spiel neu, um die \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] Dateien korrekt zu lesen." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Dieses Spiel benötigt die originalen \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a] Dateien!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Stelle sicher, dass die Jazz Jackrabbit 2 Dateien im folgenden Pfad " "vorhanden sind:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Zugriff auf externen Speicher erlauben" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Steuerung für Spieler {} neu belegen" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Steuerung neu belegen" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Touch-Steuerung" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Laufen umschalten" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Gamepad-Tastenbeschriftungen" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Gamepad-Vibration" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Erweiterte PlayStation™ Unterstützung" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Native Zurück-Taste" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Eingabe-Diagnose" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Auf Standard zurücksetzen" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Steuerung" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Stark" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Schwach" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Deaktiviert" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Aktiviert" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Charakter" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Spielmodus" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Server erstellen" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Schlacht" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Teamschlacht" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Rennen" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Teamrennen" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Schatzsuche" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Team-Schatzsuche" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Flagge erobern" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Kooperation" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Server kann nicht erstellt werden!\f[/c]\n" "\n" "\n" "Die Playlist ist nicht richtig konfiguriert.\n" "Bitte überprüfe die Serverkonfiguration und versuche es erneut." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Server kann nicht erstellt werden!\f[/c]\n" "\n" "\n" "Bitte überprüfe, dass kein anderer Server\n" "auf diesem Port läuft und versuche es erneut." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Benutzerdefinierte Level spielen" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Keine benutzerdefinierten Level gefunden!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Server aus Playlist erstellen" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Privaten Server erstellen" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Öffentlichen Server erstellen" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Keine Episode gefunden!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Episode neu starten" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Du musst zuerst \"{}\" abschließen!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episode ist gesperrt!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Klassisch" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Ich möchte das Spiel so spielen, wie es früher war." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Überarbeitet" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Ich möchte das Spiel mit etwas Neuem spielen." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "" "Willkommen zur \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] Neuimplementierung!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Gameplay" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Verbesserungen" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Du kannst deinen bevorzugten Spielstil wählen.\n" "Diese Option kann jederzeit unter \f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/" "c] > \f[c:#707070]{}\f[/c] geändert werden.\n" "Für weitere Informationen besuche {} und  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Überarbeitetes Gameplay" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Überarbeitetes HUD" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Überarbeitetes Hauptmenü" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Kanten klettern" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Waffenrad" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "" "Du kannst Verbesserungen aktivieren, die diesem Remake hinzugefügt wurden." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Aktiviert mit Munitionsanzeige" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Sprache" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Skripting" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Zu neuer Waffe wechseln" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Cheats erlauben" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Episodenabschluss überschreiben" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "\"Source\"-Verzeichnis durchsuchen" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Cache aktualisieren" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Ja" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Nein" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Nur ohne Cheats" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Nur bei höherer Punktzahl" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Immer" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Skalierungsmodus" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Auflösung" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Vollbild" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Kantenglättung" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Hintergrund-Dithering" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Wasserqualität" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Spielerspuren anzeigen" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Bevorzugter Splitscreen" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Herauszoomen bevorzugen" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Seitenverhältnis in Zwischensequenzen beibehalten" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Nicht ausgerichteter Viewport" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Leistungsmetriken" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafik" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Niedrig" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Hoch" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertikal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horizontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Aktiviert \f[c:#d0705d](Experimentell)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Kurz" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detailliert" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Bestenliste für \f[c:#d0705d]Basisspiel\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Bestenliste für \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Wähle Dateien deines Originalspiels aus, um zusätzliche Episoden " "freizuschalten" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Verarbeite {} Datei..." msgstr[1] "Verarbeite {} Dateien..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Warte auf Dateien..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Keine Dateien ausgewählt!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Keine neuen Episoden wurden importiert!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Keine Gamepads erkannt!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Spielmodus wählen" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Sound" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Benutzerprofil" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Fortsetzen" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Zuschauen" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Speichern & Beenden" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Trennen & Beenden" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Mit Server verbinden" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Verarbeite Dateien im \f[c:#9e7056]\"Source\"\f[/c] Verzeichnis..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Neu hinzugefügte Level und Episoden werden bald verfügbar sein." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Links" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Rechts" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Hoch" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Runter" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Stampfen" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Feuern" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Springen" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Laufen" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Waffe wechseln" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Zurück" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Konsole umschalten" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Waffe {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Drücke eine beliebige Taste oder einen Knopf zum Zuweisen" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "zum Entfernen der Zuweisung" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Keine / Pixel-genau" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT-Scanlinien" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT-Schattenmaske" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT-Aperturmaske" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monochrom" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Skalierungsmodus wählen" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Keine Server gefunden, aber die Suche läuft noch!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Drücke \f[c:#d0705d]Feuern\f[/c] zum Fortfahren" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Gesamtlautstärke" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Effekt-Lautstärke" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Musik-Lautstärke" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Anzahl lokaler Spieler" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Schwierigkeit" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Starten" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Leicht" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Mittel" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Schwer" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Du kannst die Position der Touch-Zonen per Drag & Drop anpassen." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Discord-Integration" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Spielername" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Eindeutige Spieler-ID" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Punkte: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Spiel startet in {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "Warte auf {} weiteren Spieler" msgstr[1] "Warte auf {} weitere Spieler" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Finde den Ausgang!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Angegebenes Level kann nicht geladen werden!\f[/c]\n" "\n" "\n" "Stelle sicher, dass alle notwendigen Dateien\n" "zugänglich sind und versuche es erneut." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Gespeicherter Zustand kann nicht fortgesetzt werden!\f[/c]\n" "\n" "\n" "Stelle sicher, dass alle notwendigen Dateien\n" "zugänglich sind und versuche es erneut." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Unbenannter Server" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Ungültiger Parameter angegeben." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Deine Client-Version ist nicht mit dem Server kompatibel." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Authentifizierung fehlgeschlagen.\n" "Kontaktiere die Server-Administratoren für weitere Informationen." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Ungültiges Passwort angegeben." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Ungültiger Spielername angegeben.\n" "Bitte überprüfe dein Profil und versuche es erneut." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Dieser Client ist nicht auf der Server-Whitelist.\n" "Kontaktiere die Server-Administratoren für weitere Informationen." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Server benötigt einen Drittanbieter für die Authentifizierung.\n" "Kontaktiere die Server-Administratoren für weitere Informationen." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Server ist voll.\n" "Bitte versuche es später erneut." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Server ist nicht in einem Zustand, in dem er deine Anfrage verarbeiten " "kann.\n" "Bitte versuche es in einigen Sekunden erneut." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Server wird heruntergefahren.\n" "Bitte versuche es später erneut." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Server wird für Wartungsarbeiten heruntergefahren.\n" "Bitte versuche es später erneut." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Server wird für Neukonfiguration heruntergefahren.\n" "Bitte versuche es später erneut." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Server wird für ein Update heruntergefahren.\n" "Bitte überprüfe deine Client-Version und versuche es in einer Minute erneut." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]Verbindung wurde verloren!\f[/c]\n" "\n" "\n" "Bitte versuche es erneut und falls das Problem weiterhin besteht,\n" "überprüfe deine Netzwerkverbindung." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Der Server antwortet nicht auf die Verbindungsanfrage." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Du wurdest vom Server \f[c:#907050]gekickt\f[/c].\n" "Kontaktiere die Server-Administratoren für weitere Informationen." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Du wurdest vom Server \f[c:#725040]gebannt\f[/c].\n" "Kontaktiere die Server-Administratoren für weitere Informationen." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Cheaten erkannt." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Dein Client enthält nicht die erforderlichen Dateien.\n" "Bitte lade die benötigten Dateien herunter und versuche es erneut." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]Verbindung wurde geschlossen!\f[/c]\n" "\n" "\n" "Der Server hat dich aufgrund von Inaktivität getrennt." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Verbindung zum Server nicht möglich!\f[/c]\n" "\n" "\n" "Dein Client enthält das Level \"{}\" nicht.\n" "Bitte lade die benötigten Dateien herunter und versuche es erneut." deathkiller-jazz2-native-2a7ccef/Content/Translations/es.mo000066400000000000000000000760571512772601700241660ustar00rootroot00000000000000", <P_Q^egnnlrJv4\#|UX+,1 ^ k     ( 6 )@ j |        '!+! C!P!c!!1! ! !!!!!" "" "")-""W" z"."+"""##'#0#?#D#K#b#@f# ## # ##7#$"$1$H$Z$r$$&$$$$%$%8%K% _%j%z%'%!%=%2&K&P&U&e&n& && &&'' '' '((($( (( 3( ?(I(Z(@n(((((( ( (V(I) X)c) r) ~)))))) )) ) )?):*<>*{*;@+|+(+ +,+.,&@,+g,),(,#,, -*7-&b-A->-% .(0.6Y.9..&.</AN/'/D/(/+&0@R0/0)060L$1?q1F1,1)%2SO2F2(223(F3Lo3N3E 40Q4,4(4>4450L59}575.5A6*`6<6'6I6D:7.737D76'8C^8d819C99<}9F9I:+K:Ew:F:J;CO;:;6;@<;F<5<D<.<F,=(s=@=}=[>:x>8>3>A ?0b?8?A? @E/@Cu@$@6@HAA^AEAIA?0B6pBIB@BD2CrwCC^CF^D0D1DQE+ZEEvFZF'F@ GQKG;G&G=HB>H,H3H4H@IXIp`K~KPLsLtjMMoNOOPmPPQ~@RdR $S3.SbS)sSSSSSSSSTT;TRT fT sT.}TT T T T TT TU U "U0UIUOU VU*aU"UUU,UU.V2V DV"RVuV V VVVVV5V-W CW&NW%uWWWW-WWX X"X)XAXWFXXXXXXFX5Y8Y&JYqYYY$Y4YZ3ZQZ ZZ{ZZZZZZ.[+4[C`[2[[[[ [[\0\J\Y\r](]]] ]]]]^ ^ ^ 0^:^Q^Oo^^^^^^^ _b____ ____` ``3`D`K`Z`Ab``J``<a b&bFb#[b.bb!bb$ c"0c#Sc"wcc>c=c3dGd/bd:ddd7eB9e|eCee!eMf_f}f2f>f0g:9gtg!gZg<hKh$fhhLhNhOAi$i.ii7j6;j4rj7j(j/k58k+nk9k$kHk=Bl0l%l>l#m2:m`mm7m9n;@n>|n>nnLo0doIoEo6%p"\p@p-p;p.*q"Yq)|qq-qqwr,r'r9r<s?Rs*s6ss4t=;tyt4tTt=uD[uAu0u,vA@v7v;vrviwQwAw"x%=xMcxxzx\Eyly)zC9zM}z8z{.{:M{{5{${3{L g|&4!dTR k !6r ,$la:9fty. [2qYW7x* ev( `O3Km #_D/HiBcE+X%nZ);h^\-oz8s]QSbUN?0 J~}p "@1{GVFPw >CMu='5I"j A< [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files!AboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate ServerDetailedDevelopersDifficultyDisabledDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFireFor more information, visit the official website:FullscreenGame ModeGamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay Shareware DemoPlay StoryPrefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsStartStrongTeam BattleTeam RaceThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnknown commandUpVerticalWaiting for files...Water QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: enfeitizador Language-Team: enfeitizador Language: es MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp [c:#337233]Reinicia el juego para leer los ficheros de [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] correctamente. [c:#704a4a]No se puede conectar al servidor! [/c] La capacidad del servidor está completa. Inténtalo de nuevo más tarde. [c:#704a4a]No se puede conectar al servidor! [/c] El servidor non está en un estado que pueda procesar tu solicitud. Inténtalo de nuevo dentro de unos segundos. [c:#704a4a]No se puede conectar al servidor! [/c] El servidor no está respondiendo a la petición de conexión. [c:#704a4a]No se puede conectar al servidor! [/c] La versión de tu cliente no es compatible con la del servidor. [c:#704a4a]No se puede cargar el nivel especificado! [/c] Asegúrate que todos los ficheros necesarios son accesibles e inténtalo de nuevo. [c:#704a4a]No se puede continuar la partida guardada! [/c] Asegúrate que todos los ficheros necesarios son accesibles e inténtalo de nuevo. [c:#704a4a]La conexión se ha cerrado! [/c] El servidor se está apagando para mantenimiento. Inténtalo de nuevo más tarde. [c:#704a4a]La conexión se ha cerrado! [/c] El servidor se está apagando para reconfigurarse. Inténtalo de nuevo más tarde. [c:#704a4a]La conexión se ha cerrado! [/c] El servidor se está apagando por una actualización. Inténtalo de nuevo más tarde. [c:#704a4a]La conexión se ha cerrado! [/c] El servidor se está apagando. Inténtalo de nuevo más tarde. [c:#704a4a]La conexión se ha cerrado! [/c] Has sido [c:#725040]baneado [/c] del servidor. Contacta con la administración del servidor para más información. [c:#704a4a]La conexión se ha cerrado! [/c] Has sido [c:#907050]expulsado [/c] del servidor. Contacta con la administración del servidor para más información. [c:#704a4a]La conexión se ha perdido! [/c] Inténtalo de nuevo y si el problema persiste, comprueba tu conexión a la red. [c:#704a4a]Este juego requiere los ficheros del [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] original!Acerca de¡Se ha concedido acceso al almacenamiento externo!Permitir trampasPermitir acceso al almacenamiento externoSiempreAntiplegamientoVolverTramado de fondoBatallaExplorar directorio "Source"Machacar con tu culoCRT rejilla de aperturaCRT líneas de exploraciónCRT máscara de sombraCapturar la banderaCambiar armaPersonajeNo se permiten trampas en la situación actualConectar a servidorContinuarColaboradoresControlesCooperativaCrear servidorDetalladoDesarrolladoresDificultadDeshabilitadoIntegración con DiscordAbajoFácilHabilitadoHabilitado [c:#d0705d](Experimental) [/c]Habilitado con conteo de municiónMejoras¡Episodio bloqueado!Compatibilidad extendida para PlayStation™DispararPara más información, visita la web oficial:Pantalla completaModo de juegoEtiquetas de los botones del mandoVibración del mandoJugabilidadGráficosDifícilAltoSólo puntuación más altaMejores puntuacionesMejores puntuaciones para [c:#d0705d]juego base [/c]Mejores puntuaciones para [c:#d0705d]{} [/c]HorizontalQuiero jugar al juego como solía ser.Quiero jugar al juego con algo nuevo.Importar episodiosDiagnósticos de entradaSaltarMantener relación de aspecto en cinemáticasIdiomaEscalada de cornisasIzquierdaLegadoNivel "{}" inicializadoBajoAsegúrate que los ficheros de Jazz Jackrabbit 2 están presentes en la siguiente ruta:Volumen principalMedioMonocromáticoVolumen de músicaBotón trasero nativoLos niveles y episodios recién agregados estarán disponibles pronto.NoSólo sin trampas¡No se encuentra nivel personalizado!No se encuentran episodios!¡No se detectaron ficheros!¡No se detectan mandos!¡No se importaron episodios nuevos!No se encuentran servidores, pero seguimos buscando!Nada / Pixel perfectoNúmero de jugadores en localOpcionesSobrescribir episodio completadoMétricas de rendimientoJugar niveles personalizadosJugar demostraciónJugar historiaAlejamiento preferidoPantalla partida preferidaPresiona [c:#d0705d]Fuego [/c] para continuarPulsa cualquier tecla o botón para asignarProcesando los ficheros del directorio [c:#9e7056]"Source" [/c]...Procesando {} fichero...Procesando {} ficheros...SalirCarreraRazer Chroma™ReforjadoJugabilidad reforjadaPantalla táctil reforjadaMenú principal reforjadoRecargar cacheReimplementación del juego [c:#9e7056]Jazz Jackrabbit 2 [/c] publicado en 1998. Compatible con varias versiones del juego (Shareware Demo, Holiday Hare '98, The Secret Files y Christmas Chronicles). Además, es compatible parcialmente con alguna funciones de la extensión JJ2+.Reasignar controlesReasignar controles para participante {}Modo reescaladoResetear a por defectoResoluciónReiniciar episodioReanudarDerechaCorrerVolumen de efectosGuardar y salirEscrituraEscoge modalidad juegoSelecciona modo de reescaladoSelecciona ficheros de tu juego original para desbloquear episodios adicionalesCortoMostrar rastros del jugadorSonidosEmpezarFuerteBatalla en equipoCarrera en equipoEste proyecto usa el motor de juego [c:#9e7056]nCine [/c] modificado y las siguientes librerías:Alternar consolaAlternar correrControles táctilesTraductoresEn busca del tesoroVentana gráfica no alineadaComando desconocidoArribaVerticalEsperando por los ficheros...Calidad del aguaDébilRueda de armasArma {}¡Bienvenido a [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementado!SiPuedes ajustar la posición de las zonas táctiles arrastrando y soltando.Puedes elegir tu estilo de juego preferido. Esta opción se puede cambiar en cualquier momento en [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. ¡Para más información, visita {} y  Discord!Puedes habilitar las mejoras que se agregaron a este remake.Tienes que completar "{}" antes! Los dragones viven en burbank. Busca a la ardilla. Mark lleva calzoncillos. Hoo Hah! A Nick le encanta el brillo. ¡Como siempre! Spaz se comió al dopefish. Cuidado con of chainsaw schmalz. No le des a Mark un burrito. Envíale a Nigel una tarjeta verde. Envíale a Tim nuevos calcetines. Cuidado con los enemigos que caen. ¡Craig sigue siendo un imbécil! Tiempo de nivel secreto!!! Machaca con tu culo una caja de plata para despejar tu camino Las cajas también pueden hacer que aparezcan plataformas... Leh es un campista Derretir el trampolín... Un lanzallamas funciona bien contra microbios. Cantos rodados que caen pueden causarte dolor de cabeza. ¡Buenas noches, bubba! Que dure el nivel helado. ¿Qué diablos? ¡Aaaah! No! ¡Esto NO ha terminado! Colecciona monedas para activar dispositivos de ventanas extras. Nada que ver aquí. Los bastones te hacen girar para que puedas ir aún más rápido. Sala del tesoro secreto. Has encontrado una zona secreta. ¡Machaca fuerte con tu culo la caja de metal para abrir los bloques clave! El queso es verde el martes. Craig es el rey idiota. ¡Enhorabuena! ¡Ahora ve a buscar a Devan Shell! ¡Aprieta y salta por debajo de estos bloques para romperlos! Para ganar a la reina tírala de su alféizar. Para pasar a través de estos bloques, ¡aprieta y salta! Machaca tu culo para salir. Este trampolín está congelado. Las corazas te darán especial protección ilimitada durante un corto espacio de tiempo. Los cronógrafos añadirán tiempo a la vida de una coraza. Un secreto súper genial. ¡Este schwartzenguard es un plomo! ¡Aja! ¡Estoy ganando! ¡No puedes ganarme, Jazz! ¡Prepárate a enfrentarte con mi súper robot! Estos bloques son bloques de velocidad. ¡Corre hacia ellos a toda velocidad! Después de saltar, vuelve a apretar saltar para hacer un movimiento especial. Cuidado con cosas aguzadas. Duelen. Las piedras azules cuentan como diez piedras. Las zanahorias te dan salud. Los controles te salvan el puesto si pierdes una vida. Recoge monedas para abrir las habitaciones de primas. Recoge piedras preciosas para tener una vida más. Recoge cosas buenas para conseguir puntos y sorpresas. Buen trabajo. Recuerda buscar secretos. Las piedras verdes cuentan como cinco piedras. Ahora ya puedes jugar. Buena suerte y a divertirse. Las piedras rojas cuentan como una piedra. Los secretos abundan en Jazz 2. Comprueba las paredes. Puede dispararse a algunas paredes. Bienvenido a Jazz Jackrabbit 2. Este nivel es un nivel de formación. Cuando en el aire, aprieta para machacar fuerte con tu culo. Machaca con tu culo la tapa de la alcantarilla! Los círculos de humo te marearán. No ganes de Nigel jugando al billar. ¡Te lo he advertido! :) Busca la caja y despeja tu camino. No hay premios para los que no tienen mano firme. Sólo Spaz puede ir a la habitación izquierda. Posiblemente necesita algo para ponerse de pie. El pronóstico del tiempo para hoy: ¡Vientos fuertes! Bienvenido a Jazz Jackrabbit 2: ¡Los Archivos secretos! ¡No puedes machacar con el culo pues ponte de pie y vete!Si comes demasiados huevos de chocolate te pondrás enfermo :p Un camino conduce a la riqueza. Un camino conduce a la lucha. Entrada de túnel inclinada Para entrar en los túneles superiores tienes que buscar el warp de acceso. Busca la caja para dejar aparecer tus escalones ¡Sólo los que pueden hacer un salto doble pueden llegar a los chismes! Machacando esta caja también pone en libertad a algunos enemigos :) Pero necesitas una manera para llegar aquí arriba... Entra en la casa con cautela..... No se pueden romper las cajas de plata que están en el agua... Machacar las cajas puede ser bueno o malo... La caja de control del nivel del agua se encuentra arriba. Michelle, Te amaré siempre y para siempre :) No has logrado el salto, ¿he? :) Salta a la derecha cuan lejos posible... ¡Huye a los techos! Los cielos premiarán a los que machacan.... ¡Utiliza tus movimientos especiales para elevar los tubos de aire! Para Jazz, pulsa Agacharse y Saltar. Para Spaz, pulsa Saltar dos veces! ¡Bien hecho! Coleccionar 20 monedas es mas provechoso... ¡Busca la caja y las joyas son tuyas! Los trampolines no funcionan cuando estén congelados... ¡ATENCIÓN! Bandadas de cuervos pueden ser muy peligrosas. ¡Busca una tapadera y empieza a pegar a diestro y siniestro!! Busca la caja para quitar los bloques.... Ve a www.project2.com usa la contraseña: BUNNYLOVER Hola GeoBunny :) Para quitar los bloques mira al edificio más alto. Las monedas te dan acceso a ventanas que aparecen después. ¡Dispara estos bloques! ¡Algunos cajones tienen bombas y personajes malos! ¡Pisa fuerte en el lugar correcto y es posible que te encuentres con una sorpresa! Para pasar esta zona pisa fuerte el cajón de metal secreto. Cuando estés en el aire, aprieta para machacar fuerte con tu culo. ¡Necesitas veinte monedas para pasar por esta ventana secreta! Un lanzallamas funciona bien contra microbios. ¡Los círculos de humo te marearán mucho! ¡Necesitas veinte monedas para pasar por esta ventana secreta! ¡Cuidado con la bruja! Puede convertirte en una rana. ¡Si te conviertes en una rana Eva Earlong puede ayudarte! ¡Lo conseguiste! Este es el final de la versión de demostración. Ahora revisa la orden de pedido para M Á S!para eliminar la asignación Felicitaciones estivales de Epic MegaGames Orange Games y Project 2 Interactive! Algunos bloques sólo se pueden romper con una determinada arma. ¡Cuidado con los picos de abajo! ¡Bienvenido a Christmas Chronicles! ¡Puedes machacar con el culo algunos de los puntos débiles en los caminos! ¡Hola Piggy! - Poopy Kassi Nicole: Anhelo decirte un millón de cosas Pero mis palabras siempre conducen al mismo final Te quiero, te quiero. Michelle: Has cambiado mi vida de muchas maneras Te dedico este proyecto. Te quiero mucho. Entrada al sendero de gemas. Sube a las copas de los árboles para encontrar y machacar la caja de entrada. Cajón de entrada para sendero de gemas. Golpear los bloques que están encima tuyo puede ser gratificante. ¡Puedes machacar con el culo algunos de los puntos débiles en los caminos! ¡Puedes pararte en la cima de algunos de los árboles! ¡No pierdas el control! Abandonando Burrowsville Visítanos de nuevo. Contraseña: xmasbunny Por favor, visita www.project2.com Usa tu TNT sabiamente. Robert y Craig: ¡Los trampolines son lo MÁXIMO! :) Este puente no parece muy seguro... Bienvenido a Burrowsville Conduce con precaución.deathkiller-jazz2-native-2a7ccef/Content/Translations/es.po000066400000000000000000002000531512772601700241520ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:57+0100\n" "PO-Revision-Date: \n" "Last-Translator: enfeitizador \n" "Language-Team: enfeitizador\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz se comió al dopefish." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Busca a la ardilla." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Los dragones viven en burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark lleva calzoncillos. \n" "Hoo Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "A Nick le encanta el brillo. \n" "¡Como siempre!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Envíale a Tim nuevos calcetines." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Envíale a Nigel una tarjeta verde." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Cuidado con of chainsaw schmalz." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "No le des a Mark un burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Cuidado con los enemigos que caen." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "¡Craig sigue siendo un imbécil!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Tiempo de nivel secreto!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Machaca con tu culo una caja de plata\n" "para despejar tu camino" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Las cajas también pueden hacer que aparezcan plataformas..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Derretir el trampolín..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh es un campista" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Cantos rodados que caen pueden \n" "causarte dolor de cabeza." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Un lanzallamas funciona\n" "bien contra microbios." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Que dure el nivel helado." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "¡Buenas noches, bubba!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "¿Qué diablos? ¡Aaaah! No! \n" "¡Esto NO ha terminado!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Los bastones te hacen girar para\n" " que puedas ir aún más rápido." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Has encontrado una zona secreta." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Sala del tesoro secreto." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Nada que ver aquí." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Colecciona monedas para activar \n" "dispositivos de ventanas extras." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "El queso es verde el martes." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig es el rey idiota." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Para ganar a la reina \n" "tírala de su alféizar." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "¡Enhorabuena!\n" "¡Ahora ve a buscar a Devan Shell!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Para pasar a través de estos\n" "bloques, ¡aprieta y salta!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "¡Aprieta y salta por debajo de\n" "estos bloques para romperlos!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "¡Machaca fuerte con tu culo la caja de metal \n" "para abrir los bloques clave!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Machaca tu culo para salir." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Este trampolín está congelado." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Un secreto súper genial." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Las corazas te darán especial protección ilimitada \n" "durante un corto espacio de tiempo." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Los cronógrafos añadirán tiempo a\n" "la vida de una coraza." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "¡Este schwartzenguard es un plomo!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "¡No puedes ganarme, Jazz!\n" "¡Prepárate a enfrentarte con mi súper robot!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "¡Aja! ¡Estoy ganando!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Estos bloques son bloques de velocidad.\n" "¡Corre hacia ellos a toda velocidad!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Bienvenido a Jazz Jackrabbit 2. \n" " Este nivel es un nivel de formación." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Recoge cosas buenas para\n" "conseguir puntos y sorpresas." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Después de saltar, vuelve a apretar saltar\n" "para hacer un movimiento especial." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Puede dispararse a algunas paredes." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Cuando en el aire, aprieta para\n" "machacar fuerte con tu culo." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Los secretos abundan en Jazz 2. \n" " Comprueba las paredes." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Buen trabajo. Recuerda\n" "buscar secretos." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Recoge piedras preciosas para \n" "tener una vida más." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Las piedras rojas cuentan\n" "como una piedra." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Las piedras verdes cuentan\n" "como cinco piedras." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Las piedras azules cuentan\n" "como diez piedras." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Las zanahorias te dan salud." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Los controles te salvan el puesto\n" "si pierdes una vida." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Recoge monedas para\n" "abrir las habitaciones de primas." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Cuidado con cosas aguzadas.\n" "Duelen." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Ahora ya puedes jugar.\n" " Buena suerte y a divertirse." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Machaca con tu culo la tapa de la alcantarilla!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Los círculos de humo \n" "te marearán." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "¡No puedes machacar con el culo\n" "pues ponte de pie y vete!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "No hay premios para los que\n" "no tienen mano firme." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "El pronóstico del tiempo para hoy: ¡Vientos fuertes!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Busca la caja\n" "y despeja tu camino." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Bienvenido a\n" "Jazz Jackrabbit 2:\n" "¡Los Archivos secretos!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Sólo Spaz puede ir\n" "a la habitación izquierda.\n" "Posiblemente necesita algo\n" "para ponerse de pie." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "No ganes de Nigel jugando al billar.\n" "¡Te lo he advertido! :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Si comes demasiados huevos\n" "de chocolate te pondrás enfermo :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Entrada de túnel inclinada" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Para entrar en los túneles superiores\n" "tienes que buscar el warp de acceso." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Un camino conduce a la riqueza.\n" "Un camino conduce a la lucha." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "¡Sólo los que pueden hacer un salto doble\n" "pueden llegar a los chismes!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Busca la caja para\n" "dejar aparecer tus escalones" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Machacando esta caja también\n" "pone en libertad a algunos enemigos :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Entra en la casa con cautela....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "No se pueden romper las cajas de plata que están en el agua..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "La caja de control del nivel del agua se encuentra arriba." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Machacar las cajas puede ser bueno o malo..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Pero necesitas una manera para llegar aquí arriba..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Te amaré siempre y para siempre :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "¡Huye a los techos!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Los cielos premiarán\n" "a los que machacan...." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Salta a la derecha\n" "cuan lejos posible..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "No has logrado el salto, ¿he? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "¡Bien hecho!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "¡Utiliza tus movimientos especiales para elevar los tubos de aire!\n" "Para Jazz, pulsa Agacharse y Saltar.\n" "Para Spaz, pulsa Saltar dos veces!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "¡Busca la caja y las joyas son tuyas!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Los trampolines no funcionan cuando estén congelados..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Coleccionar 20 monedas es mas provechoso..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Busca la caja para quitar los bloques...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "¡ATENCIÓN! Bandadas de cuervos pueden ser\n" "muy peligrosas." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Para quitar los bloques\n" "mira al edificio más alto." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "¡Busca una tapadera y empieza a pegar a diestro y siniestro!!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Hola GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Ve a www.project2.com\n" "usa la\n" "contraseña: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "¡Dispara estos bloques!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Cuando estés en el aire, aprieta\n" "para machacar fuerte con tu culo." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Para pasar esta zona pisa fuerte\n" "el cajón de metal secreto." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "¡Necesitas veinte monedas para pasar por \n" "esta ventana secreta!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Las monedas te dan acceso a \n" "ventanas que aparecen después." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "¡Pisa fuerte en el lugar correcto y\n" "es posible que te encuentres con una sorpresa!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "¡Algunos cajones tienen\n" "bombas y personajes malos!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "¡Necesitas veinte monedas para pasar por \n" "esta ventana secreta!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Un lanzallamas funciona bien \n" "contra microbios." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "¡Los círculos de humo\n" "te marearán mucho!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "¡Cuidado con la bruja! Puede\n" "convertirte en una rana." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "¡Si te conviertes en una rana\n" "Eva Earlong puede ayudarte!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "¡Lo conseguiste! Este es el final\n" " de la versión de demostración.\n" "Ahora revisa la orden de pedido\n" "para M Á S!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "¡Cuidado con los picos de abajo!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "¡Puedes machacar con el culo\n" "algunos de los puntos débiles\n" "en los caminos!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Algunos bloques sólo\n" "se pueden romper con\n" "una determinada arma." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "¡Bienvenido a Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Felicitaciones estivales de\n" "Epic MegaGames\n" "Orange Games y\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "¡Puedes pararte en la cima\n" "de algunos de los árboles!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "¡Puedes machacar con el culo\n" "algunos de los puntos débiles\n" "en los caminos!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Golpear los bloques que están encima tuyo\n" "puede ser gratificante." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Cajón de entrada para sendero de gemas." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Entrada al sendero de gemas.\n" "Sube a las copas de los árboles para encontrar\n" "y machacar la caja de entrada." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "¡Hola Piggy!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Has cambiado mi vida de muchas maneras\n" "Te dedico este proyecto.\n" "Te quiero mucho." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Anhelo decirte un millón de cosas\n" "Pero mis palabras siempre conducen al mismo final\n" "Te quiero, te quiero." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Usa tu TNT sabiamente." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "¡No pierdas el control!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Bienvenido a Burrowsville\n" "Conduce con precaución." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Este puente no parece\n" "muy seguro..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert y Craig:\n" "¡Los trampolines son lo MÁXIMO! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Abandonando Burrowsville\n" "Visítanos de nuevo." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Contraseña: xmasbunny\n" "Por favor, visita\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Nivel \"{}\" inicializado" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Para más información, visita la web oficial:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "No se permiten trampas en la situación actual" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Comando desconocido" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Reimplementación del juego \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] publicado en " "1998. Compatible con varias versiones del juego (Shareware Demo, Holiday " "Hare '98, The Secret Files y Christmas Chronicles). Además, es compatible " "parcialmente con alguna funciones de la extensión JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Desarrolladores" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Colaboradores" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Traductores" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Este proyecto usa el motor de juego \f[c:#9e7056]nCine\f[/c] modificado y " "las siguientes librerías:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Jugar historia" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Jugar demostración" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importar episodios" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Continuar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Mejores puntuaciones" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Opciones" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Acerca de" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Salir" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "¡Se ha concedido acceso al almacenamiento externo!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Reinicia el juego para leer los ficheros de \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#337233] correctamente." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Este juego requiere los ficheros del \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a] original!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Asegúrate que los ficheros de Jazz Jackrabbit 2 están presentes en la " "siguiente ruta:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Permitir acceso al almacenamiento externo" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Reasignar controles para participante {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Reasignar controles" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Controles táctiles" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Alternar correr" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Etiquetas de los botones del mando" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibración del mando" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Compatibilidad extendida para PlayStation™" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Botón trasero nativo" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnósticos de entrada" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Resetear a por defecto" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Controles" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Fuerte" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Débil" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Deshabilitado" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Habilitado" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Personaje" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Modo de juego" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Crear servidor" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Batalla" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Batalla en equipo" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Carrera" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Carrera en equipo" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "En busca del tesoro" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Capturar la bandera" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Cooperativa" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Jugar niveles personalizados" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "¡No se encuentra nivel personalizado!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "No se encuentran episodios!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Reiniciar episodio" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Tienes que completar \"{}\" antes!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "¡Episodio bloqueado!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Legado" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Quiero jugar al juego como solía ser." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Reforjado" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Quiero jugar al juego con algo nuevo." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "¡Bienvenido a \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementado!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Jugabilidad" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Mejoras" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Puedes elegir tu estilo de juego preferido.\n" "Esta opción se puede cambiar en cualquier momento en \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "¡Para más información, visita {} y  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Jugabilidad reforjada" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Pantalla táctil reforjada" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Menú principal reforjado" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Escalada de cornisas" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Rueda de armas" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Puedes habilitar las mejoras que se agregaron a este remake." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Habilitado con conteo de munición" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Idioma" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Escritura" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Permitir trampas" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Sobrescribir episodio completado" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Explorar directorio \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Recargar cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Si" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "No" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Sólo sin trampas" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Sólo puntuación más alta" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Siempre" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Modo reescalado" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Resolución" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Pantalla completa" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antiplegamiento" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Tramado de fondo" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Calidad del agua" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Mostrar rastros del jugador" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Pantalla partida preferida" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Alejamiento preferido" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Mantener relación de aspecto en cinemáticas" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Ventana gráfica no alineada" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Métricas de rendimiento" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Gráficos" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Bajo" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Alto" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertical" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horizontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Habilitado \f[c:#d0705d](Experimental)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Corto" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detallado" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Mejores puntuaciones para \f[c:#d0705d]juego base\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Mejores puntuaciones para \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Selecciona ficheros de tu juego original para desbloquear episodios " "adicionales" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Procesando {} fichero..." msgstr[1] "Procesando {} ficheros..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Esperando por los ficheros..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "¡No se detectaron ficheros!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "¡No se importaron episodios nuevos!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "¡No se detectan mandos!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Escoge modalidad juego" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Sonidos" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Reanudar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Guardar y salir" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Conectar a servidor" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "" "Procesando los ficheros del directorio \f[c:#9e7056]\"Source\"\f[/c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Los niveles y episodios recién agregados estarán disponibles pronto." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Izquierda" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Derecha" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Arriba" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Abajo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Machacar con tu culo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Disparar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Saltar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Correr" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Cambiar arma" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Volver" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Alternar consola" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Arma {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Pulsa cualquier tecla o botón para asignar" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "para eliminar la asignación" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Nada / Pixel perfecto" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT líneas de exploración" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT máscara de sombra" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT rejilla de apertura" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monocromático" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Selecciona modo de reescalado" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "No se encuentran servidores, pero seguimos buscando!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Presiona \f[c:#d0705d]Fuego\f[/c] para continuar" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Volumen principal" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Volumen de efectos" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Volumen de música" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Número de jugadores en local" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Dificultad" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Empezar" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Fácil" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Medio" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Difícil" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "" "Puedes ajustar la posición de las zonas táctiles arrastrando y soltando." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integración con Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "" msgstr[1] "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]No se puede cargar el nivel especificado!\f[/c]\n" "\n" "\n" "Asegúrate que todos los ficheros necesarios\n" "son accesibles e inténtalo de nuevo." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]No se puede continuar la partida guardada!\f[/c]\n" "\n" "\n" "Asegúrate que todos los ficheros necesarios\n" "son accesibles e inténtalo de nuevo." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]No se puede conectar al servidor!\f[/c]\n" "\n" "\n" "La versión de tu cliente no es compatible con la del servidor." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]No se puede conectar al servidor!\f[/c]\n" "\n" "\n" "La capacidad del servidor está completa.\n" "Inténtalo de nuevo más tarde." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]No se puede conectar al servidor!\f[/c]\n" "\n" "\n" "El servidor non está en un estado que pueda procesar tu solicitud.\n" "Inténtalo de nuevo dentro de unos segundos." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]La conexión se ha cerrado!\f[/c]\n" "\n" "\n" "El servidor se está apagando.\n" "Inténtalo de nuevo más tarde." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]La conexión se ha cerrado!\f[/c]\n" "\n" "\n" "El servidor se está apagando para mantenimiento.\n" "Inténtalo de nuevo más tarde." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]La conexión se ha cerrado!\f[/c]\n" "\n" "\n" "El servidor se está apagando para reconfigurarse.\n" "Inténtalo de nuevo más tarde." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]La conexión se ha cerrado!\f[/c]\n" "\n" "\n" "El servidor se está apagando por una actualización.\n" "Inténtalo de nuevo más tarde." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]La conexión se ha perdido!\f[/c]\n" "\n" "\n" "Inténtalo de nuevo y si el problema persiste,\n" "comprueba tu conexión a la red." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]No se puede conectar al servidor!\f[/c]\n" "\n" "\n" "El servidor no está respondiendo a la petición de conexión." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]La conexión se ha cerrado!\f[/c]\n" "\n" "\n" "Has sido \f[c:#907050]expulsado\f[/c] del servidor.\n" "Contacta con la administración del servidor para más información." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]La conexión se ha cerrado!\f[/c]\n" "\n" "\n" "Has sido \f[c:#725040]baneado\f[/c] del servidor.\n" "Contacta con la administración del servidor para más información." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" #~ msgid "Unknown" #~ msgstr "Desconocido" #~ msgid "Play Custom Game" #~ msgstr "Jugar juego personalizado" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Conectando a {}:{}..." #~ msgid "Create Custom Server" #~ msgstr "Crear servidor personalizado" #~ msgid "Connect to Server" #~ msgstr "Conectar a servidor" #~ msgid "or" #~ msgstr "o" #~ msgid "Creating server..." #~ msgstr "Creando servidor..." #~ msgid "Error" #~ msgstr "Error" deathkiller-jazz2-native-2a7ccef/Content/Translations/fr.mo000066400000000000000000001077501512772601700241610ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@SS UUjUaVZVRVW|Wn&XX:YoYXZ[[{W\\m]]~^H_zZ_~_T`i`mhaab+cdc!d$ad:d d0de&*eQe Zegeneeeeeee ff ,f67fnf f f f ffff%f g (g 6g BgNgfg{ggg(g"ggg'h.hBh3Hh5|h h hh%h i "i .i 9iCiHibi5si)i i1i/j@jWjoj!vjjjjjjjRj 4kBk HkSk[k=pkkk$kkl%l ?l3`llll"ll m+m:m Sm am om{mm/m2mDn8Snnnnnn!nnoop(/p Xpfp pp ppppp pppZqmqsqq q qqqqqq_rorrr rrrrrsss1s:s@Usss ssJstJt_tL}}5}<}>/~3n~'~C~&95!oRA,&.S6+9s(-S8>:wD΂BIV71؃* >5.t34ׄ$ 21d, H1V16B14+f0Ç;ӇYi>B7D:CBÉ5C<A@ŠmqUEڋ$ #EAiŌXF]!AAa/ӎ(2&F(m 0?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: Mwyann Language: fr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Le jeu va bientôt commencer ! {} a gagné ! [c:#337233]Relancer le jeu pour lire les fichiers [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] correctement. [c:#704a4a]Impossible de se connecter au serveur ! [/c] L'authentification a échoué. Contactez un administrateur du serveur pour plus d'information. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Paramètre invalide spécifié. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Mot de passe incorrect. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Nom d'utilisateur invalide. Vérifiez votre profil et réessayez. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Le serveur est plein. Essayez à nouveau plus tard. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Le serveur n'est pas dans l'état de traiter votre demande. Réessayez à nouveau dans quelques secondes. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Ce serveur nécessite une authentification tierce. Contactez un administrateur du serveur pour plus d'information. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Le serveur ne répond pas aux demandes de connexion. [c:#704a4a]Impossible de se connecter au serveur ! [/c] Ce client n'est pas autorisé à se connecter. Contactez un administrateur du serveur pour plus d'information. [c:#704a4a]Impossible de se connecter au serveur ![/c] Votre client ne contient pas le niveau "{}". Veuillez télécharger les fichiers nécessaires et réessayez. [c:#704a4a]Impossible de se connecter au serveur ![/c] Votre client ne contient pas les éléments requis. Veuillez télécharger les fichiers nécessaires et réessayez. [c:#704a4a]Impossible de se connecter au serveur ! [/c] La version de votre client n'est pas compatible avec ce serveur. [c:#704a4a]Impossible de créer le serveur ! [/c] La playlist n'est pas correctement configurée. Vérifiez la configuration du serveur et réessayez. [c:#704a4a]Impossible de créer le serveur ! [/c] Vérifiez qu'aucun autre serveur ne fonctionne sur le même port et réessayez. [c:#704a4a]Impossible de charger le niveau! [/c] Assurez-vous que tous les fichiers nécessaires sont accessibles et essayez à nouveau. [c:#704a4a]Impossible de rétablir la sauvegarde ! [/c] Assurez-vous que tous les fichiers nécessaires sont accessibles et essayez à nouveau. [c:#704a4a]La connexion a été interrompue ! [/c] Triche détectée. [c:#704a4a]La connexion a été interrompue ! [/c] Le serveur s'arrête pour maintenance. Essayez à nouveau plus tard. [c:#704a4a]La connexion a été interrompue ! [/c] Le serveur s'arrête pour reconfiguration. Essayez à nouveau plus tard. [c:#704a4a]La connexion a été interrompue ! [/c] Le serveur s'arrête pour mise à jour. Vérifiez la version de votre client et essayez à nouveau dans une minute. [c:#704a4a]La connexion a été interrompue ! [/c] Le serveur s'arrête. Essayez à nouveau plus tard. [c:#704a4a]La connexion a été interrompue ![/c] Le serveur vous a déconnecté pour cause d'inactivité. [c:#704a4a]La connexion a été interrompue ! [/c] Vous avez été [c:#725040]banni [/c] du serveur. Contactez un administrateur du serveur pour plus d'information. [c:#704a4a]La connexion a été interrompue ! [/c] Vous avez été [c:#907050]éjecté [/c] du serveur. Contactez un administrateur du serveur pour plus d'information. [c:#704a4a]La connexion a été perdue! [/c] Réessayez à nouveau et si le problème persiste, vérifiez votre connexion à Internet. [c:#704a4a]Ce jeu requiert les fichiers d'origine du jeu [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]! [c:#d0705d]{} [/c] est connecté [c:#d0705d]{} [/c] est déconnecté [c:#d0705d]{} [/c] s'est fait griller par [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] s'est fait griller par l'environnementÀ proposL'accès au stockage externe a été autorisé !Autoriser la tricheAutoriser l'accès au stockage externeToujoursAntialiasingRetourTramage en arrière-planMatchConsulter dossier "Source"Coup vers le basGrille d'écran cathodiqueLignes d'écran cathodiqueMasque d'écran cathodiqueCapture de drapeauChanger d'armePersonnageLa triche n'est pas autorisée dans le contexte actuelConnexion au serveurContinuerContributeursContrôlesCoopérationCréer un serveur privéCréer un serveur publicCréer un serveurCréer un serveur depuis une playlistDétailléDéveloppeursDifficultéDésactivéDéconnecter et quitterIntégration DiscordBasFacileActivéActivé [c:#d0705d](Expérimental) [/c]Activé avec compteur de munitionsAméliorationsL'épisode est verrouillé !Prise en charge étendue PlayStation™Trouve une sortie !TirerPour plus d'information, visitez le site officiel :Pour plus d'informations, visitez {} et  Discord !Plein écranMode de jeuLe jeu commence dans {}Étiquettes des boutons de la manetteVibrations de la manetteMode de jeuGraphismesDifficileHautMeilleur score uniquementMeilleurs scoresMeilleurs scores pour [c:#d0705d]le jeu de base [/c]Meilleurs scores pour [c:#d0705d]{} [/c]HorizontalJe veux jouer au jeu d'origine, tel qu'il était.Je veux jouer au jeu avec quelques nouveautés.Importer des épisodesDiagnostic des entréesSauterConserver ratio des cinématiquesLangueEscalader les rebordsGaucheOriginalNiveau "{}" initialiséBasAssurez-vous que les fichiers de Jazz Jackrabbit 2 sont présents dans ce dossier:Volume globalMoyenMonochromeMusiqueTouche retour nativeDe nouveaux niveaux et épisodes seront bientôt disponibles.NonSans triche uniquementAucun niveau personnalisé trouvé !Aucun épisode trouvé !Aucun fichier sélectionné !Aucune manette trouvée !Aucun nouvel épisode importé !Aucun serveur trouvé, mais la recherche continue !Aucun / Au pixel prèsNombre de joueurs locauxOptionsRemplacer l'achèvement d'épisodeIndicateurs de performanceJouer des niveaux personnalisésJouer en ligneJouer la démo SharewareMode histoireNom du joueurPoints : {}Préférer dézooméÉcran partagé préféréAppuyez sur [c:#d0705d]Feu [/c] pour continuerAppuyer sur une touche ou un bouton pour attribuerTraitement des fichiers dans le dossier [c:#9e7056]"Source" [/c]...Traitement de {} fichier...Traitement de {} fichiers...QuitterCourseRazer Chroma™RénovéMode de jeu rénovéInformations à l'écran rénovéMenu principal rénovéRafraîchir le cacheRéimplementation du jeu [c:#9e7056]Jazz Jackrabbit 2 [/c] sorti en 1998. Gère différentes versions du jeu (démo Shareware, Holiday Hare '98, The Secret Files et Christmas Chronicles). Support partiel de certaines fonctionnalités de l'extension JJ2+.Changer les contrôlesChanger les contrôles pour le joueur {}Mode de renduRéinitialiser par défautRésolutionRecommencer l'épisodeReprendreDroiteCourirSonsSauver & QuitterScriptingChoisir un mode de jeuSélectionner le mode de renduSélectionner les fichiers du jeu d'origine pour débloquer des épisodes supplémentairesCourtAfficher traînées joueurSonsSpectateurCommencerFortesBasculer vers la nouvelle armeMatch en équipeCourse par équipeChasse au trésor en équipeCe projet utilise le moteur de jeu [c:#9e7056]nCine [/c] modifié et les librairies suivantes:Affichage de la consoleMode Marche/CourseContrôles tactilesTraducteursChasse au trésorVue non alignéeID unique de joueurCommande inconnueServeur sans nomHautProfil de l'utilisateurVerticalEn attente des fichiers...En attente de {} joueur en plusEn attente de {} joueurs en plusQualité de l'eauFaiblesRoue d'armesArme {}Bienvenue à la réimplémentation de [c:#9e7056]Jazz Jackrabbit 2 [/c] !OuiVous pouvez ajuster la position des zones tactiles en les faisant glisser.Vous pouvez choisir votre mode de jeu préféré. Cette option peut être changée à tout moment dans [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Pour plus d'information, visiter {} et  Discord !Vous pouvez activer les améliorations qui ont été ajoutées à ce remake.Terminez l'épisode "{}" d'abord ! Des dragons habitent à Burbank. Trouve le Trionyx. Mark porte un slip. Ouah ! Nick aime tout ce qui brille. Il a toujours été comme ça ! Spaz a gobé le Dopefish. Méfie-toi du Schmalz à la tronçonneuse. Pas de burrito pour Mark. Envoie un visa à Nigel. Envoie des chaussettes neuves à Tim. Méfie-toi des ennemis qui tombent. Craig est toujours un imbécile ! C'est l'heure du niveau secret !!! Piétine ton butin pour sortir. Les caisses peuvent aussi faire apparaître des plateformes... Leh est un campeur Fais fondre le ressort... Les lanceflammes sont super pour faire la guerre aux moustiques. En tombant, les boulders peuvent te donner mal à la tête... Bonne nuit Bubba ! Vive l'ère des glaces ! Comment ? Aaaah ! Non ! Ce n'est PAS terminé ! Ramasse les pièces pour activer les tunnels téléporteurs. Rien à voir ici. Une perche te fait tournoyer pour que tu puisses aller encore plus vite. Chambre au trésor. Tu as trouvé une chambre secrète. Tape ton derrière sur la malle pour ouvrir les dalles à clé ! Le fromage est vert le mardi. Craig est le roi des imbéciles. Félicitations ! Maintenant, tu peux sortir du chateau ! Appuie sur Bas et saute sous ces dalles pour les casser ! Pour vaincre la reine tire pour la faire tomber de sa corniche. Pour renverser ces dalles, appuie sur Bas et saute ! Tape ton derrière pour sortir. Ce ressort est gelé. Pendant quelques instants, un bouclier te donnera des munitions à gogo. Un chrono prolonge la durée de vie d'un bouclier. Secret super-extra. Ce garde-chiourme est en civet ! Ouille, Ouille ! Moi, je me casse ! Tu n'as aucune chance ! Prépare-toi à faire face à mon Super-Robot ! Ces blocs sont des blocs de vitesse. Cours dessus à pleine vitesse ! Après avoir sauté, saute une deuxième fois pour faire un mouvement spécial. Méfie-toi des objets pointus. Ils font mal. Les diamants bleus comptent pour dix. Les carrottes te gardent en bonne santé. Les postes de contrôle sauvent ta peau si tu perds une vie. Ramasse les pièces pour ouvrir les salles de bonus. Ramasse les diamants pour gagner des vies supplémentaires. Ramasse les goodies pour gagner des points et des surprises. Bien joué ! N'oublie pas de chercher les secrets. Les diamants verts comptent pour cinq. Ca y est ! Tu es prêt à jouer. Bonne chance et amuse-toi bien ! Les diamants rouges comptent pour un. Jazz 2 est truffé de secrets. Vérifie bien les murs. Tu peux tirer sur certains murs. Jazz Jackrabbit 2 est content de te voir. Ce niveau est juste pour t'entraîner. Une fois en l'air, appuie sur Bas pour taper avec ton derrière. Tape ton derrière sur la bouche d'égout ! Les ronds de fumée te donneront le vertige. Ne battez pas Nigel au billard Vous êtes prévenu ! Trouvez le coffre et dégagez votre chemin Pas de récompense pour ceux qui n'ont pas la main sûre Seul Spaz peut accèder à la pièce en haut à gauche. Il aura peut-être besoin de quelque chose sur quoi monter Météo pour aujourd'hui : Vent violent Bienvenue sur Jackrabbit 2: The Secret Files Vous n'avez pas la possibilité de ruer Alors levez-vous et mettez-vous en route !Manger trop d'oeufs en chocolat peut vous rendre malade Un chemin mène aux richesses un chemin mène à la lutte Entrée de biais du tunnel Pour accéder aux tunnels au-dessus trouvez le tunnel téléporteur Trouvez le coffre afin de faire apparaître vos blocs de grimpée Seuls ceux qui peuvent faire un double saut pourront arriver aux gadgets Heurter ce coffre peut aussi libérer quelques ennemis Mais vous devez trouver le moyen d'aller là-bas Entrez dans la maison avec précaution... Les coffres en argent ne peuvent pas être brisés sous l'eau Heurter les coffres peut être bien ou mal... Coffre pour le contrôle du niveau d'eau au dessus Michelle, Je t'aimerai toujours et pour toujours :) Vous avez loupé votre saut, hein ? Sautez sur la droite le plus loin que vous pouvez Sauvez-vous sur les toits ! Les cieux récompenseront ceux qui frappent Utilisez vos Mouvements Spéciaux pour soulever les conduites d'air Pour Jazz appuyez sur Accroupissement et Saut pour Spaz, appuyez deux fois sur Saut! Bien joué ! Il est plus rémunérateur de réunir 20 pièces Trouvez le coffre et les joyaux seront à vous ! Les trampolines ne marchent pas quand ils sont gelés ATTENTION! les nuées de corbeaux peuvent être très dangereuses Choisissez un bouclier et donnez une bonne ruade Trouvez le coffre pour éliminer les blocs Goto www.project2.com use password: BUNNYLOVER Hi GeoBunny :) Pour déplacer les blocs regardez le building le plus haut Les pièces permettent de traverser des tunnels téléporteurs que tu verras plus tard. Tire sur ces dalles ! Certaines caisses peuvent contenir des bombes ou des sbires ! Piétine là où il faut et tu pourrais bien avoir une surprise ! Pour traverser cette zone, piétine la malle secrète. Quand tu es en l'air, appuie sur Bas pour taper avec ton derrière. Il te faut vingt pièces pour traverser ce tunnel téléporteur ! Les lanceflammes sont super pour faire la guerre aux moustiques. Les ronds de fumée te donneront un sacré vertige ! Il te faut vingt pièces pour traverser ce tunnel téléporteur ! Méfie-toi de la sorcière ! Elle peut te changer en grenouille. Si tu es transformé en grenouille Eva Earlong peut te sauver ! Vous l'avez fait ! Ceci est la fin de la version démo. Consultez le bon de commande pour en avoir P L U S !pour désattribuer Meilleurs voeux de la part de Epic MegaGames Orange Games et Project 2 Interactive ! Certains blocs ne peuvent être détruits qu'avec une arme précise. Fais attention aux pointes en bas ! Bienvenue à Christmas Chronicles! Tu peux taper ton derrière sur les parties fragiles du chemin ! Salut, Piggy ! - Poopy Kassi Nicole: J'ai tant de choses à te dire, mais mes mots se concluent toujours de la même manière : Je t'aime, je t'aime. Michelle : Tu as tellement changé ma vie Je te dédie ce projet. Je t'aime tellement. Entrée de la Gem Trail. Grimpez sur les arbres pour trouver et frapper la caisse d'entrée. Caisse d'entrée de la Gem Trail Frappe les blocs au dessus de toi pour recevoir une récompense. Tu peux taper ton derrière sur les parties fragiles du chemin ! Tu peux marcher au dessus de certains arbres ! Ne lâche pas prise ! Vous quittez Burrowsville. A bientôt ! Password: xmasbunny Please visit www.project2.com Utilise les explosifs intelligemment. Robert et Craig: Vive le printemps ! :) Ce pont n'a pas l'air solide... Bienvenue à Burrowsville Conduisez prudemment.deathkiller-jazz2-native-2a7ccef/Content/Translations/fr.po000066400000000000000000002051051512772601700241550ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:57+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Mwyann\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz a gobé le Dopefish." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Trouve le Trionyx." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Des dragons habitent à Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark porte un slip. \n" "Ouah !" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick aime tout ce qui brille. \n" "Il a toujours été comme ça !" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Envoie des chaussettes neuves à Tim." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Envoie un visa à Nigel." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Méfie-toi du Schmalz à la tronçonneuse." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Pas de burrito pour Mark." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Méfie-toi des ennemis qui tombent." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig est toujours un imbécile !" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "C'est l'heure du niveau secret !!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Piétine ton butin pour sortir." #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Les caisses peuvent aussi faire apparaître\n" "des plateformes..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Fais fondre le ressort..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh est un campeur" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "En tombant, les boulders peuvent \n" "te donner mal à la tête..." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Les lanceflammes sont super \n" "pour faire la guerre aux moustiques." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Vive l'ère des glaces !" #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Bonne nuit Bubba !" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Comment ? Aaaah ! Non !\n" "Ce n'est PAS terminé !" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Une perche te fait tournoyer\n" "pour que tu puisses aller encore plus vite." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Tu as trouvé une chambre secrète." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Chambre au trésor." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Rien à voir ici." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Ramasse les pièces pour activer\n" "les tunnels téléporteurs." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "Le fromage est vert le mardi." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig est le roi des imbéciles." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Pour vaincre la reine \n" "tire pour la faire tomber de sa corniche." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Félicitations ! \n" "Maintenant, tu peux sortir du chateau !" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Pour renverser ces \n" "dalles, appuie sur Bas et saute !" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Appuie sur Bas et saute sous \n" "ces dalles pour les casser !" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Tape ton derrière sur la malle \n" "pour ouvrir les dalles à clé !" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Tape ton derrière pour sortir." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Ce ressort est gelé." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Secret super-extra." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Pendant quelques instants, un bouclier\n" "te donnera des munitions à gogo." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Un chrono prolonge\n" "la durée de vie d'un bouclier." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Ce garde-chiourme est en civet !" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "Tu n'as aucune chance !\n" "Prépare-toi à faire face à mon Super-Robot !" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Ouille, Ouille ! Moi, je me casse !" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Ces blocs sont des blocs de vitesse.\n" "Cours dessus à pleine vitesse !" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Jazz Jackrabbit 2 est content de te voir. \n" "Ce niveau est juste pour t'entraîner." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Ramasse les goodies pour \n" "gagner des points et des surprises." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Après avoir sauté, saute une deuxième fois\n" "pour faire un mouvement spécial." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Tu peux tirer sur certains murs." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Une fois en l'air, appuie sur Bas\n" "pour taper avec ton derrière." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Jazz 2 est truffé de secrets. \n" " Vérifie bien les murs." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Bien joué ! N'oublie pas de\n" "chercher les secrets." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Ramasse les diamants pour gagner\n" "des vies supplémentaires." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Les diamants rouges comptent\n" "pour un." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Les diamants verts comptent\n" "pour cinq." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Les diamants bleus comptent\n" "pour dix." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Les carrottes te gardent en bonne santé." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Les postes de contrôle sauvent ta \n" "peau si tu perds une vie." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Ramasse les pièces pour\n" "ouvrir les salles de bonus." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Méfie-toi des objets pointus.\n" "Ils font mal." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Ca y est ! Tu es prêt à jouer.\n" " Bonne chance et amuse-toi bien !" #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Tape ton derrière sur la bouche d'égout !" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Les ronds de fumée te donneront \n" "le vertige." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Vous n'avez pas la possibilité de ruer\n" "Alors levez-vous et mettez-vous en route !" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Pas de récompense pour ceux\n" "qui n'ont pas la main sûre" #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Météo pour aujourd'hui : Vent violent" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Trouvez le coffre\n" "et dégagez votre chemin" #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Bienvenue sur\n" "Jackrabbit 2:\n" "The Secret Files" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Seul Spaz peut accèder à\n" "la pièce en haut à gauche.\n" "Il aura peut-être besoin de quelque chose\n" "sur quoi monter" #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Ne battez pas Nigel au billard\n" " Vous êtes prévenu !" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Manger trop d'oeufs en chocolat\n" " peut vous rendre malade" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Entrée de biais du tunnel" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Pour accéder aux tunnels au-dessus\n" "trouvez le tunnel téléporteur" #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Un chemin mène aux richesses\n" "un chemin mène à la lutte" #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Seuls ceux qui peuvent faire un double saut\n" "pourront arriver aux gadgets" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Trouvez le coffre afin de faire\n" "apparaître vos blocs de grimpée" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Heurter ce coffre peut aussi\n" "libérer quelques ennemis" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Entrez dans la maison avec précaution..." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Les coffres en argent ne peuvent\n" "pas être brisés sous l'eau" #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Coffre pour le contrôle du niveau\n" "d'eau au dessus" #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Heurter les coffres peut être\n" "bien ou mal..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Mais vous devez trouver le\n" "moyen d'aller là-bas" #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Je t'aimerai toujours et pour toujours :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Sauvez-vous sur les toits !" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Les cieux récompenseront\n" "ceux qui frappent" #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Sautez sur la droite le plus loin\n" "que vous pouvez" #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Vous avez loupé votre saut, hein ?" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Bien joué !" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Utilisez vos Mouvements Spéciaux\n" "pour soulever les conduites d'air\n" "Pour Jazz appuyez sur\n" "Accroupissement et Saut\n" "pour Spaz, appuyez deux fois sur Saut!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Trouvez le coffre et les joyaux seront à vous !" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Les trampolines ne marchent pas\n" "quand ils sont gelés" #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Il est plus rémunérateur de réunir 20 pièces" #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Trouvez le coffre pour éliminer les blocs" #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "ATTENTION!\n" "les nuées de corbeaux peuvent\n" "être très dangereuses" #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Pour déplacer les blocs\n" "regardez le building le plus haut" #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Choisissez un bouclier et\n" "donnez une bonne ruade" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Hi GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Tire sur ces dalles !" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Quand tu es en l'air, appuie sur Bas\n" "pour taper avec ton derrière." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Pour traverser cette zone, piétine\n" "la malle secrète." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Il te faut vingt pièces pour traverser \n" "ce tunnel téléporteur !" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Les pièces permettent de traverser \n" "des tunnels téléporteurs que tu verras plus tard." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Piétine là où il faut et\n" "tu pourrais bien avoir une surprise !" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Certaines caisses peuvent contenir\n" "des bombes ou des sbires !" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Il te faut vingt pièces pour traverser \n" "ce tunnel téléporteur !" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Les lanceflammes sont super \n" "pour faire la guerre aux moustiques." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Les ronds de fumée te donneront\n" "un sacré vertige !" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Méfie-toi de la sorcière ! Elle peut\n" "te changer en grenouille." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Si tu es transformé en grenouille\n" "Eva Earlong peut te sauver !" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Vous l'avez fait ! Ceci est\n" "la fin de la version démo.\n" "Consultez le bon de commande\n" "pour en avoir P L U S !" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Fais attention aux pointes en bas !" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Tu peux taper ton derrière\n" "sur les parties fragiles\n" "du chemin !" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Certains blocs ne peuvent\n" "être détruits qu'avec\n" "une arme précise." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Bienvenue à Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Meilleurs voeux de la part de\n" "Epic MegaGames\n" "Orange Games et\n" "Project 2 Interactive !" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Tu peux marcher au dessus\n" "de certains arbres !" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Tu peux taper ton derrière\n" "sur les parties fragiles\n" "du chemin !" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Frappe les blocs au dessus de toi\n" "pour recevoir une récompense." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Caisse d'entrée de la Gem Trail" #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Entrée de la Gem Trail.\n" "Grimpez sur les arbres pour trouver\n" "et frapper la caisse d'entrée." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Salut, Piggy !\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle :\n" "Tu as tellement changé ma vie\n" "Je te dédie ce projet.\n" "Je t'aime tellement." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "J'ai tant de choses à te dire, mais mes mots\n" "se concluent toujours de la même manière :\n" "Je t'aime, je t'aime." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Utilise les explosifs intelligemment." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Ne lâche pas prise !" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Bienvenue à Burrowsville\n" "Conduisez prudemment." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Ce pont n'a pas\n" "l'air solide..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert et Craig:\n" "Vive le printemps ! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Vous quittez Burrowsville.\n" "A bientôt !" #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Niveau \"{}\" initialisé" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Pour plus d'information, visitez le site officiel :" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "La triche n'est pas autorisée dans le contexte actuel" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Le jeu va bientôt commencer !" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] s'est fait griller par \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] s'est fait griller par l'environnement" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] est déconnecté" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] est connecté" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "{} a gagné !" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Commande inconnue" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Réimplementation du jeu \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] sorti en 1998. " "Gère différentes versions du jeu (démo Shareware, Holiday Hare '98, The " "Secret Files et Christmas Chronicles). Support partiel de certaines " "fonctionnalités de l'extension JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Développeurs" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Contributeurs" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Traducteurs" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Ce projet utilise le moteur de jeu \f[c:#9e7056]nCine\f[/c] modifié et les " "librairies suivantes:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Mode histoire" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Jouer la démo Shareware" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importer des épisodes" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Continuer" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Jouer en ligne" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Meilleurs scores" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Options" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "À propos" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Quitter" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Pour plus d'informations, visitez {} et  Discord !" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "L'accès au stockage externe a été autorisé !" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Relancer le jeu pour lire les fichiers \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#337233] correctement." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Ce jeu requiert les fichiers d'origine du jeu \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Assurez-vous que les fichiers de Jazz Jackrabbit 2 sont présents dans ce " "dossier:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Autoriser l'accès au stockage externe" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Changer les contrôles pour le joueur {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Changer les contrôles" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Contrôles tactiles" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Mode Marche/Course" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Étiquettes des boutons de la manette" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibrations de la manette" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Prise en charge étendue PlayStation™" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Touche retour native" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnostic des entrées" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Réinitialiser par défaut" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Contrôles" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Fortes" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Faibles" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Désactivé" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Activé" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Personnage" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Mode de jeu" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Créer un serveur" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Match" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Match en équipe" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Course" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Course par équipe" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Chasse au trésor" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Chasse au trésor en équipe" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Capture de drapeau" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Coopération" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Impossible de créer le serveur !\f[/c]\n" "\n" "\n" "La playlist n'est pas correctement configurée.\n" "Vérifiez la configuration du serveur et réessayez." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Impossible de créer le serveur !\f[/c]\n" "\n" "\n" "Vérifiez qu'aucun autre serveur\n" "ne fonctionne sur le même port et réessayez." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Jouer des niveaux personnalisés" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Aucun niveau personnalisé trouvé !" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Créer un serveur depuis une playlist" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Créer un serveur privé" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Créer un serveur public" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Aucun épisode trouvé !" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Recommencer l'épisode" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Terminez l'épisode \"{}\" d'abord !" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "L'épisode est verrouillé !" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Original" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Je veux jouer au jeu d'origine, tel qu'il était." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Rénové" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Je veux jouer au jeu avec quelques nouveautés." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "" "Bienvenue à la réimplémentation de \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] !" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Mode de jeu" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Améliorations" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Vous pouvez choisir votre mode de jeu préféré.\n" "Cette option peut être changée à tout moment dans \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "Pour plus d'information, visiter {} et  Discord !" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Mode de jeu rénové" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Informations à l'écran rénové" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Menu principal rénové" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Escalader les rebords" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Roue d'armes" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "" "Vous pouvez activer les améliorations qui ont été ajoutées à ce remake." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Activé avec compteur de munitions" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Langue" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Scripting" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Basculer vers la nouvelle arme" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Autoriser la triche" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Remplacer l'achèvement d'épisode" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Consulter dossier \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Rafraîchir le cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Oui" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Non" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Sans triche uniquement" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Meilleur score uniquement" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Toujours" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Mode de rendu" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Résolution" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Plein écran" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antialiasing" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Tramage en arrière-plan" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Qualité de l'eau" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Afficher traînées joueur" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Écran partagé préféré" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Préférer dézoomé" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Conserver ratio des cinématiques" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Vue non alignée" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Indicateurs de performance" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Graphismes" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Bas" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Haut" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertical" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horizontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Activé \f[c:#d0705d](Expérimental)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Court" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Détaillé" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Meilleurs scores pour \f[c:#d0705d]le jeu de base\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Meilleurs scores pour \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Sélectionner les fichiers du jeu d'origine pour débloquer des épisodes " "supplémentaires" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Traitement de {} fichier..." msgstr[1] "Traitement de {} fichiers..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "En attente des fichiers..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Aucun fichier sélectionné !" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Aucun nouvel épisode importé !" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Aucune manette trouvée !" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Choisir un mode de jeu" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Sons" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Profil de l'utilisateur" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Reprendre" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Spectateur" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Sauver & Quitter" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Déconnecter et quitter" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Connexion au serveur" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "" "Traitement des fichiers dans le dossier \f[c:#9e7056]\"Source\"\f[/c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "De nouveaux niveaux et épisodes seront bientôt disponibles." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Gauche" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Droite" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Haut" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Bas" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Coup vers le bas" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Tirer" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Sauter" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Courir" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Changer d'arme" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Retour" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Affichage de la console" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Arme {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Appuyer sur une touche ou un bouton pour attribuer" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "pour désattribuer" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Aucun / Au pixel près" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "Lignes d'écran cathodique" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "Masque d'écran cathodique" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "Grille d'écran cathodique" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monochrome" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Sélectionner le mode de rendu" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Aucun serveur trouvé, mais la recherche continue !" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Appuyez sur \f[c:#d0705d]Feu\f[/c] pour continuer" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Volume global" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Sons" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Musique" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Nombre de joueurs locaux" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Difficulté" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Commencer" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Facile" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Moyen" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Difficile" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "" "Vous pouvez ajuster la position des zones tactiles en les faisant glisser." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Intégration Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Nom du joueur" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "ID unique de joueur" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Points : {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Le jeu commence dans {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "En attente de {} joueur en plus" msgstr[1] "En attente de {} joueurs en plus" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Trouve une sortie !" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Impossible de charger le niveau!\f[/c]\n" "\n" "\n" "Assurez-vous que tous les fichiers nécessaires\n" "sont accessibles et essayez à nouveau." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Impossible de rétablir la sauvegarde !\f[/c]\n" "\n" "\n" "Assurez-vous que tous les fichiers nécessaires\n" "sont accessibles et essayez à nouveau." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Serveur sans nom" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Paramètre invalide spécifié." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "La version de votre client n'est pas compatible avec ce serveur." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "L'authentification a échoué.\n" "Contactez un administrateur du serveur pour plus d'information." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Mot de passe incorrect." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Nom d'utilisateur invalide.\n" "Vérifiez votre profil et réessayez." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Ce client n'est pas autorisé à se connecter.\n" "Contactez un administrateur du serveur pour plus d'information." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Ce serveur nécessite une authentification tierce.\n" "Contactez un administrateur du serveur pour plus d'information." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Le serveur est plein.\n" "Essayez à nouveau plus tard." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Le serveur n'est pas dans l'état de traiter votre demande.\n" "Réessayez à nouveau dans quelques secondes." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Le serveur s'arrête.\n" "Essayez à nouveau plus tard." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Le serveur s'arrête pour maintenance.\n" "Essayez à nouveau plus tard." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Le serveur s'arrête pour reconfiguration.\n" "Essayez à nouveau plus tard." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Le serveur s'arrête pour mise à jour.\n" "Vérifiez la version de votre client et essayez à nouveau dans une minute." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]La connexion a été perdue!\f[/c]\n" "\n" "\n" "Réessayez à nouveau et si le problème persiste,\n" "vérifiez votre connexion à Internet." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur !\f[/c]\n" "\n" "\n" "Le serveur ne répond pas aux demandes de connexion." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Vous avez été \f[c:#907050]éjecté\f[/c] du serveur.\n" "Contactez un administrateur du serveur pour plus d'information." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Vous avez été \f[c:#725040]banni\f[/c] du serveur.\n" "Contactez un administrateur du serveur pour plus d'information." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]La connexion a été interrompue !\f[/c]\n" "\n" "\n" "Triche détectée." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur ![/c]\n" "\n" "\n" "Votre client ne contient pas les éléments requis.\n" "Veuillez télécharger les fichiers nécessaires et réessayez." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]La connexion a été interrompue ![/c]\n" "\n" "\n" "Le serveur vous a déconnecté pour cause d'inactivité." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Impossible de se connecter au serveur ![/c]\n" "\n" "\n" "Votre client ne contient pas le niveau \"{}\".\n" "Veuillez télécharger les fichiers nécessaires et réessayez." #~ msgid "Unknown" #~ msgstr "Inconnu" #~ msgid "Play Custom Game" #~ msgstr "Jouer une partie personnalisée" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Connexion à {}:{}..." #~ msgid "Create Custom Server" #~ msgstr "Créer un serveur personnalisé" #~ msgid "Connect to Server" #~ msgstr "Connecter au serveur" #~ msgid "or" #~ msgstr "ou" #~ msgid "Creating server..." #~ msgstr "Création du serveur..." deathkiller-jazz2-native-2a7ccef/Content/Translations/gl.mo000066400000000000000000000742551512772601700241570ustar00rootroot00000000000000", <P_Q^egnnlrJv4\#|UX+,1 ^ k     ( 6 )@ j |        '!+! C!P!c!!1! ! !!!!!" "" "")-""W" z"."+"""##'#0#?#D#K#b#@f# ## # ##7#$"$1$H$Z$r$$&$$$$%$%8%K% _%j%z%'%!%=%2&K&P&U&e&n& && &&'' '' '((($( (( 3( ?(I(Z(@n(((((( ( (V(I) X)c) r) ~)))))) )) ) )?):*<>*{*;@+|+(+ +,+.,&@,+g,),(,#,, -*7-&b-A->-% .(0.6Y.9..&.</AN/'/D/(/+&0@R0/0)060L$1?q1F1,1)%2SO2F2(223(F3Lo3N3E 40Q4,4(4>4450L59}575.5A6*`6<6'6I6D:7.737D76'8C^8d819C99<}9F9I:+K:Ew:F:J;CO;:;6;@<;F<5<D<.<F,=(s=@=}=[>:x>8>3>A ?0b?8?A? @E/@Cu@$@6@HAA^AEAIA?0B6pBIB@BD2CrwCC^CF^D0D1DQE+ZEEvFZF'F@ GQKG;G&G=HB>H,H3H4H@IXIp`KbK4LsLsCMMw8NgNiOObP{PQrQ^ R R*RR)RR RRSSSf2@fsf$ffFfE gJSg&g&gg<h2Eh)xh*h+h)h:#i*^i6i%iAiD(j mj j?j'j?k[Wk'k/k) l25l6hll?l;l<8m7um0mm7m&6n*]n*n n0no9o~So o,o$ p'0p6Xp%p,p8pq3,q:`qq-q9q8rAUrDr/r( sD5s0zs2spsOtLgt<tt u?0uputu\udYv)v4v?w-]ww<w:wx&6x"]x,xL g|&4!dTR k !6r ,$la:9fty. [2qYW7x* ev( `O3Km #_D/HiBcE+X%nZ);h^\-oz8s]QSbUN?0 J~}p "@1{GVFPw >CMu='5I"j A< [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files!AboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate ServerDetailedDevelopersDifficultyDisabledDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFireFor more information, visit the official website:FullscreenGame ModeGamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay Shareware DemoPlay StoryPrefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsStartStrongTeam BattleTeam RaceThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnknown commandUpVerticalWaiting for files...Water QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: enfeitizador Language-Team: enfeitizador Language: gl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp [c:#337233]Restabelece o xogo para ler os ficheiros do [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] correctamente. [c:#704a4a]Non se pode conectar co servidor! [/c] O servidor está completo. Proba máis tarde. [c:#704a4a]Non se pode conectar co servidor! [/c] O servidor non está nun estado que poida procesar a túa solicitude. Proba de novo dentro dun pouco. [c:#704a4a]Non se pode conectar co servidor! [/c] A túa versión do cliente non é compatíbel coa do servidor. [c:#704a4a]Non se pode conectar co servidor! [/c] A túa versión do cliente non é compatíbel coa do servidor. [c:#704a4a]Non se pode cargar o nivel especificado! [/c] Asegura que todo ficheiro necesario sexa accesíbel e proba de novo. [c:#704a4a]Non se pode retomar o progreso! [/c] Asegura que todo ficheiro necesario sexa accesíbel e proba de novo. [c:#704a4a]Pechouse a conexión! [/c] O servidor estase apagando para mantemento. Proba máis tarde. [c:#704a4a]Pechouse a conexión! [/c] O servidor estase apagando para configurarse. Proba máis tarde. [c:#704a4a]Pechouse a conexión! [/c] O servidor estase apagando para unha actualización. Comproba a túa versión de cliente e proba nun minuto. [c:#704a4a]Non se pode conectar co servidor! [/c] O servidor está completo. Proba máis tarde. [c:#704a4a]Pechouse a conexión! [/c] Fuches [c:#725040]baneado [/c] do servidor. Contacta coa administración do servidor para máis información. [c:#704a4a]Pechouse a conexión! [/c] Fuches [c:#907050]expulsado [/c] do servidor. Contacta coa administración do servidor para máis información. [c:#704a4a]Perdeuse a conexión! [/c] Proba de novo e se o problema continúa, revisa a túa conexión á rede. [c:#704a4a]Este xogo require ficheiros do [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] orixinal!Acerca deAcceso ao almacenamento externo garantido!Permitir trucosPermite o acceso ao almacenamento externoSempreSuavizadoAtrásTramado de fondoBatallaExplorar directorio "Source"Esmagar co traseiroCRT grella de aperturaCRT liñas de escaneadoCRT máscara de sombraCapturar a bandeiraCambiar armaPersonaxeNon se permiten trucos no contexto actualConectar ao servidorContinuarColaboradoresControisCooperativoCrear servidorDetalladoDesenvolvedoresDificultadeDesactivadoIntegración co DiscordAbaixoFácilActivadoActivado [c:#d0705d](experimental) [/c]Activado con reconto de municiónMellorasEpisodio bloqueado!Compatibilidade extendida para PlayStation™DispararPara máis información, visita a páxina web oficial:A toda pantallaModo de xogoEtiquetas dos botóns do mandoVibración do mandoAxustes do xogoGráficosDifícilAltoSó puntuación máis altaMáximas puntuaciónsMellores puntuacións para [c:#d0705d]xogo base [/c]Mellores puntuacións para [c:#d0705d]{} [/c]HorizontalQuero xogar ao xogo como se debería facer.Quero xogar ao xogo con algo novo.Importar episodiosDiagnoses de entradaSaltarManter proporción do aspecto nas cinemáticasIdiomaEscalada de saíntesEsquerdaLegadoNivel "{}" inicializadoBaixoAsegura que os ficheiros do Jazz Jackrabbit 2 estean no seguinte camiño:Volume xeralMedioMonocromáticoVolume músicaBotón atrás nativoOs novos niveis e episodios engadidos estarán dispoñíbeis axiña.NonSó sen trucosNon se atopa nivel personalizado!Non se atopa episodio!Non se seleccionaron ficheiros!Non se detectan mandos!Non se importaron novos episodios!Non se atoparon servidores, pero seguimos a busca!Nada / Píxel perfectoNúmero de participantes locaisOpciónsSobrescribir episodio completadoMétricas de rendementoXogar niveis personalizadosXogar Shareware DemoXogar historiaAfastamento preferidoPantalla partida preferidaPreme [c:#d0705d]Disparo [/c] para seguirPreme una tecla ou botón para asignarProcesando ficheiros no directorio [c:0x9e7056]"Source" [c]...A procesar {} ficheiro...A procesar {} ficheiros...SaírCarreiraRazer Chroma™ReforxadoXogabilidade reforxadaHUD reforxadoMenú principal reforxadoRecargar cachéRe-implementación do xogo [c:#9e7056]Jazz Jackrabbit 2 [/c] publicado no 1998. Admite varias versións do xogo (Shareware Demo, Holiday Hare '98, The Secret Files e Christmas Chronicles). Tamén admite parcialmente algunhas características da extensión JJ2+.Reconfigurar controisReasignar controis para participante {}Modo de escaladoRestabelecer ao por defectoResoluciónRestabelecer episodioRetomarDereitaCorrerVolume efectosGardar e saírSecuencia de comandosSelecciona modo de xogoSelecciona modo de escaladoSelecciona ficheiros do xogo orixinal para desbloquear episodios adicionaisCurtoAmosar rastro do xogadorSonsComezarForteBatalla en equipoCarreira en equipoEste proxecto usa o motor de xogo [c:#9e7056]nCine [c] modificado e as seguintes librarías:Alternar consolaAlternar correrControis táctilesTradutoresBusca do tesouroXanela gráfica sen aliñarOrde descoñecidaArribaVerticalAgardando por ficheiros...Calidade da augaFebleRoda de armasArma {}Benvida a [c:#9e7056]Jazz Jackrabbit 2 [/c] re-implementado!SiPodes axustar a posición das zonas de toque por esvarar e soltar.Podes escoller o teu estilo de xogo preferido. Esta opción pode cambiarse en calquera momento en [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Para máis información, visita {} e  Discord!Podes activar as melloras engadidas a este remake.Tes que completar "{}" antes! Os dragóns viven en Burbank. Atopa o esquío. Mark leva calzóns slip. Hoo Hah! Nick ama o brillante. Desde sempre! Spaz comeu o Dopefish. Coidado coa parvada da motoserra. Non lle deas a Mark un burrito. Mándalle a Nigel unha tarxeta verde. Mándalle a Tim calcetíns novos. Coidado cos inimigos que caen. Craig segue a ser un idiota! Tempo do nivel secreto!!! Esmaga co traseiro unha caixa de prata para limpar o camiño As caixas tamén fan aparecer plataformas... Leh é un campista Derrete o resorte... O lanzachamas é o tal contra os bechos. As pedras que caen poden darche xaquecas. Boa noite, Bubba! Longa vida ao nivel de xeo. Que cousa? Aaaah! Non! Isto NON rematou! Recolle moedas para activar dispositivos de fiestras extra. Nada que ver aquí. Os mastros fante xirar para que poidas ir máis rápido. Cuarto do tesouro secreto. Atopaches unha área secreta. Esmaga co traseiro a caixa de metal para abrir os bloques chave! O queixo é verde o martes. Craig é o rei parvo. Bo traballo! Agora colle a Devan Shell! Preme abaixo e salta baixo nestes bloques para rompelos! Para derrotar á raíña tíraa do seu rebordo. Para pasar a través destes bloques, preme abaixo e salto! Esmaga o teu botín para saír. O resorte está conxelado. Os escudos danche munición especial ilimitada por un tempo. Os cronómetros engaden tempo á vida dun escudo. Secreto súper xenial. Este Schwartzenguard está torrado! Ai! Lisco de aquí! Non me poderás gañar, Jazz! Prepárate a enfrontar o súper robot! Estes son bloques de velocidade. Corre cara eles o máis que poidas! Despois de saltar, preme saltar de novo para facer un movemento especial. Coidado coas cousas aguzadas. Mancan. As xemas azuis contan como dez xemas. As cenorias danche saúde. Os puntos de control gardan o progreso se perdes unha vida. Recolle moedas para desbloquear cuartos de bonos. Recolle xemas para unha vida adicional. Recolle obxectos para puntos e sorpresas. Bo traballo. Lembra buscar polos secretos. As xemas verdes contan como cinco xemas. Agora estás a punto para xogar. Boa sorte e divírtete. As xemas vermellas contan como unha xema. Os secretos abundan no Jazz 2. Comproba as paredes. Pódese disparar a algunhas paredes. Benvida a Jazz Jackrabbit 2. Este é un nivel de adestramento. Cando estás no aire, preme abaixo para pegar duro co teu traseiro. Esmaga duro a tampa da arqueta! Os aneis de fume vante marear. Non lle gañes a Nigel ao billar. Advertíuseche, xa sabes. :) Atopa a caixa para despexar o camiño. Non hai recompensa para eses con ronchas nos dedos do gatillo. Só Spaz pode chegar ao cuarto de arriba á esquerda. Quizais necesite algo onde pousarse. Prognóstico de hoxe: Fortes refachos! Benvida a Jazz Jackrabbit 2: The Secret Files! Non podes esmagalo así que sube e xira!Rillar moitos ovos de chocolate farate enfermar :p Unha ruta leva á riqueza. Unha ruta leva á batalla. Entrada ao túnel inclinado Para acceder aos túneles de arriba atopa a fiestra de acceso. Atopa a caixa para facer que aparezan os bloques a escalar Só aqueles que fan o dobre salto conseguirán os obxectos! Esmagando esta caixa tamén libera algúns inimigos :) Pero necesitas unha maneira de chegar arriba... Entra na casa con cautela..... As caixas de prata non se poden romper baixo a auga... Esmagar caixas pode ser bo ou malo... Caixa de control do nivel de auga enriba. Michelle, amareite sempre e por sempre :) E non fixéchelo salto, meu? :) Salta o máis cara á dereita que sexas quen... Sube aos tellados! Os ceos por riba han recompensar aqueles que esmaguen... Usa os movementos especiais para chegar aos condutos! Para Jazz, preme agachar e saltar. Para Spaz, preme saltar dúas veces! Ben feito! Recoller 20 moedas é máis gratificante... Atopa a caixa e as xemas son túas! Os resortes non funciona conxelados... COIDADO! As mandas de corvos poden ser moi perigosas. Escolle unha cuberta e esmaga forte! Atopa a caixa para limpar estes bloques.... Vai a www.project2.com e usa o contrasinal: BUNNYLOVER Ola GeoBunny :) Para quitar os bloques mira o edificio máis alto. As moedas danche acceso a fiestras que aparecen despois. Dispara a eses bloques! Algunhas caixas conteñen bombas ou malotes! Esmaga no lugar correcto e quizais haches unha sorpresa! Para pasar esta área, esmaga a caixa secreta de metal. Cando estás no aire, preme abaixo para esmagar co teu traseiro. Necesitas vinte moedas para pasar a través desta fiestra secreta! O lanzachamas vai ben contra vermes noxentos. Os aneis de fumes farán que te marees! Necesitas vinte moedas para pasar a través desta fiestra secreta! Coidado coa bruxa! Pódete converter nunha rá. Se te convertes nunha ra Eva Earlong pode axudar! Fixéchelo! Este é o final da versión de demostración. Agora bota un ollo á folla de pedido para M Á I S!para quitar asignación Saúdos estacionais de Epic MegaGames Orange Games e Project 2 Interactive! Algúns bloques só se poden romper cunha arma específica. Coidado cos picos de abaixo! Benvida a Christmas Chronicles! Podes esmagar por medio de algúns puntos febles nos camiños! Ola, Piggy! - Poopy Kassi Nicole: Quixera dicirche un millón de cousas pero as miñas palabras levan ao mesmo fin Quérote, quérote. Michelle: Cambiaches a miña vida de moitas formas Adícote este proxecto. Quérote moito. Entrada ao sendeiro de xemas. Rube ás copas das árbores para atopar e esmagar a caixa de entrada. Caixón de entrada ao sendeiro de xemas. Bater nos bloques encima túa pode ser beneficioso. Podes esmagar por medio de algúns puntos febles nos camiños! Podes parar encima de algunhas das árbores! Non perdas o control! Agora abandonas Burrowsville Por favor, visítanos de novo. Contrasinal: xmasbunny Por favor, visita www.project2.com Usa a túa TNT sabiamente. Robert e Craig: Os resortes MOLAN! :) Esa ponte non se ve moi segura... Benvida a Burrowsville Conduce con coidado.deathkiller-jazz2-native-2a7ccef/Content/Translations/gl.po000066400000000000000000001763101512772601700241550ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:57+0100\n" "PO-Revision-Date: \n" "Last-Translator: enfeitizador \n" "Language-Team: enfeitizador\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz comeu o Dopefish." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Atopa o esquío." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Os dragóns viven en Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark leva calzóns slip. \n" "Hoo Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick ama o brillante. \n" "Desde sempre!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Mándalle a Tim calcetíns novos." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Mándalle a Nigel unha tarxeta verde." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Coidado coa parvada da motoserra." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Non lle deas a Mark un burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Coidado cos inimigos que caen." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig segue a ser un idiota!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Tempo do nivel secreto!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Esmaga co traseiro unha caixa de prata\n" "para limpar o camiño" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "As caixas tamén fan aparecer plataformas..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Derrete o resorte..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh é un campista" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "As pedras que caen \n" "poden darche xaquecas." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "O lanzachamas é o tal\n" "contra os bechos." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Longa vida ao nivel de xeo." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Boa noite, Bubba!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Que cousa? Aaaah! Non! \n" "Isto NON rematou!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Os mastros fante xirar para que\n" " poidas ir máis rápido." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Atopaches unha área secreta." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Cuarto do tesouro secreto." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Nada que ver aquí." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Recolle moedas para activar \n" "dispositivos de fiestras extra." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "O queixo é verde o martes." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig é o rei parvo." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Para derrotar á raíña \n" "tíraa do seu rebordo." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Bo traballo! \n" "Agora colle a Devan Shell!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Para pasar a través destes\n" "bloques, preme abaixo e salto!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Preme abaixo e salta baixo \n" "nestes bloques para rompelos!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Esmaga co traseiro a caixa de metal \n" "para abrir os bloques chave!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Esmaga o teu botín para saír." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "O resorte está conxelado." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Secreto súper xenial." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Os escudos danche munición \n" "especial ilimitada por un tempo." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Os cronómetros engaden tempo á\n" "vida dun escudo." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Este Schwartzenguard está torrado!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Non me poderás gañar, Jazz!\n" "Prepárate a enfrontar o súper robot!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Ai! Lisco de aquí!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Estes son bloques de velocidade.\n" "Corre cara eles o máis que poidas!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Benvida a Jazz Jackrabbit 2. \n" " Este é un nivel de adestramento." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Recolle obxectos para\n" "puntos e sorpresas." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Despois de saltar, preme saltar de\n" "novo para facer un movemento especial." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Pódese disparar a algunhas paredes." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Cando estás no aire, preme abaixo\n" "para pegar duro co teu traseiro." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Os secretos abundan no Jazz 2. \n" " Comproba as paredes." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Bo traballo. Lembra buscar\n" "polos secretos." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Recolle xemas para \n" "unha vida adicional." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "As xemas vermellas contan\n" "como unha xema." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "As xemas verdes contan\n" "como cinco xemas." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "As xemas azuis contan\n" "como dez xemas." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "As cenorias danche saúde." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Os puntos de control gardan\n" "o progreso se perdes unha vida." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Recolle moedas para\n" "desbloquear cuartos de bonos." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Coidado coas cousas aguzadas.\n" "Mancan." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Agora estás a punto para xogar.\n" " Boa sorte e divírtete." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Esmaga duro a tampa da arqueta!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Os aneis de fume \n" "vante marear." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Non podes esmagalo\n" "así que sube e xira!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Non hai recompensa para eses\n" "con ronchas nos dedos do gatillo." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Prognóstico de hoxe: Fortes refachos!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Atopa a caixa\n" "para despexar o camiño." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Benvida a\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Só Spaz pode chegar ao\n" "cuarto de arriba á esquerda.\n" "Quizais necesite algo onde\n" "pousarse." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Non lle gañes a Nigel ao billar.\n" "Advertíuseche, xa sabes. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Rillar moitos ovos de\n" "chocolate farate enfermar :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Entrada ao túnel inclinado" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Para acceder aos túneles de arriba\n" "atopa a fiestra de acceso." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Unha ruta leva á riqueza.\n" "Unha ruta leva á batalla." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Só aqueles que fan o dobre salto\n" "conseguirán os obxectos!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Atopa a caixa para facer\n" "que aparezan os bloques a escalar" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Esmagando esta caixa tamén\n" "libera algúns inimigos :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Entra na casa con cautela....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "As caixas de prata non se poden romper baixo a auga..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Caixa de control do nivel de auga enriba." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Esmagar caixas pode ser bo ou malo..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Pero necesitas unha maneira de chegar arriba..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "amareite sempre e por sempre :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Sube aos tellados!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Os ceos por riba han recompensar\n" "aqueles que esmaguen..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Salta o máis cara á\n" "dereita que sexas quen..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "E non fixéchelo salto, meu? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Ben feito!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Usa os movementos especiais para chegar aos condutos!\n" "Para Jazz, preme agachar e saltar.\n" "Para Spaz, preme saltar dúas veces!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Atopa a caixa e as xemas son túas!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Os resortes non funciona conxelados..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Recoller 20 moedas é máis gratificante..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Atopa a caixa para limpar estes bloques...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "COIDADO! As mandas de corvos poden\n" "ser moi perigosas." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Para quitar os bloques\n" "mira o edificio máis alto." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Escolle unha cuberta e esmaga forte!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Ola GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Vai a www.project2.com\n" "e usa o\n" "contrasinal: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Dispara a eses bloques!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Cando estás no aire, preme abaixo\n" "para esmagar co teu traseiro." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Para pasar esta área, esmaga\n" "a caixa secreta de metal." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Necesitas vinte moedas para pasar \n" "a través desta fiestra secreta!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "As moedas danche acceso a \n" "fiestras que aparecen despois." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Esmaga no lugar correcto e\n" "quizais haches unha sorpresa!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Algunhas caixas conteñen\n" "bombas ou malotes!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Necesitas vinte moedas para pasar \n" "a través desta fiestra secreta!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "O lanzachamas vai ben \n" "contra vermes noxentos." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Os aneis de fumes farán\n" "que te marees!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Coidado coa bruxa! Pódete\n" "converter nunha rá." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Se te convertes nunha ra\n" "Eva Earlong pode axudar!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Fixéchelo! Este é o final\n" " da versión de demostración.\n" "Agora bota un ollo á folla de pedido\n" "para M Á I S!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Coidado cos picos de abaixo!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Podes esmagar por medio\n" "de algúns puntos febles\n" "nos camiños!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Algúns bloques só se\n" "poden romper cunha\n" "arma específica." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Benvida a Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Saúdos estacionais de\n" "Epic MegaGames\n" "Orange Games e\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Podes parar encima de\n" "algunhas das árbores!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Podes esmagar por medio\n" "de algúns puntos febles\n" "nos camiños!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Bater nos bloques encima túa\n" "pode ser beneficioso." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Caixón de entrada ao sendeiro de xemas." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Entrada ao sendeiro de xemas.\n" "Rube ás copas das árbores para atopar\n" "e esmagar a caixa de entrada." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Ola, Piggy!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Cambiaches a miña vida de moitas formas\n" "Adícote este proxecto.\n" "Quérote moito." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Quixera dicirche un millón de cousas\n" "pero as miñas palabras levan ao mesmo fin\n" "Quérote, quérote." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Usa a túa TNT sabiamente." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Non perdas o control!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Benvida a Burrowsville\n" "Conduce con coidado." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Esa ponte non se ve\n" "moi segura..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert e Craig:\n" "Os resortes MOLAN! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Agora abandonas Burrowsville\n" "Por favor, visítanos de novo." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Contrasinal: xmasbunny\n" "Por favor, visita\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Nivel \"{}\" inicializado" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Para máis información, visita a páxina web oficial:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Non se permiten trucos no contexto actual" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Orde descoñecida" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Re-implementación do xogo \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] publicado no " "1998. Admite varias versións do xogo (Shareware Demo, Holiday Hare '98, The " "Secret Files e Christmas Chronicles). Tamén admite parcialmente algunhas " "características da extensión JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Desenvolvedores" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Colaboradores" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Tradutores" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Este proxecto usa o motor de xogo \f[c:#9e7056]nCine\f[c] modificado e as " "seguintes librarías:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Xogar historia" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Xogar Shareware Demo" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importar episodios" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Continuar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Máximas puntuacións" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Opcións" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Acerca de" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Saír" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Acceso ao almacenamento externo garantido!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Restabelece o xogo para ler os ficheiros do \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#337233] correctamente." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Este xogo require ficheiros do \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] orixinal!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Asegura que os ficheiros do Jazz Jackrabbit 2 estean no seguinte camiño:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Permite o acceso ao almacenamento externo" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Reasignar controis para participante {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Reconfigurar controis" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Controis táctiles" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Alternar correr" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Etiquetas dos botóns do mando" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibración do mando" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Compatibilidade extendida para PlayStation™" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Botón atrás nativo" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnoses de entrada" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Restabelecer ao por defecto" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Controis" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Forte" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Feble" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Desactivado" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Activado" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Personaxe" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Modo de xogo" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Crear servidor" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Batalla" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Batalla en equipo" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Carreira" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Carreira en equipo" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Busca do tesouro" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Capturar a bandeira" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Cooperativo" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Xogar niveis personalizados" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Non se atopa nivel personalizado!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Non se atopa episodio!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Restabelecer episodio" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Tes que completar \"{}\" antes!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episodio bloqueado!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Legado" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Quero xogar ao xogo como se debería facer." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Reforxado" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Quero xogar ao xogo con algo novo." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Benvida a \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] re-implementado!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Axustes do xogo" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Melloras" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Podes escoller o teu estilo de xogo preferido.\n" "Esta opción pode cambiarse en calquera momento en \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "Para máis información, visita {} e  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Xogabilidade reforxada" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "HUD reforxado" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Menú principal reforxado" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Escalada de saíntes" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Roda de armas" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Podes activar as melloras engadidas a este remake." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Activado con reconto de munición" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Idioma" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Secuencia de comandos" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Permitir trucos" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Sobrescribir episodio completado" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Explorar directorio \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Recargar caché" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Si" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Non" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Só sen trucos" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Só puntuación máis alta" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Sempre" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Modo de escalado" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Resolución" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "A toda pantalla" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Suavizado" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Tramado de fondo" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Calidade da auga" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Amosar rastro do xogador" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Pantalla partida preferida" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Afastamento preferido" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Manter proporción do aspecto nas cinemáticas" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Xanela gráfica sen aliñar" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Métricas de rendemento" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Gráficos" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Baixo" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Alto" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertical" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horizontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Activado \f[c:#d0705d](experimental)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Curto" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detallado" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Mellores puntuacións para \f[c:#d0705d]xogo base\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Mellores puntuacións para \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Selecciona ficheiros do xogo orixinal para desbloquear episodios adicionais" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "A procesar {} ficheiro..." msgstr[1] "A procesar {} ficheiros..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Agardando por ficheiros..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Non se seleccionaron ficheiros!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Non se importaron novos episodios!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Non se detectan mandos!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Selecciona modo de xogo" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Sons" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Retomar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Gardar e saír" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Conectar ao servidor" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Procesando ficheiros no directorio \f[c:0x9e7056]\"Source\"\f[c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Os novos niveis e episodios engadidos estarán dispoñíbeis axiña." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Esquerda" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Dereita" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Arriba" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Abaixo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Esmagar co traseiro" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Disparar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Saltar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Correr" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Cambiar arma" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Atrás" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Alternar consola" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Arma {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Preme una tecla ou botón para asignar" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "para quitar asignación" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Nada / Píxel perfecto" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT liñas de escaneado" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT máscara de sombra" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT grella de apertura" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monocromático" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Selecciona modo de escalado" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Non se atoparon servidores, pero seguimos a busca!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Preme \f[c:#d0705d]Disparo\f[/c] para seguir" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Volume xeral" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Volume efectos" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Volume música" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Número de participantes locais" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Dificultade" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Comezar" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Fácil" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Medio" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Difícil" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Podes axustar a posición das zonas de toque por esvarar e soltar." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integración co Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "" msgstr[1] "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Non se pode cargar o nivel especificado!\f[/c]\n" "\n" "\n" "Asegura que todo ficheiro necesario\n" "sexa accesíbel e proba de novo." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Non se pode retomar o progreso!\f[/c]\n" "\n" "\n" "Asegura que todo ficheiro necesario\n" "sexa accesíbel e proba de novo." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Non se pode conectar co servidor!\f[/c]\n" "\n" "\n" "A túa versión do cliente non é compatíbel coa do servidor." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Non se pode conectar co servidor!\f[/c]\n" "\n" "\n" "O servidor está completo.\n" "Proba máis tarde." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Non se pode conectar co servidor!\f[/c]\n" "\n" "\n" "O servidor non está nun estado que poida procesar a túa solicitude.\n" "Proba de novo dentro dun pouco." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Non se pode conectar co servidor!\f[/c]\n" "\n" "\n" "O servidor está completo.\n" "Proba máis tarde." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Pechouse a conexión!\f[/c]\n" "\n" "\n" "O servidor estase apagando para mantemento.\n" "Proba máis tarde." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Pechouse a conexión!\f[/c]\n" "\n" "\n" "O servidor estase apagando para configurarse.\n" "Proba máis tarde." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]Pechouse a conexión!\f[/c]\n" "\n" "\n" "O servidor estase apagando para unha actualización.\n" "Comproba a túa versión de cliente e proba nun minuto." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]Perdeuse a conexión!\f[/c]\n" "\n" "\n" "Proba de novo e se o problema continúa,\n" "revisa a túa conexión á rede." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Non se pode conectar co servidor!\f[/c]\n" "\n" "\n" "A túa versión do cliente non é compatíbel coa do servidor." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Pechouse a conexión!\f[/c]\n" "\n" "\n" "Fuches \f[c:#907050]expulsado\f[/c] do servidor.\n" "Contacta coa administración do servidor para máis información." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Pechouse a conexión!\f[/c]\n" "\n" "\n" "Fuches \f[c:#725040]baneado\f[/c] do servidor.\n" "Contacta coa administración do servidor para máis información." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" #~ msgid "Unknown" #~ msgstr "Descoñecido" #~ msgid "Play Custom Game" #~ msgstr "Xogar partida personalizada" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Conectando a {}:{}..." #~ msgid "Create Custom Server" #~ msgstr "Crear servidor propio" #~ msgid "Connect to Server" #~ msgstr "Conectar ao servidor" #~ msgid "or" #~ msgstr "ou" #~ msgid "Creating server..." #~ msgstr "Creando servidor..." #~ msgid "Error" #~ msgstr "Erro" #~ msgid "Created By" #~ msgstr "Creado por" deathkiller-jazz2-native-2a7ccef/Content/Translations/hu.mo000066400000000000000000001107141512772601700241600ustar00rootroot00000000000000FL |XYx_{McL{^zieg g!!y"n"l"A^#r#v$$\%ay%%t&| 'X'' (6"(.Y((,( ( (( (())) 8)B) V)d)t) ) )))))) )) **** ?*M*i* r* }*******'** ++)+ I+T+1Y+/+ + ++++,,,,#, 5,)@,"j, ,.,+,,---:-C-R-W-^-u-@y- -- - --7-2.5.D.[.m...&...//7/K/ ^/j/ ~/ / ///'/!/=02N000000 00 0011 2!2 22=2M2T2Z2 ^2 i2 u222@22223333 03 <3F3VY33 33 3 3344'464 94F4O46d4 44 4 4?45< 5F5; 6G6(e6 6,6.6& 7+27)^7(7#7,7*8&-8AT8>8%8(86$99[99&9<9A:'[:D:(:+:@;/^;);6;L;?<<F|<,<)<S=Fn=(=2=(>L:>N>E>0?,M?(z?>?4?0@9H@7@.@A@*+A<VA'AIADB.JB3yBDB6BC)CdmC1CCD<HDFDID+EEBEFEJECF:^F6F@F;G5MGDG.GFG(>H@gH}H&I:CI8~I3IAI0-J8^JAJ JEJC@K$K6KHKA)LEkLIL?L6;MIrM@MDMrBNN^NF)O0pO1OQO+%PQPvPZRQ'Q@QQR;hR&R=RB S,LS3yS4S@S#T V&Vk5VVICWIWWxbXXYjRZZz[\j\*]]l^_=_s_yJ``fea^a+bbxcad tdd?d6d*e=3eqe.ee eeeee f%f 5fAfUfffvf7ff fff f gg 8gYg-og g g gggggg g, h#7h[hlh(hh h>hW ibi {i*iii iijj j -j78j;pj j=j9j0kHk^k/ekk kk kkkjk MlYl blllqlKll&lm m9m'Wm!m.mmmm$ n2nHn Ynen nn n&nn=nA2oUtoKo p p(p DpQp'nppppq q rr 8rCr [rfrkr rrr rr#rPr2s(9sbs is usss sssjs:tRtct t ttttttt uu-*u Xufumu |uCuuHuvA"w5dw!ww!w9w2x)Nxxx%xx$xxyE,y*ryyy*y.y%z8z,NzG{zz>z {"7{*Z{{{:{L{:?|Fz|!||?|<?}|}&}}J}(~O?~(~8~~[4m.72 8<Nu8Ā7&57\G"܁"6"5Y$O#:(Bc56܃O,<|DH&G!n9Dʅ(28 k0Bӆy :3؇* =74u6M/>BM ω2P#<t<O<>({VA==`{%܌b=e$!ȍB-Gqˎy=#9ۏB&X=F̐%'9-a<Yu y}'q3 /P  .*O?@$>*w##%[JnA<x%rE )7V"82zo{)=s;Mh$R0>l\'+Ue= 2D &4CI" TW8SHB3KL9<+:!(p-b5GCj7 /64a5DfQ X@N9d`_-E]~,F^i&1 |:.kc6!F(Z?vBmA1gt0 ;, The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContinuous JumpContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: thatcakepiece Language: hu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp A játék hamarosan kezdődik! A nyertes {} [c:#337233]Indítsd újra a játékot a [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] fájlok beolvasásához. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] Sikertelen hitelesítés! További információért vedd fel a kapcsolatot a szerver adminisztrátoraival. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] Hibás paraméter. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] Helytelen jelszó. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] Helytelen felhasználónév. Kérlek ellenőrizd a profilodat, majd probáld újra. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] A szerver kapacitása megtelt. Kérlek, próbáld újra később. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] A szerver nincs olyan állapotban, hogy feldolgozhassa a kérésedet. Kérlek, próbáld újra néhány másodperc múlva. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] Ez a szerver harmadik feltől származó azonosítást vár. További információért vedd fel a kapcsolatot a szerver adminisztrátoraival. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] A szerver nem válaszol a csatlakozási kérelemre. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] Ez a kilens nincs jelen a szerver fehérlistájában További információért vedd fel a kapcsolatot a szerver adminisztrátoraival. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] A kliensed nem tartalmazza a(z) "{}" szintet. Töltsd le a szükséges fájlokat, majd próbáld újra. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] A kliensed nem tartalmazza a szükséges fájlokat. Töltsd le a szükséges fájlokat, majd próbáld újra. [c:#704a4a]Nem lehet csatlakozni a szerverhez! [/c] A kliensed verziója nem kompatibilis a szerverrel. [c:#704a4a]Nem lehet létrehozni a szervert! [/c] A lejátszási lista nincs rendesen beállítva. Kérlek ellenőrizd a szerverkonfigurációt és próbáld újra. [c:#704a4a]Nem lehet létrehozni a szervert! [/c] Kérlek bizonyosodj meg arról, hogy nem-e fut egy másik szerver ezen a porton és próbáld újra. [c:#704a4a]Nem lehet betölteni a megadott pályát! [/c] Győződj meg róla, hogy az összes szükséges fájl elérhető, és próbáld újra. [c:#704a4a]Nem lehet folytatni a mentett állapotot! [/c] Győződj meg róla, hogy az összes szükséges fájl elérhető, és próbáld újra. [c:#704a4a]A kapcsolat megszakadt! [/c] Csalás észlelve. [c:#704a4a]A kapcsolat megszakadt! [/c] A szerver karbantartás miatt leáll. Kérlek, próbáld újra később. [c:#704a4a]A kapcsolat megszakadt! [/c] A szerver újrakonfigurálás miatt leáll. Kérlek, próbáld újra később. [c:#704a4a]A kapcsolat megszakadt! [/c] A szerver frissítés miatt leáll. Kérlek, ellenőrizd a kliens verzióját, és próbáld újra egy percen belül. [c:#704a4a]A kapcsolat megszakadt! [/c] A szerver éppen leáll. Kérjük, próbálja meg később. [c:#704a4a]A kapcsolat megszakadt! [/c] A szerver bontotta a kapcsolatot tétlenség miatt. [c:#704a4a]A kapcsolat megszakadt! [/c] [c:#907050]Ki lettél tiltva [/c] a szerverről. További információért vedd fel a kapcsolatot a szerver adminisztrátoraival. [c:#704a4a]A kapcsolat megszakadt! [/c] Ki lettél [c:#907050]rúgva [/c] a szerverről. További információkért fordulj a szerveradminisztrátorokhoz. [c:#704a4a]A kapcsolat megszakadt! [/c] Kérlek, próbálja meg újra, és ha a probléma továbbra is fennáll, ellenőrizd a hálózati kapcsolatot. [c:#704a4a]Ez a játék az eredeti [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] fájlokat igényli! [c:#d0705d]{} [/c] csatlakozott [c:#d0705d]{} [/c] kilépett [c:#d0705d]{} [/c] megsütötte [c:#d0705d]{} [/c] játékost [c:#d0705d]{} [/c] játékost a környezet kiütötteNévjegyA külső tárhelyhez való hozzáférés engedélyezve lett!Csalások engedélyezéseKülső tárhely elérésének engedélyezéseMinden alkalommalÉlsimításVisszaHáttér zajszórásaCsataJátékfájlok böngészéseSeggcsapásCRT rekeszrácsCRT vonalakCRT árnyékszűrőCapture The FlagFegyverváltásKarakterA csalások nem engedélyezettek a jelenlegi helyzetbenCsatlakozás a szerverhezFolytatásFolytonos ugrásKözreműködőkIrányításTöbbjátékosPrivát szerver létrehozásaNyilvános szerver létrehozásaSzerver létrehozásaSzerver létrehozása lejátszási listábólRészletesFejlesztőkNehézségLetiltvaKapcsolat bontásaDiscord integrációLeKönnyűEngedélyezveEngedélyezve [c:#d0705d](Kísérleti) [/c]Bekapcsolva, lőszer számlálóvalKiegészítésekAz epizód zárolva van!Kiterjesztett PlayStation™ támogatásKeress kijáratot!TüzelésTovábbi információért látogass el a hivatalos weboldalra:További információért látogass el a {} weboldalra és a  Discord szerverünkre!Teljes képernyős ablakJátékmódA játék {} másodpercen belül kezdődikJátékvezérlő gombfeliratokJátékvezérlő rezgéseJátékmenetGrafikaNehézMagasCsak magasabb pontszám eseténRanglétraAz [c:#d0705d]alapjáték [/c] legmagasabb pontszámaiLegmagasabb pontszámok a(z) [c:#d0705d]{} [/c] epizódhozVízszintesÚgy akarok játszani a játékkal, mint amilyen régen volt.Valami újdonsággal szeretnék találkozni a játékban.Epizódok importálásaBeviteli diagnosztikaUgrásKéparány megtartása az átvezető videókbanNyelvPeremmászásBalHagyományosA(z) „{}” pálya betöltveAlacsonyGyőződj meg róla, hogy a Jazz Jackrabbit 2 fájljai megtalálhatók a következő elérési útvonalon:FőhangerőKözepesMonokrómZeneBeépített vissza gombA frissen hozzáadott pályák és epizódok hamarosan elérhetők lesznek.NemCsak csalás nélküli játék eseténNem található egyéni pálya!Nem található epizód!Nem választottál ki fájlt!Nincs csatlakoztatott játékvezérlő!Nem lett importálva új epizód!Nem találtunk szervereket, de még nyomozunk!Nincs / Pixel-pontosHelyi játékosok számaBeállításokEpizód eredményeinek felülírásaTeljesítménymérőkEgyéni pályákOnline módShareware változat indításaÚj játékJátékos nevePontszám: {}Kicsinyítés előnyben részesítéseOsztott képernyő elrendezéseNyomd meg a [c:#d0705d]Tüzelés [/c] gombot a folytatáshozNyomj meg bármilyen billentyűt vagy gombot a hozzárendeléshezA fájlok feldolgozása a [c:#9e7056]"Source" [/c] könyvtárban folyamatban van...{} fájl feldolgozása folyamatban...{} fájl feldolgozása folyamatban...KilépésVersenyRazer Chroma™ támogatásFeljavítottÚjrakovácsolt játékmenetÚjrakovácsolt felhasználói felületÚjrakovácsolt főmenüGyorsítótár felfrissítéseA 1998-ban megjelent [c:#9e7056]Jazz Jackrabbit 2 [/c] játék újraírása. Támogatja a játék különböző verzióit (Shareware Demo, Holiday Hare '98, The Secret Files és Christmas Chronicles). Ezen kívül részben támogatja a JJ2+ kiegészítő egyes funkcióit.Gombkiosztás módosítása{}. játékosÁtméretezési módAlapértékre állításFelbontásEpizód újraindításaFolytatásJobbFutásHangeffektekMentés és kilépésSzkriptelésJátékmód kiválasztásaÁtméretezési mód kiválasztásaVálaszd ki az eredeti játék fájljait, hogy feloldhasd a további epizódokatRövidJátékos nyomvonalainak megjelenítéseHangokNéző módIndításErősÚj fegyverre váltásCsapatharcCsapatos versenyCsapatos kincskeresésEz a projekt a módosított [c:#9e7056]nCine [/c] játékmotort és az alábbi könyvtárakat használja:Konzol be/kikapcsolásaFolytonos futásÉrintőképernyős vezérlésFordítókKincskeresésEltolt nézetablakJátékosazonosítóIsmeretlen parancsNévtelen szerverFelFelhasználói profilFüggőlegesVárakozás a fájlokra...{} játékosra várunk{} játékosra várunkVízminőségGyengeFegyvertárcsa{}. fegyverÜdvözlünk a [c:#9e7056]Jazz Jackrabbit 2 [/c] újraírásában!IgenAz érintési zónák helyzetét a dobozok húzásával állíthatod be.Kiválaszthatod a számodra megfelelő játékmódot. Ez az opció bármikor módosítható a(z) [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c] menüpontban. További információért látogass el a {} weboldalra és a  Discord szerverünkre!Bekapcsolhatod a felújítás során hozzáadott fejlesztéseket.Először be kell fejezned a(z) "{}" nevű epizódot! A sárkányok Burbankban élnek. Találd meg a mormotát. Mark alsóneműt visel. Hú-ha! Nick szereti a csillogó dolgokat. Mindig is így volt! Spaz megette a dopefish-t. Óvakodj a láncfűrészes schmalz-tól. Ne adj Marknak burritót. Küldj Nigelnek egy zöld kártyát. Küldj Timnek új zoknikat. Vigyázz a lezuhanó ellenségekre. Craig még mindig egy idióta! Titkos szint!!! Csapj seggcsapással az ezüst ládára hogy megtisztítsd az utadat A ládák platformokat is felfedhetnek... Leh egy kempingező Olvaszd meg a rugót... A lángszóró hatékony a bogarak ellen. A lezuhanó sziklák fejfájást okozhatnak. Jó éjt, kicsim! Éljen a jégpálya. Mi a…? Aaaah! Ne! Ezzel még NINCS vége! Gyűjts érméket hogy aktiválhasd a bónusz térhajtó eszközöket. Nincs itt semmi látnivaló. A rúdak megpörgetnek, így még gyorsabban száguldozhatsz. Titkos Kincses Szoba. Találtál egy titkos területet. Seggcsapással nyisd ki a kulcsládákat! A sajt kedden zöld. Craig a bolondok királya. Szép munka! Most menj, és szerezd meg a Devan kagylót! Nézz lefelé ugrás közben, hogy széttörd az alattad lévő kockákat! A királynő legyőzéséhez lődd le a párkányáról. A kockákat úgy rúghatod át, hogy lefelé nézel miközben ugrasz! Vonszold a segged a kijárathoz. Ez a rugó meg van fagyva. A pajzsok rövid ideig korlátlan speciális lőszert adnak. A stopperórák meghosszabbítják a pajzs élettartamát. Szuper titkos. Ez a Schwartzenguard el van intézve! Júj! Itt sem vagyok! Nem győzhetsz le, Jazz! Készülj fel szembenézni a szuperrobotommal! Sebességblokkok! Teljes gázzal neki! Ugrás után ugorj még egyszer egy különleges mozdulat végrehajtásához. Vigyázz az éles dolgokra! Bántanak. A kék drágakövek 10 darab drágakőnek számítanak. A sárgarépák életet adnak. Az ellenőrzőpontok elmentik a helyzeted, életveszteség esetén itt fogsz újraéledni. Gyűjts érméket a bónuszszobák kinyitásához. Gyűjts drágaköveket további életekért. Gyűjts jóságokat pontokért és meglepetésekért. Szép munka! Ne felejts el mindig körülnézni. A zöld drágakövek 5 darab drágakőnek számítanak. Most már készen állsz a játékra. Sok szerencsét és jó szórakozást. A piros drágakövek 1 darab drágakőnek számítanak. A Jazz 2 tele van titkokkal! Vizsgáld meg a falakat. Bizonyos objektumokat le tudsz lőni. Üdv a Jazz Jackrabbit 2-ben! Ez egy gyakorlópálya. Míg a levegőben vagy nézz lefelé, hogy seggcsapást hajts végre. Seggcsapást a csatornafedélnek! A füstkarikák megszédítenek. Ne verd meg Nigelt biliárdban. Figyelmeztettelek. :) Találd meg a ládát, hogy megtisztítsd az utadat. A türelmetleneknek nincs jutalom. Csak Spaz juthat fel a bal felső szobába. Kellhet valami, amin megállhat. A mai előrejelzés: erős szelek! Köszöntünk a Jazz Jackrabbit 2: The Secret Files-ban! Itt nem tudsz seggcsapást használni, menj fel és kerüld meg!Túl sok csokitojás megevése betegséget okozhat :p Az egyik út gazdagsághoz vezet. A másik csatához. Lejtős alagútbejárat A felső alagutak hozzáféréséhez keresd meg a teleportáló készüléket. Találd meg a ládát, hogy megjelenjenek a mászókockáid Csak azok juthatnak el a jóságokhoz akik képesek magasra ugrani! Ennek a ládának a seggcsapása néhány ellenséget is kiszabadít :) De kell egy mód, hogy feljuss oda… Óvatosan lépj be a házba..... Az ezüstládákat nem lehet széttörni a víz alatt… A ládák seggcsapással való rugdosása lehet jó is, rossz is… A vízszint szabályzó láda fent van. Michelle, Mindig és örökké szeretni foglak :) Nem sikerült az ugrás, mi? :) Ugorj a lehető legtávolabbra jobb irányba… Menj fel a tetőkre! Az égiek megjutalmazzák azokat akik seggcsapást használnak... Használd a különleges mozdulataidat, hogy feljuss a légkondikra! Jazz: guggolj le, majd ugorj. Spaz:, ugorj duplán! Szép volt! 20 érme összegyűjtése még nagyobb jutalommal jár… Találd meg a ládát, és a drágakövek a tieid! A rugók nem működnek, ha megfagynak… VIGYÁZZ! A hollókseregek nagyon veszélyesek tudnak lenni. Válassz fedezéket, és rúgj neki seggcsapással! Találd meg a ládát a kockák eltakarításához… Látogass el a www.project2.com oldalra és használd a BUNNYLOVER jelszót Szia GeoNyuszi :) A kockák eltávolításához nézz a legmagasabb épületre. Az érmék lehetővé teszik a később megjelenő térkapuk használatát. Lődd szét ezeket a kockákat! Néhány láda bombákat vagy ellenségeket rejt! Használd a seggcsapást a megfelelő helyen, és nyeremény ütheti markodat! A terület teljesítéséhez rúgd le a titkos fémládát. A levegőben nézz lefelé, hogy seggcsapást hajts végre. Ehhez a titkos teleportáló készülékhez 20 darab érmére van szükséged! A lángszóró tökéletes ezek a nyavalyás bogarak ellen. A füstkarikák nagyon megszédítenek! 20 darab érmére van szükséged, hogy használhasd ezt a térhajtó készüléket! Vigyázz a boszorkánnyal! Képes téged békává változtatni. Ha békává változtál Hosszúfülü Eva segíthet rajtad! Sikerült! Ez itt a ShareWare verzió vége. Vedd meg a teljes játékot további tartalomért!a hozzárendelés eltávolításához Ünnepi üdvözlet az Epic MegaGames-től, az Orange Games-től és a Project 2 Interactive-tól! Néhány kocka csak egy megadott fegyverrel törhető szét. Vigyázz az alul lévő tüskékre! Üdv a Christmas Chronicles-ben! A seggcsapással átütheted az útvonal számos gyenge pontjait! Szia, Malacka! - Poopy Kassi Nicole: Millió dolgot szeretnék elmondani neked, de a szavaim mindig ugyanahhoz a véghez vezetnek: Szeretlek, szeretlek. Michelle: Oly sokféleképpen változtattad meg az életemet Ezt a projektet neked szentelem. Nagyon szeretlek. Drágakő-ösvény bejárata. Mássz fel a fa tetejére, hogy megtaláld és seggcsapással feltörd a belépő ládát. Drágakő-ösvény belépő láda. A feletted lévő kockák ütögetése jutalmazó lehet. A seggcsapással átütheted az útvonal számos gyenge pontjait! Néhány fa tetején megtudsz állni! Ne engedd el! Most elhagyja Burrowsville-t Kérjük, látogasson el újra. Jelszó: xmasbunny Kérlek látogass el a www.project2.com weboldalra Kérlek a TNT-t tudatosan használd. Robert és Craig: A rugók URALNAK! :) Az a híd nem tűnik túl biztonságosnak... Köszöntjük Burrowsville-ben Kérjük vezessen óvatosan.deathkiller-jazz2-native-2a7ccef/Content/Translations/hu.po000066400000000000000000002055621512772601700241710ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:57+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: thatcakepiece\n" "Language: hu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz megette a dopefish-t." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Találd meg a mormotát." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "A sárkányok Burbankban élnek." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark alsóneműt visel. \n" "Hú-ha!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick szereti a csillogó dolgokat. \n" "Mindig is így volt!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Küldj Timnek új zoknikat." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Küldj Nigelnek egy zöld kártyát." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Óvakodj a láncfűrészes schmalz-tól." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Ne adj Marknak burritót." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Vigyázz a lezuhanó ellenségekre." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig még mindig egy idióta!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Titkos szint!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Csapj seggcsapással az ezüst ládára\n" "hogy megtisztítsd az utadat" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "A ládák platformokat is felfedhetnek..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Olvaszd meg a rugót..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh egy kempingező" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "A lezuhanó sziklák \n" "fejfájást okozhatnak." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "A lángszóró hatékony\n" "a bogarak ellen." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Éljen a jégpálya." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Jó éjt, kicsim!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Mi a…? Aaaah! Ne!\n" "Ezzel még NINCS vége!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "A rúdak megpörgetnek,\n" "így még gyorsabban száguldozhatsz." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Találtál egy titkos területet." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Titkos Kincses Szoba." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Nincs itt semmi látnivaló." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Gyűjts érméket hogy aktiválhasd\n" "a bónusz térhajtó eszközöket." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "A sajt kedden zöld." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig a bolondok királya." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "A királynő legyőzéséhez \n" "lődd le a párkányáról." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Szép munka!\n" "Most menj, és szerezd meg a Devan kagylót!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "A kockákat úgy rúghatod át,\n" "hogy lefelé nézel miközben ugrasz!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Nézz lefelé ugrás közben, \n" "hogy széttörd az alattad lévő kockákat!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Seggcsapással nyisd ki\n" "a kulcsládákat!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Vonszold a segged a kijárathoz." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Ez a rugó meg van fagyva." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Szuper titkos." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "A pajzsok rövid ideig korlátlan \n" " speciális lőszert adnak." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "A stopperórák meghosszabbítják \n" "a pajzs élettartamát." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Ez a Schwartzenguard el van intézve!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Nem győzhetsz le, Jazz! \n" "Készülj fel szembenézni a szuperrobotommal!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Júj! Itt sem vagyok!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Sebességblokkok! \n" "Teljes gázzal neki!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Üdv a Jazz Jackrabbit 2-ben! \n" "Ez egy gyakorlópálya." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Gyűjts jóságokat pontokért \n" "és meglepetésekért." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Ugrás után ugorj még egyszer \n" "egy különleges mozdulat végrehajtásához." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Bizonyos objektumokat le tudsz lőni." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Míg a levegőben vagy nézz lefelé, \n" "hogy seggcsapást hajts végre." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "A Jazz 2 tele van titkokkal! \n" "Vizsgáld meg a falakat." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Szép munka! Ne felejts el \n" "mindig körülnézni." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Gyűjts drágaköveket \n" "további életekért." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "A piros drágakövek\n" "1 darab drágakőnek számítanak." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "A zöld drágakövek\n" "5 darab drágakőnek számítanak." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "A kék drágakövek\n" "10 darab drágakőnek számítanak." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "A sárgarépák életet adnak." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Az ellenőrzőpontok elmentik a helyzeted,\n" "életveszteség esetén itt fogsz újraéledni." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Gyűjts érméket a \n" "bónuszszobák kinyitásához." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Vigyázz az éles dolgokra! \n" "Bántanak." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Most már készen állsz a játékra. \n" "Sok szerencsét és jó szórakozást." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Seggcsapást a csatornafedélnek!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "A füstkarikák \n" "megszédítenek." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Itt nem tudsz seggcsapást használni, \n" "menj fel és kerüld meg!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "A türelmetleneknek \n" "nincs jutalom." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "A mai előrejelzés: erős szelek!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Találd meg a ládát, hogy\n" "megtisztítsd az utadat." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Köszöntünk a \n" "Jazz Jackrabbit 2:\n" "The Secret Files-ban!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Csak Spaz juthat fel \n" "a bal felső szobába.\n" "Kellhet valami, \n" "amin megállhat." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Ne verd meg Nigelt biliárdban.\n" "Figyelmeztettelek. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Túl sok csokitojás megevése\n" "betegséget okozhat :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Lejtős alagútbejárat" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "A felső alagutak hozzáféréséhez\n" "keresd meg a teleportáló készüléket." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Az egyik út gazdagsághoz vezet.\n" "A másik csatához." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Csak azok juthatnak el a jóságokhoz\n" "akik képesek magasra ugrani!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Találd meg a ládát, hogy\n" "megjelenjenek a mászókockáid" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Ennek a ládának a seggcsapása néhány\n" "ellenséget is kiszabadít :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Óvatosan lépj be a házba....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Az ezüstládákat nem lehet széttörni a víz alatt…" #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "A vízszint szabályzó láda fent van." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "A ládák seggcsapással való rugdosása lehet jó is, rossz is…" #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "De kell egy mód, hogy feljuss oda…" #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Mindig és örökké szeretni foglak :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Menj fel a tetőkre!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Az égiek megjutalmazzák azokat\n" "akik seggcsapást használnak..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Ugorj a lehető\n" "legtávolabbra jobb irányba…" #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Nem sikerült az ugrás, mi? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Szép volt!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Használd a különleges mozdulataidat, hogy feljuss a légkondikra!\n" "Jazz: guggolj le, majd ugorj.\n" "Spaz:, ugorj duplán!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Találd meg a ládát, és a drágakövek a tieid!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "A rugók nem működnek, ha megfagynak…" #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "20 érme összegyűjtése még nagyobb jutalommal jár…" #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Találd meg a ládát a kockák eltakarításához…" #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "VIGYÁZZ! A hollókseregek\n" "nagyon veszélyesek tudnak lenni." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "A kockák eltávolításához nézz\n" "a legmagasabb épületre." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Válassz fedezéket, és rúgj neki seggcsapással!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Szia GeoNyuszi :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Látogass el a www.project2.com oldalra\n" "és használd a\n" "BUNNYLOVER jelszót\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Lődd szét ezeket a kockákat!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "A levegőben nézz lefelé,\n" "hogy seggcsapást hajts végre." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "A terület teljesítéséhez rúgd\n" "le a titkos fémládát." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Ehhez a titkos teleportáló készülékhez 20 darab\n" "érmére van szükséged!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Az érmék lehetővé teszik a \n" "később megjelenő térkapuk használatát." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Használd a seggcsapást a megfelelő helyen, \n" "és nyeremény ütheti markodat!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Néhány láda bombákat vagy\n" "ellenségeket rejt!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "20 darab érmére van szükséged, hogy \n" "használhasd ezt a térhajtó készüléket!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "A lángszóró tökéletes ezek\n" "a nyavalyás bogarak ellen." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "A füstkarikák nagyon\n" "megszédítenek!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Vigyázz a boszorkánnyal! Képes\n" "téged békává változtatni." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Ha békává változtál\n" "Hosszúfülü Eva segíthet rajtad!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Sikerült! Ez itt a ShareWare\n" "verzió vége.\n" "Vedd meg a teljes játékot\n" "további tartalomért!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Vigyázz az alul lévő tüskékre!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "A seggcsapással átütheted\n" "az útvonal számos\n" "gyenge pontjait!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Néhány kocka csak\n" "egy megadott fegyverrel\n" "törhető szét." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Üdv a Christmas Chronicles-ben!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Ünnepi üdvözlet az\n" "Epic MegaGames-től,\n" "az Orange Games-től\n" "és a Project 2 Interactive-tól!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Néhány fa tetején\n" "megtudsz állni!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "A seggcsapással átütheted\n" "az útvonal számos\n" "gyenge pontjait!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "A feletted lévő kockák\n" "ütögetése jutalmazó lehet." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Drágakő-ösvény belépő láda." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Drágakő-ösvény bejárata.\n" "Mássz fel a fa tetejére, hogy megtaláld\n" "és seggcsapással feltörd a belépő ládát." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Szia, Malacka!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Oly sokféleképpen változtattad meg az életemet\n" "Ezt a projektet neked szentelem.\n" "Nagyon szeretlek." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Millió dolgot szeretnék elmondani neked,\n" "de a szavaim mindig ugyanahhoz a véghez vezetnek:\n" "Szeretlek, szeretlek." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Kérlek a TNT-t tudatosan használd." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Ne engedd el!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Köszöntjük Burrowsville-ben\n" "Kérjük vezessen óvatosan." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Az a híd nem tűnik\n" "túl biztonságosnak..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert és Craig:\n" "A rugók URALNAK! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Most elhagyja Burrowsville-t\n" "Kérjük, látogasson el újra." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Jelszó: xmasbunny\n" "Kérlek látogass el a\n" "www.project2.com weboldalra" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "A(z) „{}” pálya betöltve" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "További információért látogass el a hivatalos weboldalra:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "A csalások nem engedélyezettek a jelenlegi helyzetben" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "A játék hamarosan kezdődik!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] megsütötte \f[c:#d0705d]{}\f[/c] játékost" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] játékost a környezet kiütötte" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] kilépett" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] csatlakozott" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "A nyertes {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Ismeretlen parancs" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "A 1998-ban megjelent \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] játék újraírása. " "Támogatja a játék különböző verzióit (Shareware Demo, Holiday Hare '98, The " "Secret Files és Christmas Chronicles). Ezen kívül részben támogatja a JJ2+ " "kiegészítő egyes funkcióit." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Fejlesztők" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Közreműködők" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Fordítók" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Ez a projekt a módosított \f[c:#9e7056]nCine\f[/c] játékmotort és az alábbi " "könyvtárakat használja:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Új játék" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Shareware változat indítása" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Epizódok importálása" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Folytatás" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Online mód" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Ranglétra" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Beállítások" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Névjegy" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Kilépés" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "" "További információért látogass el a {} weboldalra és a  Discord " "szerverünkre!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "A külső tárhelyhez való hozzáférés engedélyezve lett!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Indítsd újra a játékot a \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] fájlok beolvasásához." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Ez a játék az eredeti \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] fájlokat igényli!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Győződj meg róla, hogy a Jazz Jackrabbit 2 fájljai megtalálhatók a következő " "elérési útvonalon:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Külső tárhely elérésének engedélyezése" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "{}. játékos" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Gombkiosztás módosítása" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Érintőképernyős vezérlés" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Folytonos futás" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Játékvezérlő gombfeliratok" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Játékvezérlő rezgése" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Kiterjesztett PlayStation™ támogatás" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Beépített vissza gomb" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Beviteli diagnosztika" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Alapértékre állítás" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Irányítás" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Erős" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Gyenge" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Letiltva" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Engedélyezve" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Karakter" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Játékmód" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Szerver létrehozása" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Csata" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Csapatharc" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Verseny" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Csapatos verseny" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Kincskeresés" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Csapatos kincskeresés" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Capture The Flag" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Többjátékos" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Nem lehet létrehozni a szervert!\f[/c]\n" "\n" "\n" "A lejátszási lista nincs rendesen beállítva.\n" "Kérlek ellenőrizd a szerverkonfigurációt és próbáld újra." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Nem lehet létrehozni a szervert!\f[/c]\n" "\n" "\n" "Kérlek bizonyosodj meg arról, hogy\n" "nem-e fut egy másik szerver ezen a porton és próbáld újra." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Egyéni pályák" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nem található egyéni pálya!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Szerver létrehozása lejátszási listából" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Privát szerver létrehozása" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Nyilvános szerver létrehozása" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nem található epizód!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Epizód újraindítása" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Először be kell fejezned a(z) \"{}\" nevű epizódot!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Az epizód zárolva van!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Hagyományos" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Úgy akarok játszani a játékkal, mint amilyen régen volt." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Feljavított" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Valami újdonsággal szeretnék találkozni a játékban." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Üdvözlünk a \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] újraírásában!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Játékmenet" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Kiegészítések" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Kiválaszthatod a számodra megfelelő játékmódot.\n" "Ez az opció bármikor módosítható a(z) \f[c:#707070]{}\f[/c] > \f[c:#707070]" "{}\f[/c] > \f[c:#707070]{}\f[/c] menüpontban.\n" "További információért látogass el a {} weboldalra és a  Discord " "szerverünkre!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Újrakovácsolt játékmenet" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Újrakovácsolt felhasználói felület" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Újrakovácsolt főmenü" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Peremmászás" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Fegyvertárcsa" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Bekapcsolhatod a felújítás során hozzáadott fejlesztéseket." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Bekapcsolva, lőszer számlálóval" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Nyelv" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Szkriptelés" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "Folytonos ugrás" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Új fegyverre váltás" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Csalások engedélyezése" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Epizód eredményeinek felülírása" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™ támogatás" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Játékfájlok böngészése" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Gyorsítótár felfrissítése" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Igen" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Nem" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Csak csalás nélküli játék esetén" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Csak magasabb pontszám esetén" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Minden alkalommal" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Átméretezési mód" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Felbontás" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Teljes képernyős ablak" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Élsimítás" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Háttér zajszórása" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Vízminőség" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Játékos nyomvonalainak megjelenítése" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Osztott képernyő elrendezése" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Kicsinyítés előnyben részesítése" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Képarány megtartása az átvezető videókban" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Eltolt nézetablak" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Teljesítménymérők" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafika" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Alacsony" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Magas" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Függőleges" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Vízszintes" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Engedélyezve \f[c:#d0705d](Kísérleti)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Rövid" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Részletes" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Az \f[c:#d0705d]alapjáték\f[/c] legmagasabb pontszámai" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Legmagasabb pontszámok a(z) \f[c:#d0705d]{}\f[/c] epizódhoz" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Válaszd ki az eredeti játék fájljait, hogy feloldhasd a további epizódokat" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "{} fájl feldolgozása folyamatban..." msgstr[1] "{} fájl feldolgozása folyamatban..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Várakozás a fájlokra..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Nem választottál ki fájlt!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Nem lett importálva új epizód!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Nincs csatlakoztatott játékvezérlő!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Játékmód kiválasztása" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Hangok" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Felhasználói profil" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Folytatás" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Néző mód" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Mentés és kilépés" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Kapcsolat bontása" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Csatlakozás a szerverhez" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "" "A fájlok feldolgozása a \f[c:#9e7056]\"Source\"\f [/c] könyvtárban " "folyamatban van..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "A frissen hozzáadott pályák és epizódok hamarosan elérhetők lesznek." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Bal" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Jobb" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Fel" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Le" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Seggcsapás" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Tüzelés" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Ugrás" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Futás" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Fegyverváltás" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Vissza" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Konzol be/kikapcsolása" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "{}. fegyver" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Nyomj meg bármilyen billentyűt vagy gombot a hozzárendeléshez" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "a hozzárendelés eltávolításához" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Nincs / Pixel-pontos" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT vonalak" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT árnyékszűrő" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT rekeszrács" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monokróm" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Átméretezési mód kiválasztása" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Nem találtunk szervereket, de még nyomozunk!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Nyomd meg a \f[c:#d0705d]Tüzelés\f[/c] gombot a folytatáshoz" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Főhangerő" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Hangeffektek" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Zene" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Helyi játékosok száma" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Nehézség" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Indítás" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Könnyű" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Közepes" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Nehéz" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Az érintési zónák helyzetét a dobozok húzásával állíthatod be." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Discord integráció" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Játékos neve" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Játékosazonosító" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Pontszám: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "A játék {} másodpercen belül kezdődik" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "{} játékosra várunk" msgstr[1] "{} játékosra várunk" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Keress kijáratot!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nem lehet betölteni a megadott pályát!\f[/c]\n" "\n" "\n" "Győződj meg róla, hogy \n" "az összes szükséges fájl elérhető, és próbáld újra." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nem lehet folytatni a mentett állapotot!\f[/c]\n" "\n" "\n" "Győződj meg róla, hogy \n" "az összes szükséges fájl elérhető, és próbáld újra." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Névtelen szerver" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "Hibás paraméter." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "A kliensed verziója nem kompatibilis a szerverrel." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "Sikertelen hitelesítés!\n" "További információért vedd fel a kapcsolatot a szerver adminisztrátoraival." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "Helytelen jelszó." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "Helytelen felhasználónév.\n" "Kérlek ellenőrizd a profilodat, majd probáld újra." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "Ez a kilens nincs jelen a szerver fehérlistájában\n" "További információért vedd fel a kapcsolatot a szerver adminisztrátoraival." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "Ez a szerver harmadik feltől származó azonosítást vár.\n" "További információért vedd fel a kapcsolatot a szerver adminisztrátoraival." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "A szerver kapacitása megtelt.\n" "Kérlek, próbáld újra később." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "A szerver nincs olyan állapotban, hogy feldolgozhassa a kérésedet.\n" "Kérlek, próbáld újra néhány másodperc múlva." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "A szerver éppen leáll.\n" "Kérjük, próbálja meg később." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "A szerver karbantartás miatt leáll.\n" "Kérlek, próbáld újra később." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "A szerver újrakonfigurálás miatt leáll.\n" "Kérlek, próbáld újra később." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "A szerver frissítés miatt leáll.\n" "Kérlek, ellenőrizd a kliens verzióját, és próbáld újra egy percen belül." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "Kérlek, próbálja meg újra, és ha a probléma továbbra is fennáll,\n" "ellenőrizd a hálózati kapcsolatot." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "A szerver nem válaszol a csatlakozási kérelemre." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "Ki lettél \f[c:#907050]rúgva\f[/c] a szerverről.\n" "További információkért fordulj a szerveradminisztrátorokhoz." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "\f[c:#907050]Ki lettél tiltva\f[/c] a szerverről.\n" "További információért vedd fel a kapcsolatot a szerver adminisztrátoraival." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "Csalás észlelve." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "A kliensed nem tartalmazza a szükséges fájlokat. \n" "Töltsd le a szükséges fájlokat, majd próbáld újra." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]A kapcsolat megszakadt!\f[/c]\n" "\n" "\n" "A szerver bontotta a kapcsolatot tétlenség miatt." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Nem lehet csatlakozni a szerverhez!\f[/c]\n" "\n" "\n" "A kliensed nem tartalmazza a(z) \"{}\" szintet.\n" "Töltsd le a szükséges fájlokat, majd próbáld újra." #~ msgid "Unknown" #~ msgstr "Ismeretlen" #~ msgid "Play Custom Game" #~ msgstr "Egyéni játék" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Csatlakozás a {}:{}-hoz..." #~ msgid "Create Custom Server" #~ msgstr "Egyéni kiszolgáló létrehozása" #~ msgid "Connect to Server" #~ msgstr "Csatlakozás kiszolgálóhoz" deathkiller-jazz2-native-2a7ccef/Content/Translations/it.mo000066400000000000000000001067041512772601700241640ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@SSUUmUmVWWVXWxW](XX#YnYNZ[[r\\$]]p0^I^x^xd_x_gV`a` aaXb`b;c%[c=c4c c1d3d(Ddmd tddd dddd!d"e9e Oe [e2geee e e eee ff *f 6f Cf Of[fpffff)ff fffg,g;2g6ng ggggg hh &h0h5hSh/ch'h h+h(hi,iCi+Iiui|iiiiiPi"j4j :j HjVj3ojjj&jjjk*+k.Vkkk k"kkk ll4l;l JlTlol+l'lBl5#mYm^mcmsm|m mmmmn+no/o LoXolouo|o o o oo+oIo9p?p\pbp jpxp~ppppep8qQqgq wqqqqqqqq qq?rTrhrorrHrrKr!s2s-tItet#vt>tt1t*u-Guuu#uuu:u9'vavzv3v3vv w,%w;RwwAww x<+x#hx x/x6xDy0Yy#yyJy.zAzWzjzLz?zI{1[{ {"{@{3|1F|5x|2|#|8}!>}.`}}H}A}#8~*\~4~!~?~n"9F>1DpAՀ=OU@/ 876p4+܂! *K9bq .'I)q> ڄ652h4zC( 25;hAS-:*hO=9![K=D"[‰v6dv+A[0S?2ی%%4-Z;?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Hexaae Language-Team: Hexaae Language: it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Il gioco inizierà a breve! Il vincitore è {} [c:#337233]Riavviare il gioco per leggere correttamente i file di [c:#9e7056]Jazz Jackrabbit 2 [c:#337233]. [c:#704a4a]Impossibile connettersi al server! [/c] Autenticazione fallita. Contattare gli amministratori del server per ulteriori informazioni. [c:#704a4a]Impossibile connettersi al server! [/c] Parametro non valido specificato. [c:#704a4a]Impossibile connettersi al server! [/c] Password specificata non valida. [c:#704a4a]Impossibile connettersi al server! [/c] Nome del giocatore non valido. Controlla il tuo profilo e riprova. [c:#704a4a]Impossibile connettersi al server! [/c] Il server è pieno. Prego riprova dopo. [c:#704a4a]Impossibile connettersi al server! [/c] Il server non è in uno stato idoneo a processare la tua richiesta. Prego, riprova tra alcuni secondi. [c:#704a4a]Impossibile connettersi al server! [/c] Il server richiede un provider di autenticazione di terze parti. Contattare gli amministratori del server per ulteriori informazioni. [c:#704a4a]Impossibile connettersi al server! [/c] Il server is non risponde alle richieste di connessione. [c:#704a4a]Impossibile connettersi al server! [/c] Questo client non è presente nella whitelist del server. Contattare gli amministratori del server per ulteriori informazioni. [c:#704a4a]Impossibile connettersi al server! [/c] Il client non contiene il livello “{}”. Scaricare i file necessari e riprovare. [c:#704a4a]Impossibile connettersi al server! [/c] Il tuo client non contiene le risorse necessarie. Scaricare i file necessari e riprovare. [c:#704a4a]Impossibile connettersi al server! [/c] La tua versione del client non è compatibile con il server. [c:#704a4a]Impossibile creare il server! [/c] La playlist non è configurata correttamente. Prego, controlla la configurazione server e riprova. [c:#704a4a]Impossibile creare il server! [/c] Prego, verifica che nessun altro server sia in esecuzione su quella porta e riprova. [c:#704a4a]Impossibile caricare il livello specificato! [/c] Assicurarsi che tutti i file necessari siano accessibili e riprovare. [c:#704a4a]Impossibile continuare! [/c] Assicurarsi che tutti i file necessari siano accessibili e riprovare. [c:#704a4a]La connessione è stata chiusa! [/c] Rilevato un imbroglio. [c:#704a4a]La connessione è stata chiusa! [/c] Il server è in chiusura per manutenzione. Prego, riprova più tardi. [c:#704a4a]La connessione è stata chiusa! [/c] Il server è in chiusura per manutenzione. Prego, riprova più tardi. [c:#704a4a]La connessione è stata chiusa! [/c] Il server è in chiusura per manutenzione. Prego, riprova più tardi. [c:#704a4a]La connessione è stata chiusa! [/c] Il server è in chiusura. Prego, riprova più tardi. [c:#704a4a]La connessione è stata chiusa! [/c] Il server ti ha disconnesso causa inattività. [c:#704a4a]La connessione è stata chiusa! [/c] Sei stato [c:#725040]bannato [/c] dal server. Contatta gli amministratori per maggiori informazioni. [c:#704a4a]La connessione è stata chiusa! [/c] Sei stato [c:#907050]buttato fuori [/c] dal server. Contatta gli amministratori per maggiori informazioni. [c:#704a4a]La connessione è stata persa! [/c] Prego, riprova e se il problema persiste, controlla la tua connessione di rete. [c:#704a4a]Questo gioco richiede i file originali di [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]! [c:#d0705d]{} [/c] è connesso [c:#d0705d]{} [/c] si è disconnesso [c:#d0705d]{} [/c] è stato arrostito da [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] è stato arrostito dall'ambienteInformazioniL'accesso all'archivio esterno è stato concesso!Permetti TrucchiPermetti l'accesso a un archivio esternoSempreAntialiasingIndietroRetinatura di sfondoBattagliaSfoglia cartella "Source"Schiacciata col didietroGriglia di apertura CRTLinee di scansione (Scanline) CRTMaschera d'ombra (Shadow Mask) CRTAcchiappa la bandieraCambia armaPersonaggioI trucchi non sono consentiti nel contesto attualeConnettiti al serverContinuaCollaboratoriControlliCooperazioneCrea server privatoCrea server pubblicoCrea serverCrea server da playlistDettagliatoSviluppatoriDifficoltàDisattivataDisconnettiti e EsciIntegrazione DiscordGiùFacileAttivataAbilitata [c:#d0705d](Sperimentale) [/c]Con Conteggio MunizioniMiglioramentiEpisodio bloccato!Supporto PlayStation™ estesoTrova l'uscita!FuocoPer ulteriori informazioni, visitare il sito Web ufficiale:Per ulteriori informazioni, visitare {} e  Discord!Schermo pienoModalità di giocoIl gioco comincerà tra {}Nomi dei bottoni del gamepadVibrazione gamepadGiocabilitàGraficaDifficileAltaSolo in caso di Punteggi AltiPunteggi recordPunteggi record per [c:#d0705d]Gioco base [/c]Punteggi record per [c:#d0705d]{} [/c]OrizzontaleVoglio giocare al gioco come era una volta.Voglio giocare ma con qualcosa di nuovo.Importa EpisodiDiagnostica immissioneSaltaMantenere il rapporto d'aspetto nei filmatiLinguaArrampicati sulle sporgenzeSinistraCompatibilitàLivello "{}" inizializzatoBassaAssicurati che i file di Jazz Jackrabbit 2 siano presenti nel seguente percorso:Volume principaleMediaMonocromaticoVolume musicaPulsante Indietro nativoLivelli e episodi nuovi saranno presto disponibili.NoSolo senza TrucchiNessun livello personalizzato trovato!Nessun episodio trovato!Nessun file selezionato!Nessun gamepad rilevato!Nessuno nuovo episodio è stato importato!Nessun server trovato, ma sto ancora cercando!Pixel-perfettoNumero di giocatori localiImpostazioniSovrascrivi Completamento EpisodioMetriche delle prestazioniGioco PersonalizzatoGioca onlineGioca la Demo SharewareStoriaNome giocatorePunti: {}Visuale più ampia (multi)Preferenze schermo condivisoPremi [c:#d0705d]Fuoco [/c] per continuarePremi un tasto o pulsante per assegnareElaborazione dei file nella directory [c:#9e7056]"Source" [/c]...Processamento file {}...Processamento dei file {}...EsciGaraRazer Chroma™RivedutoGiocabilità rivistaHUD rivistoMenu principale rivistoRigenera la CacheReimplementazione del gioco [c:#9e7056]Jazz Jackrabbit 2 [/c] rilasciato nel 1998. Supporta varie versioni del gioco (Shareware Demo, Holiday Hare '98, The Secret Files e Christmas Chronicles). Inoltre, supporta parzialmente alcune funzioni dell'estensione JJ2+.Riconfigura i ControlliRiconfigura i controlli per il Giocatore {}Modalità ridimensionamentoReimposta valori predefinitiRisoluzioneRicomincia EpisodioRiprendiDestraCorriVolume SFXSalva e esciScriptaggioSeleziona modalità di giocoSeleziona la modalità di ridimensionamentoSeleziona i file del tuo gioco originale per sbloccare episodi aggiuntiviBreveMostra la scia del giocatoreSuoniOsservaIniza PartitaForteCambia con nuova armaBattaglia di gruppoGara a squadreCaccia al tesoro a squadreQuesto progetto utilizza il motore di gioco [c:#9e7056]nCine [/c] modificato e le seguenti librerie:Attiva/disattiva consoleCambia corsa continuaControlli touchTraduttoriCaccia al tesoroViewport non allineataPlayer ID univocoComando sconosciutoServer senza nomeSuProfilo utenteVerticaleIn attesa dei file...In attesa di {} altro giocatoreIn attesa di {} altri giocatoriQualità dell'acquaDeboleRuota delle ArmiArma {}Benvenuti nella reimplementazione di [c:#9e7056]Jazz Jackrabbit 2 [/c]!SìÈ possibile regolare la posizione delle zone touch mediante trascinamento.Puoi scegliere il tuo stile di gioco preferito. Potrai cambiare questa opzione in qualsiasi momento in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Per altre informazioni, visita {} e  Discord!Abilita i miglioramenti aggiunti da questo remake.Devi prima completare "{}"! I draghi vivono a Burbank. Trova la talpa. Mark porta i calzoncini. Ooh Aah! A Nick piace tutto quello che luccica. È sempre stato cosi! Spaz ha mangiato il Dopefish. Stai attento agli Schmalz con la sega elettrica. Non dare a Mark un burrito. Manda a Nigel una carta verde per l'entrata. Manda a Tim dei nuovi calzini. Stai attento ai nemici che cadono. Craig è sempre un tontolone! Livello segreto!!! Schiaccia Una Cassa d'Argento Per Liberare Il Tuo Cammino Le casse possono anche far apparire delle piattaforme... Leh è un campeggiatore Sciogli la molla... Un lanciafiamme funziona bene contro gli insetti. I massi che cadono possono darti il mal di testa. Buonanotte, bubba! Viva l'era del ghiaccio. Ma che diamine? Aaaah! No! Non è finita! Raccogli le monete per attivare i tunnel di teletrasporto. Qui con c'è niente da vedere. I pali ti faranno roteare in modo da spararti a tutta velocità. Stanza segreta del tesoro. Hai trovato una zona segreta. Schiaccia la cassa in metallo per aprire i blocchi-chiave! Il formaggio è verde il martedì. Craig è il re degli imbecilli. Congratulazioni! Ora puoi uscire dal castello! Premi Giù e Salto sotto questi blocchi per romperli! Per ottenere la vittoria sulla regina buttala giù dalla sporgenza. Per superare questi blocchi premi Giù e Salto! Stampa il tuo didietro per uscire. La molla è ghiacciata. Per un breve periodo gli scudi ti forniranno munizioni speciali infinite. Un cronometro prolunga la durata degli scudi. Super-extra segreto. Guardianen cotto! Ahio, Ahio! Me ne vado! Non puoi battermi, Jazz! Preparati a combattere contro il mio Super-Robot! Questi sono blocchi da velocità. Corri sparato verso di loro! Dopo aver saltato, premi di nuovo Salto per eseguire una mossa speciale. Stai attento agli oggetti appuntiti. Fanno male. Le gemme blu contano per dieci. Le carote fanno bene alla salute. I punti di controllo salveranno la posizione se perdi una vita. Raccogli le monete per aprire le stanze dei bonus. Raccogli le gemme per guadagnare un'altra vita. Raccogli le leccornie per ricevere punti e sorprese. Ben fatto. Ricordati di cercare sempre i segreti. Le gemme verdi contano per cinque. Ora sei pronto per giocare. Buona fortuna e divertiti! Le gemme rosse contano per una. Jazz 2 è pieno di segreti. Controlla i muri. Ad alcuni muri puoi sparare. Benvenuto a Jazz Jackrabbit 2. Questo livello è solo di riscaldamento. Mentre sei in aria, premi Giù per una schiacciata col didietro. Schiaccia col didietro il tombino! Gli anelli di fumo ti stordiranno un po'. Non battere Nigel a biliardo. Sei stato avvisato :) Trova la cassa per farti strada. Niente ricompensa per quelli con le dita dal grilletto facile. Solo Spaz può arrivare alla stanza in alto a sinistra. Forse ha bisogno di qualcosa su cui poggiare i piedi. Previsioni per oggi: vento forte! Siete i benvenuti a Jazz Jackrabbit 2: The Secret Files! Non puoi eseguire una schiacciata col didietro, pertanto fai il giro!Se mangi troppe uova di cioccolato ti può venire la nausea :p Un percorso porta alla ricchezza. Un percorso porta alla battaglia. Ingresso del tunnel in discesa Per accedere ai tunnel di sopra trova il punto di teletrasporto. Trova la cassa per far comparire i blocchi per arrampicarti. Solo quelli che possono fare un doppio salto possono arrivare alle cose belle! Distruggendo queste casse si libereranno anche alcuni nemici :) Ma devi trovare un modo per arrivare lassù... Entra in casa con prudenza..... Non si possono rompere le casse d'argento sott'acqua... Schiacciare le casse può essere un bene o un male... Cassa di controllo del livello dell'acqua di sopra. Michelle, ti amerò sempre e per sempre :) Non hai fatto il salto, vero? :) Salta più a destra che puoi... Vai in cima ai tetti! I cieli lassù ricompenseranno quelli che schiacciano... Usa le tue mosse speciali per salire in aria! Per Jazz, premi Giù e poi Salto. Per Spaz, premi due volte Salto! Ben fatto! È più remunerativo raccogliere 20 monete... Trova la cassa e le gemme saranno tue! Le molle non vanno quando sono gelate... ATTENTO! Gli stormi di corvi possono essere molto pericolosi. Scegli un coperchio e fila via! Trova la cassa per fare piazza pulita dei blocchi.... Vai su www.project2.com usa la password: BUNNYLOVER Ciao GeoBunny :) Per rimuovere i blocchi cerca l'edificio più alto. Le monete ti fanno entrare nei tunnel che appariranno più in là. Spara a questi blocchi! Alcune casse contengono bombe o nemici! Colpisci il punto giusto e troverai una sorpresa! Per passare questa zona rompi la cassa segreta in metallo. Mentre sei in aria, premi Giù per una schiacciata col didietro. Hai bisogno di venti monete per passare attraverso questo tunnel di teletrasporto! Un lanciafiamme è utile contro gli insetti. Gli anelli di fumo ti stordiranno un po'! Hai bisogno di venti monete per passare attraverso il tunnel di teletrasporto! Stai attento alla strega! Può farti diventare un ranocchio. Se sei diventato un ranocchio Eva Earlong può aiutarti! Ce l'hai fatta! Questa è la fine della versione Shareware. Ora controlla le informazioni per fare l'ordine per averne A N C O R A!rimuovi tasto assegnato Saluti di stagione da Epic MegaGames Orange Games e Project 2 Interactive! Alcuni blocchi possono essere distrutti solo con certe armi. Occhio alle punte di sotto! Benvenuto a Christmas Chronicles! Con una schiacciata del didietro puoi passare attraverso alcuni punti deboli del percorso! Ciao, Piggy! - Poopy Kassi Nicole: Un milione di cose che vorrei dirti Ma le mie parole portano sempre allo stesso finale Ti amo, ti amo. Michelle: hai cambiato la mia vita in molti modi. Dedico questo progetto a te. Ti amo così tanto. Ingresso del Sentiero delle Gemme. Arrampicati sulle cime degli alberi per trovare e schiacciare la Cassa d'Ingresso. Cassa d'Ingresso del Sentiero delle Gemme. Prendere a pugni i blocchi sopra di te può essere gratificante. Con una schiacciata del didietro puoi passare attraverso alcuni punti deboli del percorso! Puoi restare in piedi in cima ad alcuni alberi! Non perdere la presa! In partenza da Burrowsville Vi invitiamo a tornare a trovarci. Password: xmasbunny Prego visita www.project2.com Prego, utilizza il TNT con saggezza. Robert e Craig: Le molle REGNANO! :) Quel ponte non sembra essere molto sicuro... Benvenuti a Burrowsville Si prega di guidare con prudenza.deathkiller-jazz2-native-2a7ccef/Content/Translations/it.po000066400000000000000000002040311512772601700241570ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:57+0100\n" "PO-Revision-Date: \n" "Last-Translator: Hexaae\n" "Language-Team: Hexaae\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz ha mangiato il Dopefish." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Trova la talpa." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "I draghi vivono a Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark porta i calzoncini. \n" "Ooh Aah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "A Nick piace tutto quello che luccica. \n" "È sempre stato cosi!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Manda a Tim dei nuovi calzini." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Manda a Nigel una carta verde per l'entrata." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Stai attento agli Schmalz con la sega elettrica." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Non dare a Mark un burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Stai attento ai nemici che cadono." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig è sempre un tontolone!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Livello segreto!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Schiaccia Una Cassa d'Argento\n" "Per Liberare Il Tuo Cammino" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Le casse possono anche far apparire delle piattaforme..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Sciogli la molla..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh è un campeggiatore" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "I massi che cadono possono \n" "darti il mal di testa." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Un lanciafiamme funziona \n" "bene contro gli insetti." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Viva l'era del ghiaccio." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Buonanotte, bubba!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Ma che diamine? Aaaah! No! \n" "Non è finita!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "I pali ti faranno roteare in modo\n" "da spararti a tutta velocità." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Hai trovato una zona segreta." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Stanza segreta del tesoro." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Qui con c'è niente da vedere." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Raccogli le monete per attivare\n" "i tunnel di teletrasporto." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "Il formaggio è verde il martedì." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig è il re degli imbecilli." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Per ottenere la vittoria sulla regina\n" "buttala giù dalla sporgenza." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Congratulazioni!\n" "Ora puoi uscire dal castello!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Per superare questi blocchi\n" "premi Giù e Salto!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Premi Giù e Salto sotto\n" "questi blocchi per romperli!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Schiaccia la cassa in metallo \n" "per aprire i blocchi-chiave!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Stampa il tuo didietro per uscire." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "La molla è ghiacciata." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Super-extra segreto." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Per un breve periodo gli scudi\n" "ti forniranno munizioni speciali infinite." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Un cronometro prolunga\n" "la durata degli scudi." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Guardianen cotto!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Non puoi battermi, Jazz!\n" "Preparati a combattere contro il mio Super-Robot!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Ahio, Ahio! Me ne vado!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Questi sono blocchi da velocità.\n" "Corri sparato verso di loro!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Benvenuto a Jazz Jackrabbit 2.\n" "Questo livello è solo di riscaldamento." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Raccogli le leccornie per\n" "ricevere punti e sorprese." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Dopo aver saltato, premi di nuovo Salto\n" "per eseguire una mossa speciale." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Ad alcuni muri puoi sparare." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Mentre sei in aria, premi Giù\n" "per una schiacciata col didietro." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Jazz 2 è pieno di segreti.\n" "Controlla i muri." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Ben fatto. Ricordati di\n" "cercare sempre i segreti." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Raccogli le gemme\n" " per guadagnare un'altra vita." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Le gemme rosse contano\n" " per una." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Le gemme verdi contano\n" "per cinque." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Le gemme blu contano\n" "per dieci." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Le carote fanno bene alla salute." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "I punti di controllo salveranno\n" "la posizione se perdi una vita." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Raccogli le monete per\n" "aprire le stanze dei bonus." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Stai attento agli oggetti appuntiti.\n" "Fanno male." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Ora sei pronto per giocare.\n" " Buona fortuna e divertiti!" #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Schiaccia col didietro il tombino!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Gli anelli di fumo ti\n" "stordiranno un po'." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Non puoi eseguire una schiacciata\n" "col didietro, pertanto fai il giro!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Niente ricompensa per quelli\n" "con le dita dal grilletto facile." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Previsioni per oggi: vento forte!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Trova la cassa\n" "per farti strada." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Siete i benvenuti a\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Solo Spaz può arrivare alla\n" "stanza in alto a sinistra.\n" "Forse ha bisogno di qualcosa\n" "su cui poggiare i piedi." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Non battere Nigel a biliardo.\n" "Sei stato avvisato :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Se mangi troppe uova di cioccolato\n" "ti può venire la nausea :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Ingresso del tunnel in discesa" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Per accedere ai tunnel di sopra\n" "trova il punto di teletrasporto." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Un percorso porta alla ricchezza.\n" "Un percorso porta alla battaglia." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Solo quelli che possono fare un doppio salto\n" "possono arrivare alle cose belle!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Trova la cassa per far comparire\n" "i blocchi per arrampicarti." #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Distruggendo queste casse si libereranno\n" "anche alcuni nemici :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Entra in casa con prudenza....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Non si possono rompere\n" "le casse d'argento sott'acqua..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Cassa di controllo del livello\n" "dell'acqua di sopra." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Schiacciare le casse può essere un bene o un male..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Ma devi trovare un modo per arrivare lassù..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "ti amerò sempre e per sempre :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Vai in cima ai tetti!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "I cieli lassù ricompenseranno\n" "quelli che schiacciano..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Salta più a destra\n" "che puoi..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Non hai fatto il salto, vero? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Ben fatto!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Usa le tue mosse speciali per salire in aria!\n" "Per Jazz, premi Giù e poi Salto.\n" "Per Spaz, premi due volte Salto!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Trova la cassa e le gemme saranno tue!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Le molle non vanno quando sono gelate..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "È più remunerativo raccogliere 20 monete..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Trova la cassa per fare piazza pulita dei blocchi...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "ATTENTO! Gli stormi di corvi possono\n" "essere molto pericolosi." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Per rimuovere i blocchi\n" "cerca l'edificio più alto." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Scegli un coperchio e fila via!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Ciao GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Vai su www.project2.com\n" "usa la\n" "password: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Spara a questi blocchi!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Mentre sei in aria, premi\n" "Giù per una schiacciata col didietro." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Per passare questa zona\n" "rompi la cassa segreta in metallo." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Hai bisogno di venti monete\n" "per passare attraverso questo tunnel di teletrasporto!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Le monete ti fanno entrare\n" "nei tunnel che appariranno più in là." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Colpisci il punto giusto e\n" "troverai una sorpresa!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Alcune casse contengono\n" "bombe o nemici!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Hai bisogno di venti monete\n" "per passare attraverso il tunnel di teletrasporto!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Un lanciafiamme è utile\n" "contro gli insetti." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Gli anelli di fumo ti\n" "stordiranno un po'!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Stai attento alla strega! Può\n" "farti diventare un ranocchio." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Se sei diventato un ranocchio\n" "Eva Earlong può aiutarti!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Ce l'hai fatta! Questa è la fine\n" "della versione Shareware.\n" "Ora controlla le informazioni per\n" "fare l'ordine per averne A N C O R A!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Occhio alle punte di sotto!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Con una schiacciata del didietro\n" "puoi passare attraverso alcuni\n" "punti deboli del percorso!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Alcuni blocchi possono\n" "essere distrutti solo\n" "con certe armi." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Benvenuto a Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Saluti di stagione da\n" "Epic MegaGames\n" "Orange Games e\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Puoi restare in piedi in cima\n" "ad alcuni alberi!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Con una schiacciata del didietro\n" "puoi passare attraverso alcuni\n" "punti deboli del percorso!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Prendere a pugni i blocchi sopra\n" "di te può essere gratificante." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Cassa d'Ingresso del Sentiero delle Gemme." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Ingresso del Sentiero delle Gemme.\n" "Arrampicati sulle cime degli alberi per trovare\n" "e schiacciare la Cassa d'Ingresso." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Ciao, Piggy!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "hai cambiato la mia vita in molti modi.\n" "Dedico questo progetto a te.\n" "Ti amo così tanto." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Un milione di cose che vorrei dirti\n" "Ma le mie parole portano sempre allo stesso finale\n" "Ti amo, ti amo." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Prego, utilizza il TNT con saggezza." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Non perdere la presa!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Benvenuti a Burrowsville\n" "Si prega di guidare con prudenza." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Quel ponte non sembra\n" "essere molto sicuro..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert e Craig:\n" "Le molle REGNANO! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "In partenza da Burrowsville\n" "Vi invitiamo a tornare a trovarci." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Password: xmasbunny\n" "Prego visita\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Livello \"{}\" inizializzato" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Per ulteriori informazioni, visitare il sito Web ufficiale:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "I trucchi non sono consentiti nel contesto attuale" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Il gioco inizierà a breve!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] è stato arrostito da \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] è stato arrostito dall'ambiente" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] si è disconnesso" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] è connesso" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "Il vincitore è {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Comando sconosciuto" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Reimplementazione del gioco \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] rilasciato " "nel 1998. Supporta varie versioni del gioco (Shareware Demo, Holiday Hare " "'98, The Secret Files e Christmas Chronicles). Inoltre, supporta " "parzialmente alcune funzioni dell'estensione JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Sviluppatori" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Collaboratori" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Traduttori" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Questo progetto utilizza il motore di gioco \f[c:#9e7056]nCine\f[/c] " "modificato e le seguenti librerie:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Storia" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Gioca la Demo Shareware" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importa Episodi" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Continua" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Gioca online" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Punteggi record" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Impostazioni" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Informazioni" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Esci" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Per ulteriori informazioni, visitare {} e  Discord!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "L'accesso all'archivio esterno è stato concesso!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Riavviare il gioco per leggere correttamente i file di " "\f[c:#9e7056]Jazz Jackrabbit 2\f[c:#337233]." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Questo gioco richiede i file originali di \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Assicurati che i file di Jazz Jackrabbit 2 siano presenti nel seguente " "percorso:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Permetti l'accesso a un archivio esterno" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Riconfigura i controlli per il Giocatore {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Riconfigura i Controlli" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Controlli touch" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Cambia corsa continua" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Nomi dei bottoni del gamepad" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibrazione gamepad" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Supporto PlayStation™ esteso" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Pulsante Indietro nativo" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnostica immissione" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Reimposta valori predefiniti" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Controlli" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Forte" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Debole" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Disattivata" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Attivata" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Personaggio" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Modalità di gioco" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Crea server" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Battaglia" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Battaglia di gruppo" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Gara" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Gara a squadre" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Caccia al tesoro" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Caccia al tesoro a squadre" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Acchiappa la bandiera" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Cooperazione" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Impossibile creare il server!\f[/c]\n" "\n" "\n" "La playlist non è configurata correttamente.\n" "Prego, controlla la configurazione server e riprova." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Impossibile creare il server!\f[/c]\n" "\n" "\n" "Prego, verifica che nessun altro server\n" "sia in esecuzione su quella porta e riprova." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Gioco Personalizzato" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nessun livello personalizzato trovato!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Crea server da playlist" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Crea server privato" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Crea server pubblico" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nessun episodio trovato!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Ricomincia Episodio" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Devi prima completare \"{}\"!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episodio bloccato!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Compatibilità" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Voglio giocare al gioco come era una volta." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Riveduto" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Voglio giocare ma con qualcosa di nuovo." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "" "Benvenuti nella reimplementazione di \f[c:#9e7056]Jazz Jackrabbit 2\f[/c]!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Giocabilità" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Miglioramenti" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Puoi scegliere il tuo stile di gioco preferito.\n" "Potrai cambiare questa opzione in qualsiasi momento in \f[c:#707070]{}\f[/c] " "> \f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "Per altre informazioni, visita {} e  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Giocabilità rivista" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "HUD rivisto" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Menu principale rivisto" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Arrampicati sulle sporgenze" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Ruota delle Armi" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Abilita i miglioramenti aggiunti da questo remake." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Con Conteggio Munizioni" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Lingua" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Scriptaggio" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Cambia con nuova arma" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Permetti Trucchi" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Sovrascrivi Completamento Episodio" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Sfoglia cartella \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Rigenera la Cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Sì" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "No" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Solo senza Trucchi" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Solo in caso di Punteggi Alti" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Sempre" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Modalità ridimensionamento" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Risoluzione" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Schermo pieno" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antialiasing" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Retinatura di sfondo" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Qualità dell'acqua" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Mostra la scia del giocatore" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Preferenze schermo condiviso" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Visuale più ampia (multi)" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Mantenere il rapporto d'aspetto nei filmati" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Viewport non allineata" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Metriche delle prestazioni" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafica" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Bassa" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Alta" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Verticale" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Orizzontale" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Abilitata \f[c:#d0705d](Sperimentale)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Breve" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Dettagliato" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Punteggi record per \f[c:#d0705d]Gioco base\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Punteggi record per \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Seleziona i file del tuo gioco originale per sbloccare episodi aggiuntivi" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Processamento file {}..." msgstr[1] "Processamento dei file {}..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "In attesa dei file..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Nessun file selezionato!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Nessuno nuovo episodio è stato importato!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Nessun gamepad rilevato!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Seleziona modalità di gioco" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Suoni" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Profilo utente" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Riprendi" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Osserva" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Salva e esci" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Disconnettiti e Esci" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Connettiti al server" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Elaborazione dei file nella directory \f[c:#9e7056]\"Source\"\f[/c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Livelli e episodi nuovi saranno presto disponibili." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Sinistra" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Destra" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Su" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Giù" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Schiacciata col didietro" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Fuoco" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Salta" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Corri" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Cambia arma" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Indietro" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Attiva/disattiva console" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Arma {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Premi un tasto o pulsante per assegnare" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "rimuovi tasto assegnato" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Pixel-perfetto" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "Linee di scansione (Scanline) CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "Maschera d'ombra (Shadow Mask) CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "Griglia di apertura CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monocromatico" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Seleziona la modalità di ridimensionamento" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Nessun server trovato, ma sto ancora cercando!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Premi \f[c:#d0705d]Fuoco\f[/c] per continuare" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Volume principale" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Volume SFX" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Volume musica" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Numero di giocatori locali" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Difficoltà" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Iniza Partita" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Facile" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Media" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Difficile" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "" "È possibile regolare la posizione delle zone touch mediante trascinamento." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integrazione Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Nome giocatore" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Player ID univoco" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Punti: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Il gioco comincerà tra {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "In attesa di {} altro giocatore" msgstr[1] "In attesa di {} altri giocatori" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Trova l'uscita!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Impossibile caricare il livello specificato!\f[/c]\n" "\n" "\n" "Assicurarsi che tutti i file necessari\n" "siano accessibili e riprovare." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Impossibile continuare!\f[/c]\n" "\n" "\n" "Assicurarsi che tutti i file necessari\n" "siano accessibili e riprovare." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Server senza nome" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Parametro non valido specificato." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "La tua versione del client non è compatibile con il server." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Autenticazione fallita.\n" "Contattare gli amministratori del server per ulteriori informazioni." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Password specificata non valida." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Nome del giocatore non valido.\n" "Controlla il tuo profilo e riprova." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Questo client non è presente nella whitelist del server.\n" "Contattare gli amministratori del server per ulteriori informazioni." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Il server richiede un provider di autenticazione di terze parti.\n" "Contattare gli amministratori del server per ulteriori informazioni." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Il server è pieno.\n" "Prego riprova dopo." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Il server non è in uno stato idoneo a processare la tua richiesta.\n" "Prego, riprova tra alcuni secondi." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Il server è in chiusura.\n" "Prego, riprova più tardi." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Il server è in chiusura per manutenzione.\n" "Prego, riprova più tardi." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Il server è in chiusura per manutenzione.\n" "Prego, riprova più tardi." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Il server è in chiusura per manutenzione.\n" "Prego, riprova più tardi." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]La connessione è stata persa!\f[/c]\n" "\n" "\n" "Prego, riprova e se il problema persiste,\n" "controlla la tua connessione di rete." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Il server is non risponde alle richieste di connessione." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Sei stato \f[c:#907050]buttato fuori\f[/c] dal server.\n" "Contatta gli amministratori per maggiori informazioni." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Sei stato \f[c:#725040]bannato\f[/c] dal server.\n" "Contatta gli amministratori per maggiori informazioni." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Rilevato un imbroglio." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Il tuo client non contiene le risorse necessarie.\n" "Scaricare i file necessari e riprovare." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]La connessione è stata chiusa!\f[/c]\n" "\n" "\n" "Il server ti ha disconnesso causa inattività." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Impossibile connettersi al server!\f[/c]\n" "\n" "\n" "Il client non contiene il livello “{}”.\n" "Scaricare i file necessari e riprovare." #~ msgid "Unknown" #~ msgstr "Sconosciuto" #~ msgid "Play Custom Game" #~ msgstr "Partita personalizzata" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Connessione con {}:{}..." #~ msgid "Create Custom Server" #~ msgstr "Crea server personalizzato" #~ msgid "Connect to Server" #~ msgstr "Collegati al server" #~ msgid "or" #~ msgstr "o" #~ msgid "Creating server..." #~ msgstr "Creazione server..." #~ msgid "Error" #~ msgstr "Errore" deathkiller-jazz2-native-2a7ccef/Content/Translations/nb.mo000066400000000000000000001043371512772601700241470ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@SSUUgU|\VIVG#WskW_W?XXczYYsZZb[[x}\y\xp]8]j"^n^^^___I``~ya[aTbsb4b'bb$b c""cEc LcYcactcyc cccc c cc*c"d3d h Vhahih rh~h6hh hhhi"*i!Mi*oiii i$ii j #j0jEj Tj ajkjj,j?j5k-z>Wz8zz%z, { 8{:Y{Z{ {3|.D|5s|2||I|3D}:x}:}4}$#~.H~7w~!~&~!~2M.] '&F+m<ր'3Q+b:Ɂ-߁9 QG8G҂2+MGy9Ah=B43$O<tȅWY׆1-N<|+/+(T#h$-?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: rtdsx Language: nb MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Spillet vil begynne straks! Vinneren er {} [c:#337233]Start spillet på nytt for å lese [c:#9e7056]Jazz Jackrabbit 2 [c:#337233]-filene riktig. [c:#704a4a]Kan ikke koble til serveren! [/c] Autentisering mislyktes. Kontakt serveradministratorene for mer informasjon. [c:#704a4a]Kan ikke koble til serveren! [/c] Ugyldig parameter angitt. [c:#704a4a]Kan ikke koble til serveren! [/c] Ugyldig passord angitt. [c:#704a4a]Kan ikke koble til serveren! [/c] Ugyldig spillernavn angitt. Kontroller profilen din og prøv igjen. [c:#704a4a]Kan ikke koble til serveren! [/c] Serveren er full. Vennligst prøv igjen senere. [c:#704a4a]Kan ikke koble til serveren![ /c] Serveren er ikke i en tilstand der den kan behandle forespørselen din. Vennligst prøv igjen om noen sekunder. [c:#704a4a]Kan ikke koble til serveren! [/c] Serveren krever tredjeparts autentiseringsleverandør. Kontakt serveradministratorene for mer informasjon. [c:#704a4a]Kan ikke koble til serveren! [/c] Serveren svarer ikke på tilkoblingsforespørselen. [c:#704a4a]Kan ikke koble til serveren! [/c] Denne klienten er ikke på serverens hviteliste. Kontakt serveradministratorene for mer informasjon. [c:#704a4a]Kan ikke koble til serveren! [/c] Klienten din inneholder ikke nivå "{}". Last ned de nødvendige filene og prøv igjen. [c:#704a4a]Kan ikke koble til serveren! [/c] Klienten din inneholder ikke nødvendige ressurser. Last ned de nødvendige filene og prøv igjen. [c:#704a4a]Kan ikke koble til serveren! [/c] Din klientversjon er ikke kompatibel med serveren. [c:#704a4a]Kan ikke opprette serveren! [/c] Spillelisten er ikke riktig konfigurert. Kontroller serverkonfigurasjonen og prøv på nytt. [c:#704a4a]Kan ikke opprette serveren! [/c] Kontroller at ingen andre servere kjører på den porten, og prøv igjen. [c:#704a4a]Kunne ikke laste angitt nivå! [/c] Sørg for at alle nødvendige filer er tilgjengelige og prøv på nytt. [c:#704a4a]Kan ikke gjenoppta lagret spill! [/c] Sørg for at alle nødvendige filer er tilgjengelige og prøv igjen. [c:#704a4a]Forbindelsen er brutt! [/c] Juks oppdaget. [c:#704a4a]Tilkoblingen er lukket! [/c] Serveren stenger for vedlikehold. Vennligst prøv igjen senere. [c:#704a4a]Tilkoblingen er lukket! [/c] Serveren stenger for omkonfigurering. Vennligst prøv igjen senere. [c:#704a4a]Tilkoblingen er lukket! [/c] Serveren stenger for oppdatering. Vennligst sjekk klientversjonen din og prøv igjen om et minutt. [c:#704a4a]Tilkoblingen er lukket! [/c] Serveren stenger ned. Vennligst prøv igjen senere. [c:#704a4a]Forbindelsen er brutt! [/c] Serveren har koblet deg fra på grunn av inaktivitet. [c:#704a4a]Tilkoblingen er lukket! [/c] Du har blitt [c:#725040]utelukket/bannet [/c] fra serveren. Kontakt serveradministratorene for mer informasjon. [c:#704a4a]Tilkoblingen er lukket! [/c] Du har blitt [c:#907050]kastet [/c] ut av serveren. Kontakt serveradministratorene for mer informasjon. [c:#704a4a]Tilkoblingen er mistet! [/c] Vennligst prøv igjen, og hvis problemet vedvarer, sjekk nettverkstilkoblingen din. [c:#704a4a]Dette spillet krever originale [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]-filer! [c:#d0705d]{} [/c] koblet på [c:#d0705d]{} [/c] frakoblet [c:#d0705d]{} [/c] Var stekt av [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] var stekt av miljøOmTilgang til ekstern lagring er gitt!Tillat juksTillat tilgang til ekstern lagringAlltidAntialiasingTilbakeBakgrunnsditheringKampBla gjennom "Source"-mappenRumpestompCRT-åpningsgitterCRT-skannelinjerCRT-skyggefilterFang flaggetBytt våpenKarakterJuks er ikke tillat i nåværende kontekstKoble til serverFortsettBidragsytereKontrollerSamarbeidOpprett privat serverOpprett offentlig serverOpprett serverOpprett server fra spillelisteDetaljertUtviklereVanskelighetsgradSkrudd avKoble fra og avsluttDiscord-integrasjonNedLettSkrudd påAktivert [c:#d0705d](Eksperimentell) [/c]Aktivert med ammunisjonstellingForbedringerEpisoden er låst!Extedned PlayStation™-støtteFinn utgang!SkytFor mer informasjon, besøk den offisielle nettsiden:For mer informasjon, besøk {} og  Discord!FullskjermSpillmodusSpillet starter om {}Gamepad-knappetiketterGamepad-vibreringSpillopplevelseGrafikkVanskeligHøyKun høyere poengsumHøyeste poengsummerHighscores for [c:#d0705d]Basisspill [/c]Høye poengsummer for [c:#d0705d]{} [/c]HorisontalJeg vil spille spillet slik det var før.I want to play the game with something new.Importer episoderInndatadiagnostikkHoppBehold bildeforholdet i filmsekvenserSpråkKantklatringVenstreKlassiskNivå "{}" initialisertLavSørg for at Jazz Jackrabbit 2-filene finnes i følgende bane:HovedvolumMiddelsMonokromMusikkvolumInnebygd tilbake-knappNye nivåer og episoder vil snart være tilgjengelige.NeiKun uten juksIngen tilpasset nivå funnet!Ingen episoder funnet!Ingen filer ble valgt!Ingen spillkontroller er oppdaget!Ingen nye episoder ble importert!Ingen servere funnet, men søker fortsatt!Ingen / PikselperfektAntall lokale spillereInstillingerOverskriv ferdigstillelse av episodeYtelsesmålingerSpill tilpassede nivåerSpill OnlineSpill Shareware DemoSpill historieSpiller NavnPoeng: {}Foretrekk å zoome utForetrukket delt skjermTrykk [c:#d0705d]Skyt [/c] for å fortsetteTrykk på en hvilken som helst tast eller knapp for å tilordneBehandler filer i [c:#9e7056]"Source" [/c]-mappen…Behandler {}-fil...Behandling av {} filer...AvsluttRaceRazer Chroma™FornyetFornyet spillopplevelseFornyet HUDFornyet hovedmenyOppdater cacheReimplementering av spillet [c:#9e7056]Jazz Jackrabbit 2 [/c] utgitt i 1998. Støtter ulike versjoner av spillet (Shareware Demo, Holiday Hare '98, The Secret Files og Christmas Chronicles). Støtter også delvis noen funksjoner i JJ2+ utvidelsen.Omkonfigurer kontrollerOmkonfigurer kontroller for spiller {}OmskaleringsmodusTilbakestill til standardOppløsningStart episode på nyttFortsettHøyreLøpEffektvolumLagre og avsluttSkriptingVelg spillmodusVelg skaleringsmodusVelg filer fra det originale spillet for å låse opp flere episoderKortVis spillersporLyderSe påStartSterkBytt til nytt våpenLagkampLagkonkurranseLagskattejaktDette prosjektet bruker modifisert [c:#9e7056]nCine [/c] spillmotor og følgende biblioteker:Skru av/på konsollSlå av/på LøpBerøringskontrollerOversettereSkattejaktUjustert visningsvinduUnik Spiller IDUkjent kommandoNavnløs serverOppBrukerprofilVertikalVenter på filer...Venter for {} mer spillerVenter for {} mer spillereVannkvalitetSvakVåpenhjulVåpen {}Velkommen til [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementering!JaDu kan justere plasseringen av berøringssonene ved å dra og slippe.Du kan velge din foretrukne spillstil. Dette alternativet kan endres når som helst under [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For mer informasjon, besøk {} og  Discord!Du kan aktivere forbedringer som ble lagt til i denne nyutgivelsen.Du må fullføre "{}" først! Drager lever in Burbank. Finn gopheren. Mark bruker truser. Hoo Hah! Nick loves shiny, Always has! Spaz spiste dopefisken. Pass deg for motorsag schmalsz. Ikke gi Mark en burrito. Send Nigel et grønt kort. Send Tim nye sokker. Se opp for fallende fiender. Craig er fortsatt en idiot! Hemmelig Level Tid!!! Rumpestomp en sølvkasse For å rydde veien din Kasser kan også få plattformer til å vises... Leh er en Camper Smelt fjæren... En flammekaster fungerer godt mot insekter. Fallende steinblokker kan gi deg hodepine. God natt, Bubba! Lenge leve isnivået. Hva i all verden? Aaaah! Nei! Dette er IKKE over! Samle mynter for å aktivere bonus-warp-enheter. Ingenting å se her. Polene snurrer deg rundt slik at du kan gå enda raskere. Hemmelig skattkammer. Du har funnet et hemmelig område. Buttstomp på metallboksen for å åpne nøkkelblokkene! Ost er grønn på tirsdag. Craig er kongen av idioter. Bra jobbet! Nå går du og henter Devan Shell! Trykk ned og hopp under disse blokkene for å knuse dem! For å slå dronningen skyt henne ned fra kanten. For å sparke gjennom disse blokkene, trykk ned og hopp! Stomp med rumpa for å gå ut. Denne fjæren er frossen. Skjold gir deg ubegrenset spesiell ammunisjon i en kort periode. Stoppeklokker vil forlenge levetiden til et skjold. Superduper hemmelig. Denne schwartzenguarden er ferdig! Ack! Jeg stikker! Du kan ikke beseire meg, Jazz! Gjør deg klar til å møte superboten min! Disse blokkene er hastighetsblokker. Kjør inn i dem med full fart! Etter å ha hoppet, trykk på hopp igjen for å utføre et spesialtrekk. Vær forsiktig med skarpe gjenstander. Det gjør vondt. Blå edelstener teller som ti edelstener. Gulrøtter gir deg helse. Kontrollpunkter lagrer din plassering hvis du mister et liv. Samle mynter for å låse opp bonusrom. Samle edelstener for å få et ekstra liv. Samle godbiter for poeng og overraskelser. Bra jobbet. Husk å se etter hemmeligheter. Grønne edelstener teller som fem edelstener. Nå er du klar til å spille. Lykke til og ha det gøy. Røde edelstener teller som én edelsten. Jazz 2 er full av hemmeligheter. Sjekk veggene. Noen vegger kan skytes. Velkommen til Jazz Jackrabbit 2. Dette er et treningsnivå. Når du er i luften, trykk ned for å stampe med rumpa. Buttstomp kumlokket! Røykringer vil gjøre deg svimmel. Ikke slå Nigel i biljard. Du er advart. :) Finn kassen for å rydde veien. Ingen belønning til de som er for raske på avtrekkeren. Bare Spaz kan komme seg til rommet oppe til venstre. Han trenger kanskje noe å stå på. Dagens værmelding: Sterk vind! Velkommen til Jazz Jackrabbit 2: The Secret Files! Du kan ikke Rompestumpe så gå opp og rundt!Å spise for mange sjokoladeegg kan gjøre deg syk :p En vei fører til rikdom. En vei fører til kamp. Skrånende tunnelinnkjørsel For å få tilgang til tunnelene ovenfor, må du finne tilgangsportalen. Finn kassen for å få klatreblokkene til å vises Bare de som kan dobbel-hoppe kan komme seg til godbitene! Å Stompe på denne kassen frigjør også noen fiender :) Men du trenger en måte å komme deg opp dit på... Gå inn i huset med forsiktighet... Sølvkasser kan ikke ødelegges under vann... Å trampe på kasser kan være både bra og dårlig... Vannstandskontrollkasse ovenfor. Michelle, Jeg vil alltid elske deg :) Du klarte ikke å hoppe, hva? :) Hopp så langt til høyre som du overhodet kan... Opp på taket! Himmelen over vil belønne dem som stomper... Bruk spesialbevegelsene dine for å komme deg opp i luften! For Jazz, trykk på Crouch og Jump. For Spaz, trykk på Jump to ganger! Bra jobbet! Å samle 20 mynter er mer lønnsomt... Finn kassen, så er edelstenene dine! Fjærer fungerer ikke når de er frosne... VÆR FORSIKTIG! Flokker av ravner kan være svært farlige. Velg et cover og stamp i vei! Finn kassen for å fjerne blokkene.... Gå til www.project2.com bruk passord: BUNNYLOVER Hei GeoBunny :) Fjern blokkene se mot det høyeste bygget. Myntene gir deg tilgang til warps som dukker opp senere. Skyt disse blokkene! Noen kasser inneholder bomber eller skurker! Stomp på riktig sted, så kan du finne en overraskelse! For å komme forbi dette området, må du stompe på den hemmelige metallkassen. Når du er i luften, trykk ned for å stompe med rumpa. Du trenger tjue mynter for å passere gjennom denne hemmelige warpen! En flammekaster fungerer godt mot ekle insekter. Røykringer vil gjøre deg veldig svimmel! Du trenger tjue mynter for å passere gjennom denne hemmelige warpen! Pass deg for heksen! Hun kan forvandle deg til en frosk. Hvis du blir forvandlet til en frosk kan Eva Earlong hjelpe deg! Du klarte det! Dette er slutten på shareware-versjonen. Sjekk nå bestillingsinformasjonen for M E R!å fjerne tilordning God jul fra Epic MegaGames Orange Games og Project 2 Interactive! Noen blokker kan bare knuses med et bestemt våpen. Pass på piggene nedenfor! Velkommen til Christmas Chronicles! Du kan Rumpestompe gjennom noen av svakhetene i gangveiene! Hei, Piggy! - Poopy Kassi Nicole: Det er så mange ting jeg ønsker å si til deg, men ordene mine ender alltid på samme måte: Jeg elsker deg, jeg elsker deg. Michelle: Du har forandret livet mitt på så mange måter. Jeg dedikerer dette prosjektet til deg. Jeg elsker deg så høyt. Gem Trail-inngangen. Klatre opp i tretoppene for å finne og tråkke på inngangskassen. Gem Trail-innføringskasse. Å slå blokkene over deg kan være givende. Du kan Rumpestompe gjennom noen av svakhetene i gangveiene! Du kan stå på toppen av noen av trærne! Ikke mist grepet! Forlater nå Burrowsville. Kom gjerne tilbake. Passord: xmasbunny Besøk www.project2.com Bruk TNT med omhu. Robert og Craig: Fjærer RULER! :) Den broen ser ikke så sikker ut... Velkommen til Burrowsville. Kjør forsiktig.deathkiller-jazz2-native-2a7ccef/Content/Translations/nb.po000066400000000000000000002005271512772601700241500ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:58+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: rtdsx\n" "Language: nb\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz spiste dopefisken." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Finn gopheren." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Drager lever in Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark bruker truser.\n" "Hoo Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick loves shiny,\n" "Always has!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Send Tim nye sokker." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Send Nigel et grønt kort." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Pass deg for motorsag schmalsz." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Ikke gi Mark en burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Se opp for fallende fiender." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig er fortsatt en idiot!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Hemmelig Level Tid!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Rumpestomp en sølvkasse\n" "For å rydde veien din" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Kasser kan også få plattformer til å vises..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Smelt fjæren..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh er en Camper" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Fallende steinblokker kan \n" "gi deg hodepine." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "En flammekaster fungerer\n" "godt mot insekter." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Lenge leve isnivået." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "God natt, Bubba!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Hva i all verden? Aaaah! Nei! \n" "Dette er IKKE over!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Polene snurrer deg rundt slik at\n" " du kan gå enda raskere." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Du har funnet et hemmelig område." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Hemmelig skattkammer." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Ingenting å se her." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Samle mynter for å aktivere \n" "bonus-warp-enheter." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "Ost er grønn på tirsdag." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig er kongen av idioter." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "For å slå dronningen \n" "skyt henne ned fra kanten." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Bra jobbet! \n" "Nå går du og henter Devan Shell!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "For å sparke gjennom disse\n" "blokkene, trykk ned og hopp!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Trykk ned og hopp under \n" "disse blokkene for å knuse dem!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Buttstomp på metallboksen \n" "for å åpne nøkkelblokkene!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Stomp med rumpa for å gå ut." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Denne fjæren er frossen." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Superduper hemmelig." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Skjold gir deg ubegrenset \n" "spesiell ammunisjon i en kort periode." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Stoppeklokker vil forlenge\n" "levetiden til et skjold." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Denne schwartzenguarden er ferdig!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Du kan ikke beseire meg, Jazz!\n" "Gjør deg klar til å møte superboten min!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Ack! Jeg stikker!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Disse blokkene er hastighetsblokker.\n" "Kjør inn i dem med full fart!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Velkommen til Jazz Jackrabbit 2. \n" " Dette er et treningsnivå." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Samle godbiter for\n" "poeng og overraskelser." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Etter å ha hoppet, trykk på hopp\n" "igjen for å utføre et spesialtrekk." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Noen vegger kan skytes." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Når du er i luften, trykk ned\n" "for å stampe med rumpa." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Jazz 2 er full av hemmeligheter. \n" " Sjekk veggene." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Bra jobbet. Husk å\n" "se etter hemmeligheter." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Samle edelstener for å få \n" "et ekstra liv." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Røde edelstener teller\n" "som én edelsten." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Grønne edelstener teller\n" "som fem edelstener." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Blå edelstener teller\n" "som ti edelstener." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Gulrøtter gir deg helse." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Kontrollpunkter lagrer din\n" "plassering hvis du mister et liv." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Samle mynter for å\n" "låse opp bonusrom." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Vær forsiktig med skarpe gjenstander.\n" "Det gjør vondt." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Nå er du klar til å spille.\n" " Lykke til og ha det gøy." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Buttstomp kumlokket!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Røykringer vil \n" "gjøre deg svimmel." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Du kan ikke Rompestumpe\n" "så gå opp og rundt!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Ingen belønning til de\n" "som er for raske på avtrekkeren." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Dagens værmelding: Sterk vind!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Finn kassen\n" "for å rydde veien." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Velkommen til\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Bare Spaz kan komme seg til\n" "rommet oppe til venstre.\n" "Han trenger kanskje noe\n" "å stå på." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Ikke slå Nigel i biljard.\n" "Du er advart. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Å spise for mange sjokoladeegg\n" "kan gjøre deg syk :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Skrånende tunnelinnkjørsel" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "For å få tilgang til tunnelene ovenfor,\n" "må du finne tilgangsportalen." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "En vei fører til rikdom.\n" "En vei fører til kamp." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Bare de som kan dobbel-hoppe\n" "kan komme seg til godbitene!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Finn kassen for å få\n" "klatreblokkene til å vises" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Å Stompe på denne kassen frigjør også\n" "noen fiender :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Gå inn i huset med forsiktighet..." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Sølvkasser kan ikke ødelegges under vann..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Vannstandskontrollkasse ovenfor." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Å trampe på kasser kan være både bra og dårlig..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Men du trenger en måte å komme deg opp dit på..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Jeg vil alltid elske deg :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Opp på taket!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Himmelen over vil belønne\n" "dem som stomper..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Hopp så langt til høyre\n" "som du overhodet kan..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Du klarte ikke å hoppe, hva? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Bra jobbet!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Bruk spesialbevegelsene dine for å komme deg opp i luften!\n" "For Jazz, trykk på Crouch og Jump.\n" "For Spaz, trykk på Jump to ganger!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Finn kassen, så er edelstenene dine!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Fjærer fungerer ikke når de er frosne..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Å samle 20 mynter er mer lønnsomt..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Finn kassen for å fjerne blokkene...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "VÆR FORSIKTIG! Flokker av ravner kan\n" "være svært farlige." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Fjern blokkene\n" "se mot det høyeste bygget." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Velg et cover og stamp i vei!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Hei GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Gå til www.project2.com\n" "bruk\n" "passord: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Skyt disse blokkene!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Når du er i luften, trykk ned\n" "for å stompe med rumpa." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "For å komme forbi dette området, må du stompe på\n" "den hemmelige metallkassen." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Du trenger tjue mynter for å passere \n" "gjennom denne hemmelige warpen!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Myntene gir deg tilgang til \n" "warps som dukker opp senere." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Stomp på riktig sted, så\n" "kan du finne en overraskelse!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Noen kasser inneholder\n" "bomber eller skurker!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Du trenger tjue mynter for å passere \n" "gjennom denne hemmelige warpen!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "En flammekaster fungerer godt \n" "mot ekle insekter." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Røykringer vil gjøre\n" "deg veldig svimmel!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Pass deg for heksen! Hun kan\n" "forvandle deg til en frosk." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Hvis du blir forvandlet til en frosk\n" "kan Eva Earlong hjelpe deg!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Du klarte det! Dette er slutten\n" " på shareware-versjonen.\n" "Sjekk nå bestillingsinformasjonen\n" "for M E R!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Pass på piggene nedenfor!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Du kan Rumpestompe gjennom\n" "noen av svakhetene\n" "i gangveiene!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Noen blokker kan bare\n" "knuses med et\n" "bestemt våpen." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Velkommen til Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "God jul fra\n" "Epic MegaGames\n" "Orange Games og\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Du kan stå på toppen\n" "av noen av trærne!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Du kan Rumpestompe gjennom\n" "noen av svakhetene\n" "i gangveiene!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Å slå blokkene over deg\n" "kan være givende." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Gem Trail-innføringskasse." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Gem Trail-inngangen.\n" "Klatre opp i tretoppene for å finne\n" "og tråkke på inngangskassen." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Hei, Piggy!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Du har forandret livet mitt på så mange måter.\n" "Jeg dedikerer dette prosjektet til deg.\n" "Jeg elsker deg så høyt." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Det er så mange ting jeg ønsker å si til deg,\n" "men ordene mine ender alltid på samme måte:\n" "Jeg elsker deg, jeg elsker deg." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Bruk TNT med omhu." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Ikke mist grepet!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Velkommen til Burrowsville.\n" "Kjør forsiktig." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Den broen ser ikke\n" "så sikker ut..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert og Craig:\n" "Fjærer RULER! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Forlater nå Burrowsville.\n" "Kom gjerne tilbake." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Passord: xmasbunny\n" "Besøk\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Nivå \"{}\" initialisert" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "For mer informasjon, besøk den offisielle nettsiden:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Juks er ikke tillat i nåværende kontekst" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Spillet vil begynne straks!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] Var stekt av \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] var stekt av miljø" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] frakoblet" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] koblet på" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "Vinneren er {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Ukjent kommando" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Reimplementering av spillet \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] utgitt i " "1998. Støtter ulike versjoner av spillet (Shareware Demo, Holiday Hare '98, " "The Secret Files og Christmas Chronicles). Støtter også delvis noen " "funksjoner i JJ2+ utvidelsen." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Utviklere" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Bidragsytere" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Oversettere" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Dette prosjektet bruker modifisert \f[c:#9e7056]nCine\f[/c] spillmotor og " "følgende biblioteker:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Spill historie" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Spill Shareware Demo" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importer episoder" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Fortsett" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Spill Online" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Høyeste poengsummer" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Instillinger" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Om" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Avslutt" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "For mer informasjon, besøk {} og  Discord!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Tilgang til ekstern lagring er gitt!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Start spillet på nytt for å lese \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233]-filene riktig." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Dette spillet krever originale \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a]-filer!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "Sørg for at Jazz Jackrabbit 2-filene finnes i følgende bane:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Tillat tilgang til ekstern lagring" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Omkonfigurer kontroller for spiller {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Omkonfigurer kontroller" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Berøringskontroller" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Slå av/på Løp" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Gamepad-knappetiketter" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Gamepad-vibrering" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Extedned PlayStation™-støtte" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Innebygd tilbake-knapp" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Inndatadiagnostikk" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Tilbakestill til standard" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Kontroller" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Sterk" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Svak" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Skrudd av" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Skrudd på" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Karakter" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Spillmodus" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Opprett server" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Kamp" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Lagkamp" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Race" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Lagkonkurranse" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Skattejakt" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Lagskattejakt" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Fang flagget" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Samarbeid" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Kan ikke opprette serveren!\f[/c]\n" "\n" "\n" "Spillelisten er ikke riktig konfigurert.\n" "Kontroller serverkonfigurasjonen og prøv på nytt." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Kan ikke opprette serveren!\f[/c]\n" "\n" "\n" "Kontroller at ingen andre servere\n" "kjører på den porten, og prøv igjen." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Spill tilpassede nivåer" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Ingen tilpasset nivå funnet!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Opprett server fra spilleliste" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Opprett privat server" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Opprett offentlig server" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Ingen episoder funnet!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Start episode på nytt" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Du må fullføre \"{}\" først!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episoden er låst!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Klassisk" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Jeg vil spille spillet slik det var før." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Fornyet" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "I want to play the game with something new." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Velkommen til \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementering!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Spillopplevelse" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Forbedringer" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Du kan velge din foretrukne spillstil.\n" "Dette alternativet kan endres når som helst under \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For mer informasjon, besøk {} og  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Fornyet spillopplevelse" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Fornyet HUD" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Fornyet hovedmeny" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Kantklatring" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Våpenhjul" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Du kan aktivere forbedringer som ble lagt til i denne nyutgivelsen." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Aktivert med ammunisjonstelling" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Språk" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Skripting" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Bytt til nytt våpen" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Tillat juks" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Overskriv ferdigstillelse av episode" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Bla gjennom \"Source\"-mappen" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Oppdater cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Ja" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Nei" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Kun uten juks" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Kun høyere poengsum" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Alltid" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Omskaleringsmodus" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Oppløsning" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Fullskjerm" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antialiasing" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Bakgrunnsdithering" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Vannkvalitet" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Vis spillerspor" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Foretrukket delt skjerm" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Foretrekk å zoome ut" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Behold bildeforholdet i filmsekvenser" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Ujustert visningsvindu" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Ytelsesmålinger" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafikk" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Lav" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Høy" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertikal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horisontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Aktivert \f[c:#d0705d](Eksperimentell)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Kort" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detaljert" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Highscores for \f[c:#d0705d]Basisspill\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Høye poengsummer for \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "Velg filer fra det originale spillet for å låse opp flere episoder" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Behandler {}-fil..." msgstr[1] "Behandling av {} filer..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Venter på filer..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Ingen filer ble valgt!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Ingen nye episoder ble importert!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Ingen spillkontroller er oppdaget!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Velg spillmodus" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Lyder" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Brukerprofil" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Fortsett" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Se på" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Lagre og avslutt" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Koble fra og avslutt" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Koble til server" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Behandler filer i \f[c:#9e7056]\"Source\"\f[/c]-mappen…" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Nye nivåer og episoder vil snart være tilgjengelige." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Venstre" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Høyre" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Opp" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Ned" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Rumpestomp" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Skyt" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Hopp" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Løp" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Bytt våpen" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Tilbake" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Skru av/på konsoll" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Våpen {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Trykk på en hvilken som helst tast eller knapp for å tilordne" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "å fjerne tilordning" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Ingen / Pikselperfekt" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT-skannelinjer" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT-skyggefilter" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT-åpningsgitter" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monokrom" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Velg skaleringsmodus" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Ingen servere funnet, men søker fortsatt!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Trykk \f[c:#d0705d]Skyt\f[/c] for å fortsette" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Hovedvolum" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Effektvolum" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Musikkvolum" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Antall lokale spillere" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Vanskelighetsgrad" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Start" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Lett" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Middels" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Vanskelig" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Du kan justere plasseringen av berøringssonene ved å dra og slippe." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Discord-integrasjon" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Spiller Navn" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Unik Spiller ID" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Poeng: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Spillet starter om {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "Venter for {} mer spiller" msgstr[1] "Venter for {} mer spillere" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Finn utgang!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Kunne ikke laste angitt nivå!\f[/c]\n" "\n" "\n" "Sørg for at alle nødvendige filer\n" "er tilgjengelige og prøv på nytt." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Kan ikke gjenoppta lagret spill!\f[/c]\n" "\n" "\n" "Sørg for at alle nødvendige filer\n" "er tilgjengelige og prøv igjen." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Navnløs server" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Ugyldig parameter angitt." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Din klientversjon er ikke kompatibel med serveren." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Autentisering mislyktes.\n" "Kontakt serveradministratorene for mer informasjon." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Ugyldig passord angitt." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Ugyldig spillernavn angitt.\n" "Kontroller profilen din og prøv igjen." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Denne klienten er ikke på serverens hviteliste.\n" "Kontakt serveradministratorene for mer informasjon." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Serveren krever tredjeparts autentiseringsleverandør.\n" "Kontakt serveradministratorene for mer informasjon." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Serveren er full.\n" "Vennligst prøv igjen senere." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren![\f/c]\n" "\n" "\n" "Serveren er ikke i en tilstand der den kan behandle forespørselen din.\n" "Vennligst prøv igjen om noen sekunder." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Tilkoblingen er lukket!\f[/c]\n" "\n" "\n" "Serveren stenger ned.\n" "Vennligst prøv igjen senere." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Tilkoblingen er lukket!\f[/c]\n" "\n" "\n" "Serveren stenger for vedlikehold.\n" "Vennligst prøv igjen senere." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Tilkoblingen er lukket!\f[/c]\n" "\n" "\n" "Serveren stenger for omkonfigurering.\n" "Vennligst prøv igjen senere." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]Tilkoblingen er lukket!\f[/c]\n" "\n" "\n" "Serveren stenger for oppdatering.\n" "Vennligst sjekk klientversjonen din og prøv igjen om et minutt." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]Tilkoblingen er mistet!\f[/c]\n" "\n" "\n" "Vennligst prøv igjen, og hvis problemet vedvarer, sjekk " "nettverkstilkoblingen din." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Serveren svarer ikke på tilkoblingsforespørselen." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Tilkoblingen er lukket!\f[/c]\n" "\n" "\n" "Du har blitt \f[c:#907050]kastet\f[/c] ut av serveren.\n" "Kontakt serveradministratorene for mer informasjon." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Tilkoblingen er lukket!\f[/c]\n" "\n" "\n" "Du har blitt \f[c:#725040]utelukket/bannet\f[/c] fra serveren.\n" "Kontakt serveradministratorene for mer informasjon." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]Forbindelsen er brutt!\f[/c]\n" "\n" "\n" "Juks oppdaget." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Klienten din inneholder ikke nødvendige ressurser.\n" "Last ned de nødvendige filene og prøv igjen." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]Forbindelsen er brutt!\f[/c]\n" "\n" "\n" "Serveren har koblet deg fra på grunn av inaktivitet." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Kan ikke koble til serveren!\f[/c]\n" "\n" "\n" "Klienten din inneholder ikke nivå \"{}\".\n" "Last ned de nødvendige filene og prøv igjen." deathkiller-jazz2-native-2a7ccef/Content/Translations/pl.mo000066400000000000000000001100201512772601700241450ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@S'S#V7VgJVVZmWXW!XfXYYebZZ[\f\]]/^^CQ__``d7ana bbhcYcVd%vd?d5de3eMe+eeeeeee eeff*f;f LfYf(aff ff f fffgg *g8g@g Rg^gvggg g-g+g hh#0hThfhGnh2h hhh!i;i OiYiaihioii4i'ii5i,.j[jkjj"jjjj jjj;j3kHk Pk\kpk<kkk1k"l3l"Ql,tl?lllm m7mNm em qm}mm mmm4m*n=3nQqnnnn nnoo4oSo@p Wpxppppppp#ppq qq@4quq}q qqqq'qqq qarrrr rrrss$s6sx1xxHy\y syCyy!y4zMJz8zLz{>{M^{0{{-{ |S:|?|A|E}/V}}]}8~.9~3h~'~(~)~(,@(m7^#--Q</5d"+.6XAr@ł9@@56%8.M<|(!/4)Gfq؅4,"3O66ֆ7 E>_.͇A߇K!Cmo>!/`->6J4 U%K{Nj:?X~+;*:f0Ҏ*5BH(*0ߏ?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Patrxgt Language-Team: Patrxgt & Klodizaur Language: pl MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Gra rozpocznie się za niedługo! {} zwyciężył! [c:#337233]Zresetuj grę, aby prawidłowo załadować pliki [c:#9e7056]Jazz Jackrabbit 2 [c:#337233]. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Uwierzytelnianie nie powiodło się. Skontaktuj się z administratorami serwera, aby uzyskać w tej sprawie więcej informacji. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Podano nieprawidłowy parametr. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Podano nieprawidłowe hasło. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Podano nieprawidłową nazwę gracza. Sprawdź swój profil i spróbuj ponownie. [c:#704a4a]Nie można połączyć z serwerem! [/c] Serwer jest pełny. Spróbuj ponownie później. [c:#704a4a]Nie można połączyć z serwerem! [/c] Serwer nie jest w stanie przetworzyć żądania. Spróbuj ponownie za kilka sekund. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Serwer wymaga zewnętrznego uwierzytelnienia. Skontaktuj się z administratorami serwera, aby uzyskać w tej sprawie więcej informacji. [c:#704a4a]Nie można połączyć z serwerem! [/c] Serwer nie odpowiada na żądanie połączenia. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Ten klient nie znajduje się na białej liście serwera. Skontaktuj się z administratorami serwera, aby uzyskać w tej sprawie więcej informacji. [c:#704a4a]Cannot connect to the server! [/c] Twój klient nie zawiera poziomu "{}". Pobierz wymagane pliki i spróbuj ponownie. [c:#704a4a]Nie można połączyć się z serwerem! [/c] Twój klient nie zawiera wymaganej zawartości. Pobierz wymagane pliki i spróbuj ponownie. [c:#704a4a]Nie można połączyć z serwerem! [/c] Wersja klienta nie jest kompatybilna z serwerem. [c:#704a4a]Nie można utworzyć serwera! [/c] Playlista została niepoprawnie skonfigurowana. Sprawdź konfigurację serwera i spróbuj ponownie. [c:#704a4a]Nie można utworzyć serwera! [/c] Sprawdź, czy na tym porcie nie działa żaden inny serwer i spróbuj ponownie. [c:#704a4a]Nie można załadować tego poziomu! [/c] Upewnij się, że wszystkie potrzebne pliki są na swoim miejscu i spróbuj ponownie. [c:#704a4a]Nie można wznowić zapisanego stanu gry! [/c] Upewnij się, że wszystkie niezbędne pliki są na swoim miejscu i spróbuj ponownie. [c:#704a4a]Połączenie zostało zerwane! [/c] Wykryto oszustwo. [c:#704a4a]Połączenie zostało zerwane! [/c] Serwer jest zamykany z powodu prac technicznych. Spróbuj ponownie później. [c:#704a4a]Połączenie zostało zerwane! [/c] Serwer jest zamykany z powodu ponownej konfiguracji. Spróbuj ponownie później. [c:#704a4a]Połączenie zostało zerwane! [/c] Serwer jest zamykany z powodu aktualizacji. Sprawdź wersję swojego klienta i spróbuj ponownie później. [c:#704a4a]Połączenie zostało zerwane! [/c] Serwer jest zamykany. Spróbuj ponownie później. [c:#704a4a]Połączenie zostało zerwane! [/c] Serwer rozłączył się z tobą z powodu braku aktywności. [c:#704a4a]Połączenie zostało zerwane! [/c] Zostałeś/aś [c:#907050]skazany/a na banicję [/c] z serwera. Skontaktuj się z administratorem serwera po więcej informacji. [c:#704a4a]Połączenie zostało zerwane! [/c] Zostałeś/aś [c:#907050]wykopany/a [/c] z serwera. Skontaktuj się z administratorem serwera po więcej informacji. [c:#704a4a]Połączenie zostało zerwane! [/c] Spróbuj ponownie i sprawdź, czy problem nadal występuje. Sprawdź swoje połączenie sieciowe. [c:#704a4a]Ta gra wymaga oryginalnych plików [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]! [c:#d0705d]{} [/c] dołączył [c:#d0705d]{} [/c] rozłączył się [c:#d0705d]{} [/c] został sprażony przez [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] został sprażony przez otoczenieO grzeDostęp do pamięci zewnętrznej został udzielony!Zezwól na kody oszustwZezwól na dostęp do pamięci zewnętrznejZawszeWygładzanie krawędziWsteczWygładzanie tłaBitwaPrzeglądaj katalog z danymi gryRąbnięcie tyłkiemKratka przysłony CRTLinie skanu CRTMaska cienia CRTZdobądź flagęZmień brońBohaterKody oszustw nie są aktualnie dozwolonePołącz z serweremKontynuujWspółautorzySterowanieWspółpracaUtwórz serwer prywatnyUtwórz serwer publicznyUtwórz serwerUtwórz serwer z playlistySzczegółowyAutorzyPoziom trudnościWyłączonoRozłącz się i wyjdźIntegracja z platformą DiscordDółŁatwyWłączonoWłączono [c:#d0705d](Eksperymentalne) [/c]Włączono + Wyświetlanie ilości amunicjiUlepszeniaTen epizod jest zablokowany!Rozszerzone wsparcie PlayStation™Znajdź wyjście!StrzałPo więcej informacji, odwiedź naszą oficjalną stronę internetową:Po więcej informacji, odwiedź {} i  Discorda!Pełny ekranTryb gryGra rozpocznie się za {}Oznaczenia przycisków kontroleraWibracje kontroleraRozgrywkaGrafikaTrudnyWysokiTylko wyższy wynikTabela wynikówTabela wyników dla [c:#d0705d]gry podstawowej [/c]Tabela wyników dla [c:#d0705d]{} [/c]PoziomeChcę zagrać w grę jak za starych, dobrych czasów.Chcę zagrać w grę z jakimiś nowościami.Importuj epizodDiagnostyka sterowaniaSkokZachowaj proporcje w przerywnikachJęzykWspinanie na krawędzieLewoKlasycznyPoziom "{}" zainicjowanyNiskiUpewnij się, że pliki Jazz Jackrabbit 2 znajdują się w:Głośność ogólnaŚredniMonochromiaGłośność muzykiNatywny przycisk "Wstecz"Nowo dodane poziomy i epizody będą za niedługo dostępne.NieTylko bez cheatówNie znaleziono żadnych poziomów społeczności!Nie znaleziono żadnych epizodów!Nie wybrano żadnych plików!Nie wykryto żadnych kontrolerów!Nie zaimportowano żadnych nowych odcinków!Póki co nie znaleziono żadnych serwerów, ale ciągle szukam!Brak / Idealne pikseleLiczba graczy lokalnychOpcjeNadpisz stan ukończenia epizoduStatystyki wydajnościPoziomy społecznościGraj onlineGraj w demoTryb fabularnyPseudonim graczaPunkty: {}Preferuj oddalenie kameryDzielenie ekranuNaciśnij [c:#d0705d]Strzał [/c], aby kontynuowaćNaciśnij dowolny przycisk, aby przypisaćPrzetwarzanie plików z katalogu [c:#9e7056]"Źródło" [/c]Przetwarzanie {} pliku...Przetwarzanie {} plików...Przetwarzanie {} plików...WyjdźWyścigRazer Chroma™OdnowionyOdnowiona rozgrywkaOdniowiony interfejs w grzeOdnowione menu główneOdśwież pamięć podręcznąReimplementacja gry [c:#9e7056]Jazz Jackrabbit 2 [/c] wydanej w 1998 roku. Wspiera różne wersje gry (Demo, Holiday Hare '98, The Secret Files i Christmas Chronicles) oraz (częściowo) niektóre funkcje fanowskiego rozszerzenia JJ2+.Skonfiguruj sterowanieSkonfiguruj sterowanie gracza {}Tryb skalowaniaPrzywróć domyślneRozdzielczośćZresetuj epizodWznówPrawoBiegGłośność efektów dźwiękowychZapisz i wyjdźSkryptyWybierz tryb gryWybierz tryb skalowaniaWybierz pliki oryginalnej gry, aby odblokować dodatkowe epizodyKrótkiPokazuj ślady graczaDźwiękiObserwujStartMocneZmień na nową broń przy podniesieniuBitwa drużynowaWyścig drużynowyDrużynowe poszukiwanie skarbówTen projekt używa zmodyfikowanego silnika [c:#9e7056]nCine [/c] oraz następujących bibliotek:Włącz/Wyłącz konsolęPrzełączanie bieguSterowanie dotykoweTłumaczePoszukiwanie skarbówZmiennoprzecinkowa precyzjaUnikalne ID graczaNieznana komendaNienazwany serwerGóraProfil użytkownikaPionoweOczekiwanie na pliki...Oczekiwanie na jeszcze {} graczyOczekiwanie na {} graczyOczekiwanie na {} graczyJakość wodySłabeKoło broniBroń {}Witaj w reimplementacji [c:#9e7056]Jazz Jackrabbit 2 [/c]!TakMożesz dostosować położenie miejsc dotykowych poprzez przeciągnięcie i puszczenie.Możesz wybrać preferowany przez siebie styl rozgrywki. To ustawienie może zostać zmienione, kiedy tylko ci się spodoba w [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Po więcej informacji, odwiedź {} oraz serwer  Discord!Możesz włączyć ulepszenia, które zostały dodane do tej rekreacji.Najpierw musisz ukończyć "{}"! Smoki żyją w Burbank. Znajdź susła. Mark nosi kalesony. Hu ha! Nick od zawsze kochał wszystko, co błyszczy! Spaz zjadł dopefisha. Nie rób smalcu piłą łańcuchową. Nie dawajcie Markowi burito. Wyślij Nigelowi zieloną kartę. Wyślij Timowi nowe skarpety. Uważaj na spadających wrogów. Craig to nadal przygłup! Pora na Sekretny Poziom!!! Skocz tyłkiem na srebrną skrzynkę, aby oczyścić sobie drogę Skrzynie mogą także ujawnić platformy... Leh jest kamperem Odmroź sprężynę... Miotacz ognia jest dobry na robaki. Spadające kamienie mogą przyprawić cię o ból głowy. To jeszcze nie koniec! Niech żyje poziom lodowy. Co do cholery? Aaaa! No nie! Ten poziom jest niedokończony! Zbieraj monety. A nuż odblokujesz jakiś bonus. Nie ma tu nic ciekawego. Okręcanie się na słupkach pozwala ci zwiększyć swoją prędkość. Tajny Pokój Skarbów Znalazłeś sekretną okolicę. Skocz tyłkiem na metalową skrzynię, aby otworzyć bloki-klucze! We wtorki ser zielenieje. Craig jest królem przygłupów. Dobra robota! Teraz idź skopać skorupę Devanowi! Wciśnij przycisk ruchu w dół i skoku pod tymi blokami, aby je zniszczyć! Aby pokonać królową musisz zrzucić ją z platformy. Aby przebić się przez te bloki, wciśnij przyciski ruchu w dół i skoku! Stąpnij zadkiem, aby wyjść. Ta sprężyna jest zamrożona. Tarcze dają ci nieskończoną ilość specjalnej amunicji na krótki czas. Stopery mogą przedłużyć działanie tarczy. Super duper sekret. Niech ci ziemia ciężką będzie, bydlaku! Oj! Pora stąd spadać! Nie dasz rady mnie pokonać, Jazz! Przygotuj się na spotkanie z moim superbotem! To bloki prędkości. Wbiegnij w nie najszybciej, jak się da! Po skoku, wciśnij skok jeszcze raz aby wykonać specjalny ruch! Strzeż się ostrych rzeczy. Nadzianie się na nie potrafi zaboleć. Niebieskie klejnoty liczą się za dziesięć. Marchewka daje ci zdrowie. Punkt kontrolny zapisze twój postęp, który zostanie przywrócony, jeśli stracisz życie. Zbieraj monety, aby odblokować bonusowe pomieszczenia. Zbieraj klejnoty, aby dostać ekstra życie. Zbieraj dobra, aby zdobyć punkty i niespodzianki. Dobra robota. Zawsze szukaj sekretów. Zielone klejnoty liczą się za pięć. Jesteś gotowy do gry. Baw się dobrze! Czerwone klejnoty liczą się za jeden. Mnóstwo tu sekretów. Sprawdzaj ściany. Niektóre ściany możesz rozstrzelać. Witaj w Jazz Jackrabbit 2. To jest poziom treningowy. Kiedy jesteś w powietrzu, wciśnij przycisk ruchu w dół, żeby rąbnąć swoim tyłkiem. Skocz tyłkiem na pokrywę włazu! Dymowe pierścienie mogą cię oszołomić! Nie wygrywaj z Nigelem w bilard. Zostałeś ostrzeżony. :) Znajdź skrzynkę aby oczyścić sobie drogę. Nie ma nagrody dla tych, których swędzą paluszki. Tylko Spaz może dostać się do pomieszczenia na górze po lewej. Ale musi mieć na czym stanąć. Prognoza na dziś: Uwaga na mocne wiatry! Witamy w Jazz Jackrabbit 2: The Secret Files! Jeśli nie da się dołem to idź górą i na około!Im więcej czekoladowych jajek zjesz, tym szybciej zapoznasz się z panem próchnicą :p Jedna droga prowadzi do bogactwa. Druga droga prowadzi do wojny. Wjazd do tunelu Aby dostać się do tunelu nad tobą musisz poszukać dostępu. Znajdź skrzynię, aby pojawił się blok do wspinaczki Tylko biegli w podwójnych skokach mogą dobrać się do dóbr! Rozwalenie tej skrzyni uwolni też kilku wrogów. :) Ale musisz znaleźć sposób, by się tam dostać... Uważaj przy wchodzeniu do domu..... Srebrne skrzynki nie dadzą się rozwalić pod wodą... Rozwalanie skrzyń ma swoje plusy i minusy... Skrzynia kontrolująca poziom wody znajduje się nad tobą. Michelle, Zawsze będę cię kochał :) Czyżby skok ci nie wyszedł? :) Skocz w prawo na tyle daleko na ile się da... Dawaj po dachach! Niebo wynagrodzi tych, którzy tupią... Użyj specjalnych ruchów, aby wznieść się w górę! Jazz - przysiad i skok. Spaz - dwa razy skok! Świetnie ci idzie! Za zebranie 20 monet czeka cię większa nagroda... Znajdź skrzynkę, a klejnoty będą twoje! Sprężyny nie działają, kiedy są zamrożone... UWAGA! Stada kruków mogą być bardzo niebezpieczne! Wybierz osłonę i zmykaj! Znajdź skrzynię, pomoże ci pozbyć się bloków... Wejdź na www.project2.com i użyj hasła: BUNNYLOVER Cześć GeoKróliczku :) Aby usunąć bloki, rozejrzyj się za największym budynkiem. Monety dają ci dostęp do tajemnych portali. Rozwal te bloki! Niektóre skrzynki mogą skrywać w sobie bomby i przeciwników! Walnij z zadka we właściwe miejsce, to może znajdziesz niespodziankę! Aby przejść tę część uderz w sekretną, metalową skrzynkę. Kiedy jesteś w powietrzu, wciśnij przycisk ruchu w dół żeby rąbnąć o ziemię swoimi czterema literami. Potrzebujesz 20-stu monet, aby wejść w ten sekretny portal! Miotacz ognia jest dobry na uporczywe robale. Dymowe pierścienie mogą cię oszołomić! Potrzebujesz 20-stu monet, aby wejść w ten sekretny portal! Strzeż się wiedźmy! Może zamienić cię w żabę! Jeśli zostałeś zamieniony w żabę, poproś o pomoc Długouchą Ewę! Udało ci się! To już koniec wersji shareware. Sprawdź informacje o kupnie, aby dostać W I Ę C E J poziomów w swoje puszyste łapki!aby usunąć przypisanie Pozdrowienia świąteczne od Epic MegaGames Orange Games oraz Project 2 Interactive! Niektóre bloki mogą być zniszczone tylko przy pomocy określonej broni. Uważaj na kolce pod tobą! Witamy w Christmas Chronicles! Możesz skoczyć tyłkiem na słabsze części ścieżki! Hej, Świnko! - Poopy Kassi Nicole: Mógłbym powiedzieć o tobie tyle rzeczy. Lecz me słowa zawsze prowadzą do tej samej konkluzji. Kocham cię, tak bardzo cię kocham! Michelle: Zmieniłaś moje życie na wiele różnych sposobów W podzięce dedykuję tobie ten projekt. Kocham cię najbardziej na świecie. Wejście do Szlaku Klejnotów. Wspinaj się na wierzchołki drzew, aby znaleźć i walnąć zadkiem w skrzynię wejściową. Skrzynia prowadząca do Szlaku Klejnotów. Uderzanie bloków nad tobą może zostać ci wynagrodzone. Możesz skoczyć tyłkiem na słabsze części ścieżki! Możesz stanąć na czubkach niektórych drzew! Tylko nie puszczaj! Opuszczasz Norkowice Zapraszamy ponownie. Hasło: xmasbunny Proszę, odwiedź www.project2.com Proszę, używaj TNT z głową, bo inaczej możesz ją stracić. Robert i Craig: Sprężyny RZĄDZĄ! :) Ten most nie wygląda na zbyt stabilny... Witamy w Norkowicach Proszę, jedź ostrożnie.deathkiller-jazz2-native-2a7ccef/Content/Translations/pl.po000066400000000000000000002052221512772601700241610ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:58+0100\n" "PO-Revision-Date: \n" "Last-Translator: Patrxgt\n" "Language-Team: Patrxgt & Klodizaur\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 " "|| n%100>14) ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz zjadł dopefisha." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Znajdź susła." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Smoki żyją w Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark nosi kalesony.\n" "Hu ha!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick od zawsze kochał \n" "wszystko, co błyszczy!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Wyślij Timowi nowe skarpety." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Wyślij Nigelowi zieloną kartę." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Nie rób smalcu piłą łańcuchową." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Nie dawajcie Markowi burito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Uważaj na spadających wrogów." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig to nadal przygłup!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Pora na Sekretny Poziom!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Skocz tyłkiem na srebrną skrzynkę,\n" "aby oczyścić sobie drogę" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Skrzynie mogą także ujawnić platformy..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Odmroź sprężynę..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh jest kamperem" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Spadające kamienie mogą\n" "przyprawić cię o ból głowy." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Miotacz ognia jest\n" "dobry na robaki." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Niech żyje poziom lodowy." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "To jeszcze nie koniec!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Co do cholery? Aaaa! No nie! \n" "Ten poziom jest niedokończony!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Okręcanie się na słupkach\n" "pozwala ci zwiększyć swoją prędkość." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Znalazłeś sekretną okolicę." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Tajny Pokój Skarbów" #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Nie ma tu nic ciekawego." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Zbieraj monety.\n" "A nuż odblokujesz jakiś bonus." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "We wtorki ser zielenieje." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig jest królem przygłupów." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Aby pokonać królową\n" "musisz zrzucić ją z platformy." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Dobra robota!\n" "Teraz idź skopać skorupę Devanowi!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Aby przebić się przez te bloki,\n" "wciśnij przyciski ruchu w dół i skoku!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Wciśnij przycisk ruchu w dół i skoku\n" "pod tymi blokami, aby je zniszczyć!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Skocz tyłkiem na metalową skrzynię,\n" "aby otworzyć bloki-klucze!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Stąpnij zadkiem, aby wyjść." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Ta sprężyna jest zamrożona." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Super duper sekret." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Tarcze dają ci nieskończoną \n" "ilość specjalnej amunicji na krótki czas." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Stopery mogą przedłużyć \n" "działanie tarczy." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Niech ci ziemia \n" "ciężką będzie, bydlaku!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Nie dasz rady mnie pokonać, Jazz!\n" "Przygotuj się na spotkanie z moim superbotem!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Oj! Pora stąd spadać!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "To bloki prędkości.\n" "Wbiegnij w nie najszybciej, jak się da!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Witaj w Jazz Jackrabbit 2. \n" "To jest poziom treningowy." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Zbieraj dobra, aby\n" "zdobyć punkty i niespodzianki." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Po skoku, wciśnij skok jeszcze raz\n" "aby wykonać specjalny ruch!" #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Niektóre ściany możesz rozstrzelać." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Kiedy jesteś w powietrzu, \n" "wciśnij przycisk ruchu w dół, \n" "żeby rąbnąć swoim tyłkiem." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Mnóstwo tu sekretów. \n" " Sprawdzaj ściany." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Dobra robota.\n" "Zawsze szukaj sekretów." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Zbieraj klejnoty, \n" "aby dostać ekstra życie." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Czerwone klejnoty\n" "liczą się za jeden." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Zielone klejnoty\n" "liczą się za pięć." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Niebieskie klejnoty\n" "liczą się za dziesięć." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Marchewka daje ci zdrowie." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Punkt kontrolny zapisze\n" "twój postęp, który zostanie\n" "przywrócony, jeśli stracisz życie." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Zbieraj monety,\n" "aby odblokować bonusowe pomieszczenia." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Strzeż się ostrych rzeczy.\n" "Nadzianie się na nie potrafi zaboleć." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Jesteś gotowy do gry.\n" " Baw się dobrze!" #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Skocz tyłkiem na pokrywę włazu!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Dymowe pierścienie \n" "mogą cię oszołomić!" #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Jeśli nie da się dołem\n" "to idź górą i na około!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Nie ma nagrody dla tych,\n" "których swędzą paluszki." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Prognoza na dziś: \n" "Uwaga na mocne wiatry!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Znajdź skrzynkę\n" "aby oczyścić sobie drogę." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Witamy w\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Tylko Spaz może dostać się\n" "do pomieszczenia na górze po lewej.\n" "Ale musi mieć na czym stanąć." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Nie wygrywaj z Nigelem w bilard.\n" "Zostałeś ostrzeżony. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Im więcej czekoladowych jajek zjesz,\n" "tym szybciej zapoznasz się z panem próchnicą :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Wjazd do tunelu" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Aby dostać się do tunelu nad tobą\n" "musisz poszukać dostępu." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Jedna droga prowadzi do bogactwa.\n" "Druga droga prowadzi do wojny." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Tylko biegli w podwójnych skokach\n" "mogą dobrać się do dóbr!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Znajdź skrzynię, aby \n" "pojawił się blok do wspinaczki" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Rozwalenie tej skrzyni\n" "uwolni też kilku wrogów. :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Uważaj przy wchodzeniu do domu....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Srebrne skrzynki nie dadzą się\n" "rozwalić pod wodą..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Skrzynia kontrolująca poziom wody znajduje się nad tobą." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Rozwalanie skrzyń ma swoje plusy i minusy..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Ale musisz znaleźć sposób,\n" " by się tam dostać..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Zawsze będę cię kochał :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Dawaj po dachach!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Niebo wynagrodzi tych,\n" "którzy tupią..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Skocz w prawo na tyle daleko\n" "na ile się da..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Czyżby skok ci nie wyszedł? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Świetnie ci idzie!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Użyj specjalnych ruchów, aby wznieść się w górę!\n" "Jazz - przysiad i skok.\n" "Spaz - dwa razy skok!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Znajdź skrzynkę, a klejnoty będą twoje!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Sprężyny nie działają, kiedy są zamrożone..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Za zebranie 20 monet czeka cię większa nagroda..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Znajdź skrzynię, pomoże ci pozbyć się bloków..." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "UWAGA! Stada kruków\n" "mogą być bardzo niebezpieczne!" #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Aby usunąć bloki, rozejrzyj\n" "się za największym budynkiem." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Wybierz osłonę i zmykaj!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Cześć GeoKróliczku :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Wejdź na www.project2.com\n" "i użyj hasła: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Rozwal te bloki!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Kiedy jesteś w powietrzu, wciśnij\n" "przycisk ruchu w dół żeby rąbnąć o ziemię\n" "swoimi czterema literami." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Aby przejść tę część uderz\n" "w sekretną, metalową skrzynkę." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Potrzebujesz 20-stu monet,\n" "aby wejść w ten sekretny portal!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Monety dają ci dostęp\n" "do tajemnych portali." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Walnij z zadka we właściwe miejsce, \n" "to może znajdziesz niespodziankę!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Niektóre skrzynki\n" "mogą skrywać w sobie\n" "bomby i przeciwników!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Potrzebujesz 20-stu monet,\n" "aby wejść w ten sekretny portal!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Miotacz ognia jest dobry \n" "na uporczywe robale." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Dymowe pierścienie \n" "mogą cię oszołomić!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Strzeż się wiedźmy!\n" "Może zamienić cię w żabę!" #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Jeśli zostałeś zamieniony w żabę,\n" " poproś o pomoc Długouchą Ewę!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Udało ci się!\n" "To już koniec wersji shareware.\n" "Sprawdź informacje o kupnie,\n" " aby dostać W I Ę C E J poziomów\n" "w swoje puszyste łapki!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Uważaj na kolce pod tobą!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Możesz skoczyć tyłkiem\n" "na słabsze części ścieżki!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Niektóre bloki mogą\n" "być zniszczone tylko przy\n" "pomocy określonej broni." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Witamy w Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Pozdrowienia świąteczne od\n" "Epic MegaGames\n" "Orange Games oraz\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Możesz stanąć na czubkach\n" "niektórych drzew!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Możesz skoczyć tyłkiem\n" "na słabsze części ścieżki!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Uderzanie bloków nad tobą\n" "może zostać ci wynagrodzone." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Skrzynia prowadząca do Szlaku Klejnotów." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Wejście do Szlaku Klejnotów.\n" "Wspinaj się na wierzchołki drzew, \n" "aby znaleźć i walnąć zadkiem\n" "w skrzynię wejściową." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Hej, Świnko!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Zmieniłaś moje życie na wiele różnych sposobów\n" "W podzięce dedykuję tobie ten projekt.\n" "Kocham cię najbardziej na świecie." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Mógłbym powiedzieć o tobie tyle rzeczy.\n" "Lecz me słowa zawsze prowadzą do tej samej konkluzji.\n" "Kocham cię, tak bardzo cię kocham!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Proszę, używaj TNT z głową, \n" "bo inaczej możesz ją stracić." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Tylko nie puszczaj!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Witamy w Norkowicach\n" "Proszę, jedź ostrożnie." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Ten most nie wygląda\n" "na zbyt stabilny..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert i Craig:\n" "Sprężyny RZĄDZĄ! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Opuszczasz Norkowice\n" "Zapraszamy ponownie." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Hasło: xmasbunny\n" "Proszę, odwiedź\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Poziom \"{}\" zainicjowany" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Po więcej informacji, odwiedź naszą oficjalną stronę internetową:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Kody oszustw nie są aktualnie dozwolone" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Gra rozpocznie się za niedługo!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] został sprażony przez \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] został sprażony przez otoczenie" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] rozłączył się" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] dołączył" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "{} zwyciężył!" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Nieznana komenda" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Reimplementacja gry \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] wydanej w 1998 " "roku. Wspiera różne wersje gry (Demo, Holiday Hare '98, The Secret Files i " "Christmas Chronicles) oraz (częściowo) niektóre funkcje fanowskiego " "rozszerzenia JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Autorzy" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Współautorzy" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Tłumacze" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Ten projekt używa zmodyfikowanego silnika \f[c:#9e7056]nCine\f[/c] oraz " "następujących bibliotek:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Tryb fabularny" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Graj w demo" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importuj epizod" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Kontynuuj" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Graj online" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Tabela wyników" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Opcje" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "O grze" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Wyjdź" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Po więcej informacji, odwiedź {} i  Discorda!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Dostęp do pamięci zewnętrznej został udzielony!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Zresetuj grę, aby prawidłowo załadować pliki \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#337233]." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Ta gra wymaga oryginalnych plików \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "Upewnij się, że pliki Jazz Jackrabbit 2 znajdują się w:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Zezwól na dostęp do pamięci zewnętrznej" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Skonfiguruj sterowanie gracza {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Skonfiguruj sterowanie" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Sterowanie dotykowe" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Przełączanie biegu" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Oznaczenia przycisków kontrolera" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Wibracje kontrolera" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Rozszerzone wsparcie PlayStation™" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Natywny przycisk \"Wstecz\"" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnostyka sterowania" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Przywróć domyślne" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Sterowanie" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Mocne" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Słabe" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Wyłączono" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Włączono" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Bohater" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Tryb gry" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Utwórz serwer" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Bitwa" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Bitwa drużynowa" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Wyścig" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Wyścig drużynowy" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Poszukiwanie skarbów" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Drużynowe poszukiwanie skarbów" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Zdobądź flagę" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Współpraca" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Nie można utworzyć serwera!\f[/c]\n" "\n" "\n" "Playlista została niepoprawnie skonfigurowana.\n" "Sprawdź konfigurację serwera i spróbuj ponownie." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Nie można utworzyć serwera!\f[/c]\n" "\n" "\n" "Sprawdź, czy na tym porcie nie działa\n" "żaden inny serwer i spróbuj ponownie." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Poziomy społeczności" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nie znaleziono żadnych poziomów społeczności!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Utwórz serwer z playlisty" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Utwórz serwer prywatny" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Utwórz serwer publiczny" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nie znaleziono żadnych epizodów!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Zresetuj epizod" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Najpierw musisz ukończyć \"{}\"!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Ten epizod jest zablokowany!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Klasyczny" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Chcę zagrać w grę jak za starych, dobrych czasów." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Odnowiony" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Chcę zagrać w grę z jakimiś nowościami." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Witaj w reimplementacji \f[c:#9e7056]Jazz Jackrabbit 2\f[/c]!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Rozgrywka" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Ulepszenia" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Możesz wybrać preferowany przez siebie styl rozgrywki.\n" "To ustawienie może zostać zmienione,\n" "kiedy tylko ci się spodoba w \f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c].\n" "Po więcej informacji, odwiedź {} oraz serwer  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Odnowiona rozgrywka" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Odniowiony interfejs w grze" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Odnowione menu główne" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Wspinanie na krawędzie" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Koło broni" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Możesz włączyć ulepszenia, które zostały dodane do tej rekreacji." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Włączono + Wyświetlanie ilości amunicji" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Język" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Skrypty" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Zmień na nową broń przy podniesieniu" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Zezwól na kody oszustw" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Nadpisz stan ukończenia epizodu" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Przeglądaj katalog z danymi gry" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Odśwież pamięć podręczną" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Tak" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Nie" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Tylko bez cheatów" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Tylko wyższy wynik" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Zawsze" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Tryb skalowania" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Rozdzielczość" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Pełny ekran" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Wygładzanie krawędzi" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Wygładzanie tła" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Jakość wody" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Pokazuj ślady gracza" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Dzielenie ekranu" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Preferuj oddalenie kamery" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Zachowaj proporcje w przerywnikach" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Zmiennoprzecinkowa precyzja" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Statystyki wydajności" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafika" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Niski" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Wysoki" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Pionowe" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Poziome" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Włączono \f[c:#d0705d](Eksperymentalne)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Krótki" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Szczegółowy" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Tabela wyników dla \f[c:#d0705d]gry podstawowej\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Tabela wyników dla \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "Wybierz pliki oryginalnej gry, aby odblokować dodatkowe epizody" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Przetwarzanie {} pliku..." msgstr[1] "Przetwarzanie {} plików..." msgstr[2] "Przetwarzanie {} plików..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Oczekiwanie na pliki..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Nie wybrano żadnych plików!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Nie zaimportowano żadnych nowych odcinków!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Nie wykryto żadnych kontrolerów!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Wybierz tryb gry" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Dźwięki" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Profil użytkownika" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Wznów" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Obserwuj" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Zapisz i wyjdź" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Rozłącz się i wyjdź" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Połącz z serwerem" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Przetwarzanie plików z katalogu \f[c:#9e7056]\"Źródło\"\f[/c]" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Nowo dodane poziomy i epizody będą za niedługo dostępne." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Lewo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Prawo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Góra" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Dół" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Rąbnięcie tyłkiem" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Strzał" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Skok" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Bieg" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Zmień broń" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Wstecz" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Włącz/Wyłącz konsolę" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Broń {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Naciśnij dowolny przycisk, aby przypisać" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "aby usunąć przypisanie" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Brak / Idealne piksele" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "Linie skanu CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "Maska cienia CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "Kratka przysłony CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monochromia" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Wybierz tryb skalowania" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Póki co nie znaleziono żadnych serwerów, ale ciągle szukam!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Naciśnij \f[c:#d0705d]Strzał\f[/c], aby kontynuować" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Głośność ogólna" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Głośność efektów dźwiękowych" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Głośność muzyki" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Liczba graczy lokalnych" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Poziom trudności" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Start" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Łatwy" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Średni" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Trudny" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "" "Możesz dostosować położenie miejsc dotykowych poprzez przeciągnięcie i " "puszczenie." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integracja z platformą Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Pseudonim gracza" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Unikalne ID gracza" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Punkty: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Gra rozpocznie się za {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "Oczekiwanie na jeszcze {} graczy" msgstr[1] "Oczekiwanie na {} graczy" msgstr[2] "Oczekiwanie na {} graczy" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Znajdź wyjście!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nie można załadować tego poziomu!\f[/c]\n" "\n" "\n" "Upewnij się, że wszystkie potrzebne pliki\n" "są na swoim miejscu i spróbuj ponownie." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nie można wznowić zapisanego stanu gry!\f[/c]\n" "\n" "\n" "Upewnij się, że wszystkie niezbędne pliki\n" "są na swoim miejscu i spróbuj ponownie." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Nienazwany serwer" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Podano nieprawidłowy parametr." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Nie można połączyć z serwerem!\f[/c]\n" "\n" "\n" "Wersja klienta nie jest kompatybilna z serwerem." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Uwierzytelnianie nie powiodło się.\n" "Skontaktuj się z administratorami serwera,\n" "aby uzyskać w tej sprawie więcej informacji." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Podano nieprawidłowe hasło." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Podano nieprawidłową nazwę gracza.\n" "Sprawdź swój profil i spróbuj ponownie." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Ten klient nie znajduje się na białej liście serwera.\n" "Skontaktuj się z administratorami serwera,\n" "aby uzyskać w tej sprawie więcej informacji." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Serwer wymaga zewnętrznego uwierzytelnienia.\n" "Skontaktuj się z administratorami serwera,\n" "aby uzyskać w tej sprawie więcej informacji." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Nie można połączyć z serwerem!\f[/c]\n" "\n" "\n" "Serwer jest pełny.\n" "Spróbuj ponownie później." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Nie można połączyć z serwerem!\f[/c]\n" "\n" "\n" "Serwer nie jest w stanie przetworzyć żądania.\n" "Spróbuj ponownie za kilka sekund." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Serwer jest zamykany.\n" "Spróbuj ponownie później." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Serwer jest zamykany z powodu prac technicznych.\n" "Spróbuj ponownie później." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Serwer jest zamykany z powodu ponownej konfiguracji.\n" "Spróbuj ponownie później." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Serwer jest zamykany z powodu aktualizacji.\n" "Sprawdź wersję swojego klienta i spróbuj ponownie później." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Spróbuj ponownie i sprawdź, czy problem nadal występuje.\n" "Sprawdź swoje połączenie sieciowe." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Nie można połączyć z serwerem!\f[/c]\n" "\n" "\n" "Serwer nie odpowiada na żądanie połączenia." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Zostałeś/aś \f[c:#907050]wykopany/a\f[/c] z serwera.\n" "Skontaktuj się z administratorem serwera po więcej informacji." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Zostałeś/aś \f[c:#907050]skazany/a na banicję\f[/c] z serwera.\n" "Skontaktuj się z administratorem serwera po więcej informacji." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Wykryto oszustwo." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Nie można połączyć się z serwerem!\f[/c]\n" "\n" "\n" "Twój klient nie zawiera wymaganej zawartości.\n" "Pobierz wymagane pliki i spróbuj ponownie." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]Połączenie zostało zerwane!\f[/c]\n" "\n" "\n" "Serwer rozłączył się z tobą z powodu braku aktywności." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Twój klient nie zawiera poziomu \"{}\".\n" "Pobierz wymagane pliki i spróbuj ponownie." #~ msgid "Unknown" #~ msgstr "Nieznane" #~ msgid "Play Custom Game" #~ msgstr "Gry społeczności" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Łączenie z {}:{}..." #~ msgid "Create Custom Server" #~ msgstr "Utwórz własny serwer" #~ msgid "Connect to Server" #~ msgstr "Dołącz do serwera" #~ msgid "or" #~ msgstr "lub" #~ msgid "Creating server..." #~ msgstr "Tworzenie serwera..." #~ msgid "Error" #~ msgstr "Błąd" deathkiller-jazz2-native-2a7ccef/Content/Translations/pt.mo000066400000000000000000000046521512772601700241720ustar00rootroot00000000000000)d;       %.=B IVm    ;!]{OV ] k v    '/W t  # > L :Q    $' !" )&%(# AboutBackChange WeaponCharacterControlsDifficultyDisabledDownEasyEnabledEnhancementsEpisode is locked!FireFullscreenHardJumpLanguageLedge ClimbingLeftMediumMusic VolumeNo custom level found!No episode found!None / Pixel-perfectOptionsPlay Custom LevelsPlay StoryQuitRescale ModeRestart episodeResumeRightRunSFX VolumeSave & ExitSelect Rescale ModeStartUpYou can enable enhancements that were added to this remake.You must complete "{}" first!Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: Language: pt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp AcercaVoltarMudar de ArmaPersonagemControlosDificuldadeDesativadoBaixoFácilAtivadoMelhoriasEpisódio bloqueado!DispararEcrã InteiroDifícilSaltarLínguaEscalar PlataformasEsquerdaMedioVolume da MúsicaNenhum nível personalizado encontrado!Nenhum episódio encontrado!Pixel-perfectDefiniçõesJogo PersonalizadoJogar HistóriaSairModo de Re-escalamentoRecomecar EpisódioResumirDireitaCorrerVolume dos EfeitosSalvar e SairSeleccione o Modo de Re-escalamentoComeçar JogoCimaPode ativar melhorias que foram adicionadas a este remake.Deve completar "{}" primeiro!deathkiller-jazz2-native-2a7ccef/Content/Translations/pt.po000066400000000000000000001454261512772601700242020ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:58+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: \n" "Language: pt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Jogar História" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Definições" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Acerca" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Sair" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Controlos" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Desativado" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Ativado" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Personagem" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Jogo Personalizado" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nenhum nível personalizado encontrado!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nenhum episódio encontrado!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Recomecar Episódio" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Deve completar \"{}\" primeiro!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episódio bloqueado!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "" #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Melhorias" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Escalar Plataformas" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Pode ativar melhorias que foram adicionadas a este remake." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Língua" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Modo de Re-escalamento" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Ecrã Inteiro" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "" msgstr[1] "" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Resumir" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Salvar e Sair" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Esquerda" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Direita" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Cima" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Baixo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Disparar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Saltar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Correr" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Mudar de Arma" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Voltar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Pixel-perfect" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Seleccione o Modo de Re-escalamento" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Volume dos Efeitos" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Volume da Música" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Dificuldade" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Começar Jogo" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Fácil" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Medio" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Difícil" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "" msgstr[1] "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" #, fuzzy #~| msgid "Play Custom Levels" #~ msgid "Host Custom Levels" #~ msgstr "Jogo Personalizado" #, fuzzy #~| msgid "Play Custom Levels" #~ msgid "Play Custom Levels Locally" #~ msgstr "Jogo Personalizado" #~ msgid "Error" #~ msgstr "Erro" deathkiller-jazz2-native-2a7ccef/Content/Translations/pt_BR.mo000066400000000000000000001070771512772601700245620ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@S SUVk%VVT(WO}WW}YXXYy/ZZZ[[{\]]M^^@w__?``uVaTa!bbTc`c7dRd8kd-ddEde71eiepeeeeeee ee ff *f-5fcf xf f f ffff&f gg %g 1g>gQgigog vg*g&g gg#gh%h/,h3\h h hhhh h i iii5i&>iei iiiiii%i!j(ju!Wuyuu!u%uuv1vNv>gv5vvv/w-3wawtw-wEwx7xTx#nx?xxx.yE@y0yByyzR+z<~zz#zzL{XT{D{+{"|A|H`|-|$|+|2(}%[}>}%}>}&%~AL~J~$~-~3,-`6y+?2k87׀2BC_=;7<U@'97S8܃8- Ȅ1Ԅ*415f#.2"72Cj+Ć<>-JlL80=Ln:>a5DC&<#c>Ɗފjj^Ջ&4?[>2ڌ &%5L0('܍:?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Eduardo Karasinski Language-Team: edukara & gabsF Language: pt_BR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n > 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp O jogo já vai começar! O vencedor é {} [c:#337233]Reinicie o jogo para ler os arquivos de [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] corretamente. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] Autenticação fracassada. Contate o administrador do servidor para mais informações. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] Parâmetro inválido. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] Senha inválida. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] Nome do jogador inválido. Por favor, cheque o seu perfil e tente novamente. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] O servidor está cheio. Por favor, tente novamente mais tarde. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] O servidor não está em um estado que pode processar o seu pedido. Por favor, tente novamente mais tarde. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] O servidor requer uma autenticação de terceiro. Contate o administrador do servidor para mais informações. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] O servidor não está respondendo pelo pedido de conexão. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] Este cliente não está na lista branca do servidor. Contate o administrador do servidor para mais informações. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] O seu cliente não contém a fase "{}". Por favor, baixe os arquivos necessários e tente novamente. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] O seu cliente não contém os arquivos necessários. Por favor, baixe os arquivos e tente novamente. [c:#704a4a]Não foi possível conectar-se ao servidor! [/c] A versão do seu cliente não é compatível com o servidor. [c:#704a4a]Não foi possível criar o servidor! [/c] Lista de jogo não está configurada. Por favor, revise a configuração do servidor e tente novamente. [c:#704a4a]Não foi possível criar o servidor! [/c] Por favor, verifique se não tem outro servidor rodando nesta porta e tente novamente. [c:#704a4a]Não foi possível carregar a fase! [/c] Tenha certeza de que todos os arquivos necessários estão acessíveis e tente novamente. [c:#704a4a]Não foi possível carregar o estado salvo! [/c] Tenha certeza de que todos os arquivos necessários estão acessíveis e tente novamente. [c:#704a4a]A conexão foi cancelada! [/c] Trapaça detectado. [c:#704a4a]A conexão foi cancelada! [/c] O servidor está sendo desligado por manutenção. Por favor, tente novamente mais tarde. [c:#704a4a]A conexão foi cancelada! [/c] O servidor está sendo desligado para ser reconfigurado. Por favor, tente novamente mais tarde. [c:#704a4a]A conexão foi cancelada! [/c] O servidor está sendo desligado para ser atualizado. Por favor, tente novamente mais tarde. [c:#704a4a]A conexão foi cancelada! [/c] O servidor está sendo desligado. Por favor, tente novamente mais tarde. [c:#704a4a]A conexão foi cancelada! [/c] O servidor te expulsou por inatividade. [c:#704a4a]A conexão foi cancelada! [/c] Você foi [c:#725040]banido [/c] do servidor. Contate o administrador do servidor para mais informações. [c:#704a4a]A conexão foi cancelada! [/c] Você foi [c:#907050]expulso [/c] do servidor. Contate o administrador do servidor para mais informações. [c:#704a4a]A conexão foi cancelada! [/c] Por favor, tente novamente e se o problema persistir, cheque a conexão da sua rede. [c:#704a4a]Esse jogo requer os arquivos originais de [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]! [c:#d0705d]{} [/c] entrou [c:#d0705d]{} [/c] saiu [c:#d0705d]{} [/c] foi torrado pelo [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] foi torrado pelo cenárioSobreAcesso ao dispositivo de armazenamento externo permitido com sucesso!Permitir trapaçasPermitir acesso ao dispositivo de armazenamento externoSempreAntisserrilhamentoVoltarSuavização de cores no fundoBatalhaExplorar o diretório "Source"BundadaCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCaptura BandeiraTrocar de armaPersonagemTrapaças não são permitidos neste contextoConectar ao servidorContinuarColaboradoresControlesCooperativoCriar servidor privadoCriar servidor públicoCriar servidorCriar servidor partir da lista de jogoDetalhadoDesenvolvedoresDificuldadeDesabilitadoDesconectar e sairIntegração do DiscordBaixoFácilHabilitadoHabilitado [c:#d0705d](Experimental) [/c]Habilitado com a contagem de muniçãoMelhoriasEpisódio bloqueado!Suporte de PlayStation™ extendidoAche a saída!AtirarPara mais informações, visite o site oficial:Para mais informações, visite {} e o  Discord!Tela cheiaModo de jogoO jogo começará em {}Ícones de botões do controleVibração do controleJogabilidadeGráficosDifícilAltoApenas pontuações altasRecordesRecordes do [c:#d0705d]jogo base [/c]Recordes do [c:#d0705d]{} [/c]HorizontalQuero jogar como era antes.Quero jogar com algo novo.Importar EpisódiosDiagnósticos do controlePularManter a Proporção nas CinemáticasIdiomaEscalada na BeiradaEsquerdaClássicoFase "{}" iniciadaBaixoTenha certeza de que os arquivos de Jazz Jackrabbit 2 estão no seguinte caminho:Volume PrincipalMédioMonocromáticoMúsicaBotão nativo de voltarNovas fases e episódios estarão disponíveis em breve.NãoApenas sem trapaçasNenhuma fase personalizada encontrada!Nenhum episódio encontrado!Nenhum arquivo foi selecionado!Nenhum controle foi detectado!Nenhum episódio novo foi importado!Nenhum servidor foi encontrado, mas ainda 'tamo procurando!Nenhum / Pixel-perfeitoNúmero de jogadores locaisOpçõesSubstituir conclusão de episódioMétricas de performanceJogar fases personalizadasJogar onlineJogar a Versão DEMOJogar Modo HistóriaNome do jogadorPontos: {}Prefirir menos zoomPreferência de tela divididaPressione [c:#d0705d]Atirar [/c] para continuarPressione qualquer tecla ou botão para vincularProcessando os arquivos do diretório [c:#9e7056]"Source" [/c]...Processando o arquivo {}...Processando os arquivos {}...SairCorridaRazer Chroma™ReforgedJogabilidade da ReforgedHUD da ReforgedMenu principal da ReforgedAtualizar CacheReimplementação do jogo [c:#9e7056]Jazz Jackrabbit 2 [/c] lançado em 1998. Suporta várias versões do jogo (Versão DEMO, Holiday Hare '98, The Secret Files e Christmas Chronicles). Também compatível com algumas funcionalidades da extensão JJ2+.Remapear controlesRemapear controles para o jogador {}Modo de ReescalaRedefinir ao padrãoResoluçãoRecomeçar episódioVoltar ao jogoDireitaCorrerEfeitos SonorosSalvar e SairScriptingSelecionar o modo de jogoSelecionar Modo de ReescalaSelecione arquivos do seu jogo original para desbloquear episódios adicionaisCurtoMostrar traços do jogadorÁudioObservarComeçarForteTrocar para nova armaBatalha de equipeCorrida de equipeCaça ao tesouro de equipeEsse projeto usa modificações da [c:#9e7056]nCine [/c] engine do jogo e as seguintes bibliotecas:Alternar o consoleAlternar correrControles de ToqueTradutoresCaça ao tesouroCampo de visão desalinhadoID de jogador únicoComando desconhecidoServidor sem nomeCimaPerfil do usuárioVerticalEsperando pelos arquivos...Esperando por {} mais jogadorEsperando por {} mais jogadoresQualidade da águaFracoRoda de ArmasArma {}Bem-vindo à reimplementação de [c:#9e7056]Jazz Jackrabbit 2 [/c]!SimVocê pode ajustar a posição dos controles tocando e arrastando-os.Você pode escolher o estilo de jogo preferido. Esta opção pode ser alterada a qualquer hora em [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Para mais informações, visite {} e o  Discord!Você pode ativar melhorias que foram adicionadas neste remake.Você deve completar "{}" primeiro! Os dragões moram em Burbank. Encontre o esquilo. Mark usa cuecas. Haha! O Nick ama brilho! Sempre amou! Spaz comeu o dopefish. Cuidado com a motosserra! Não dê um burrito para o Mark. Envie um cartão verde para o Nigel. Envie novas meias para o Tim. Cuidado com inimigos caindo. O Craig ainda é um bobão! Hora da Fase Secreta!!! Dê uma bundada em uma Caixa Prateada para limpar seu caminho Caixas também podem fazer plataformas aparecerem... Leh é um campista Derreta a Mola... Um lança-chamas funciona bem contra insetos. Pedras caindo podem ser uma dor de cabeça. Boa noite, Bubba! Longa vida às fases no gelo. Mas que diacho? Ah, não! Isso NÃO acabou! Colecione moedas para ativar dispositivos bônus de teletransporte. Nada para ver aqui. Mastros fazem você girar para ir ainda mais rápido. Sala Secreta do Tesouro. Você encontrou uma área secreta. Dê uma bundada na Caixa de metal para abrir os blocos-chave! O queijo é verde nas Terças. O Craig é o rei dos bobões. Bom trabalho! Agora vá pegar o Devan Shell! Pressione para baixo e pulo embaixo desses blocos para quebrá-los! Para derrotar a rainha derrube ela da beirada. Para passar por esses blocos, pressione para baixo e depois pulo! Dê uma bundada para sair. Essa mola congelou. Escudos vão lhe dar munição infinita especial por um curto período de tempo. Cronômetros vão adicionar tempo para a vida de um escudo. Super secreto. Esse schwartzenguarda está frito! Eita! Tô fora daqui! Você não pode me derrotar, Jazz! Prepare para encarar o meu Super-Robô! Esses blocos são suscetíveis à velocidade. Corra para cima deles à toda velocidade! Depois de pular, pressione pulo novamente para uma ação especial. Cuidado com coisas afiadas! Elas machucam! As Jóias azuis valem dez jóias. Cenouras aumentam sua saúde. Os Checkpoints salvam a sua posição no caso de você perder uma vida. Colete moedas para desbloquear salas bônus. Colete jóias para uma vida extra. Colete guloseimas para pontos e surpresas. Bom trabalho! Lembre-se de procurar por segredos. As Jóias Verdes valem cinco jóias. Agora você está pronto para jogar. Boa sorte e divirta-se! As Jóias Vermelhas valem uma jóia. Há muitos segredos em Jazz 2. Lembra de checar as paredes. Você pode atirar em algumas paredes. Bem-vindo ao Jazz Jackrabbit 2. Essa é a fase de treinamento. Quando estiver no ar, pressione para baixo para destruir com a sua bunda. Dê uma bundada na tampa de bueiro! As argolas de fumaça vão te deixar tonto. Não derrote Nigel na sinuca Você foi avisado. :) Encontre a caixa para liberar o seu caminho. Sem recompensas para aqueles com dedinhos apressados. Somente Spaz consegue chegar à sala que fica no topo à esquerda. Ele pode precisar de alguma coisa para subir em cima. Previsão do tempo de hoje: Ventos Fortes! Bem-vindo ao Jazz Jackrabbit 2: The Secret Files! Você não pode dar bundadas então suba e dê a volta!Comer muitos ovos de chocolate pode te deixar doente :p Um caminho leva aos ricos. Outro leva à batalha. Entrada do Túnel Inclinado Para acessar os túneis acima encontre o teletransporte de acesso. Encontre a caixa para fazer os blocos de escalada aparecerem Apenas aqueles com pulo-duplo podem chegar às guloseimas! Destruir essa caixa também liberta alguns inimigos :) Mas você precisa de uma maneira para chegar lá em cima... Entre na casa com cuidado... As Caixas Prateadas não podem ser quebradas debaixo d'água... Destruir caixas pode ser bom e ruim... A Caixa de controle do nível da água está logo acima. Michelle, Eu vou amar você para o infinito e além :) Não consegue, né? :) Pule para a direita o mais longe que você conseguir... Leve para os telhados! Os céus acima vão recompensar aqueles que destroem... Use Movimentos Especiais para pegar as moedas no ar! Para o Jazz, pressione para Baixo e então Pulo. Para o Spaz, pressione o botão de Pulo duas vezes! Muito bem! Coletar 20 moedas é muito mais recompensador... Encontre a caixa e as jóias serão suas! As molas não funcionam quando estão congeladas... CUIDADO! Bandos de Corvos podem ser muito perigosos. Escolha um disfarce e detone tudo! Encontre a caixa para acabar com os blocos... Acesse www.project2.com use a senha: BUNNYLOVER Oi GeoBunny :) Para acabar com os blocos olhe para o ponto mais alto. As moedas dão acesso para teletransportes que aparecerem depois. Atire nestes blocos! Algumas caixas contêm bombas ou inimigos! Destrua no lugar certo e você pode encontrar uma surpresa! Para passar por essa área, destrua a caixa secreta de metal. Quando estiver no ar, pressione para Baixo para destruir com a sua bunda. Você precisa de vinte moedas para passar por esse teletransporte secreto! Um lança-chamas funciona bem contra insetos nojentos. Argolas de fumaça vão lhe deixar muito tonto! Você precisa de vinte moedas para passar por esse teletransporte secreto! Cuidado com a bruxa! Ela pode lhe transformar em um sapo! Se você for transformado em sapo, a Eva Earlong pode ajudar! Você conseguiu! Esse é o fim da versão de testes. Cheque a versão completa para MUITO MAIS!para remover a vinculação Boas Festas da Epic MegaGames Orange Games e Project 2 Interactive! Alguns blocos só podem ser quebrados usando uma arma específica. Preste atenção nos espinhos abaixo! Bem vindo ao Christmas Chronicles! Você pode dar bundadas em alguns pontos fracos nos caminhos! Olá, Piggy! - Poopy Kassi Nicole: Um milhão de coisas quero dizer para você Mas as minhas palavras sempre acabam da mesma forma Eu amo você, eu amo você. Michelle: Você mudou a minha vida de tantas formas. Eu dedico esse projeto a você. Eu amo muito você. Entrada da Trilha de Jóias. Escale as árvores para encontrar e destruir a Caixa de Entrada. Caixa de Entrada da Trilha de Jóias. Dar porradas nos blocos acima de você pode ser recompensador. Você pode dar bundadas em alguns pontos fracos nos caminhos! Você pode ficar em cima de algumas das árvores! Não perca o controle! Saindo de Burrowsville. Volte sempre. Senha: xmasbunny Por favor, visite: www.project2.com Por favor, use as suas Dinamites com sabedoria. Robert and Craig: Molas SÃO DEMAIS! :) Essa ponte não parece muito segura... Bem-vindo à Burrowsville! Por favor, dirija com cuidado.deathkiller-jazz2-native-2a7ccef/Content/Translations/pt_BR.po000066400000000000000000002033501512772601700245540ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:58+0100\n" "PO-Revision-Date: \n" "Last-Translator: Eduardo Karasinski \n" "Language-Team: edukara & gabsF\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz comeu o dopefish." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Encontre o esquilo." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Os dragões moram em Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark usa cuecas. \n" "Haha!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "O Nick ama brilho! \n" "Sempre amou!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Envie novas meias para o Tim." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Envie um cartão verde para o Nigel." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Cuidado com a motosserra!" #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Não dê um burrito para o Mark." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Cuidado com inimigos caindo." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "O Craig ainda é um bobão!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Hora da Fase Secreta!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Dê uma bundada em uma Caixa Prateada\n" "para limpar seu caminho" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Caixas também podem fazer plataformas aparecerem..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Derreta a Mola..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh é um campista" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Pedras caindo podem \n" "ser uma dor de cabeça." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Um lança-chamas \n" "funciona bem contra insetos." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Longa vida às fases no gelo." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Boa noite, Bubba!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Mas que diacho? Ah, não! \n" "Isso NÃO acabou!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Mastros fazem você girar \n" "para ir ainda mais rápido." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Você encontrou uma área secreta." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Sala Secreta do Tesouro." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Nada para ver aqui." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Colecione moedas para ativar \n" "dispositivos bônus de teletransporte." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "O queijo é verde nas Terças." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "O Craig é o rei dos bobões." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Para derrotar a rainha \n" "derrube ela da beirada." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Bom trabalho! \n" "Agora vá pegar o Devan Shell!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Para passar por esses blocos,\n" "pressione para baixo e depois pulo!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Pressione para baixo e pulo embaixo \n" "desses blocos para quebrá-los!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Dê uma bundada na Caixa de metal \n" "para abrir os blocos-chave!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Dê uma bundada para sair." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Essa mola congelou." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Super secreto." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Escudos vão lhe dar munição infinita \n" "especial por um curto período de tempo." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Cronômetros vão adicionar tempo\n" "para a vida de um escudo." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Esse schwartzenguarda está frito!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Você não pode me derrotar, Jazz!\n" "Prepare para encarar o meu Super-Robô!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Eita! Tô fora daqui!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Esses blocos são suscetíveis à velocidade.\n" "Corra para cima deles à toda velocidade!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Bem-vindo ao Jazz Jackrabbit 2. \n" " Essa é a fase de treinamento." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Colete guloseimas para\n" "pontos e surpresas." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Depois de pular, pressione pulo\n" "novamente para uma ação especial." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Você pode atirar em algumas paredes." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Quando estiver no ar, pressione para baixo\n" "para destruir com a sua bunda." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Há muitos segredos em Jazz 2. \n" " Lembra de checar as paredes." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Bom trabalho! Lembre-se de\n" "procurar por segredos." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Colete jóias para \n" "uma vida extra." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "As Jóias Vermelhas valem\n" "uma jóia." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "As Jóias Verdes valem\n" "cinco jóias." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "As Jóias azuis valem\n" "dez jóias." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Cenouras aumentam sua saúde." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Os Checkpoints salvam a sua posição\n" "no caso de você perder uma vida." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Colete moedas para\n" "desbloquear salas bônus." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Cuidado com coisas afiadas!\n" "Elas machucam!" #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Agora você está pronto para jogar.\n" " Boa sorte e divirta-se!" #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Dê uma bundada na tampa de bueiro!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "As argolas de fumaça \n" "vão te deixar tonto." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Você não pode dar bundadas\n" "então suba e dê a volta!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Sem recompensas para aqueles\n" "com dedinhos apressados." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Previsão do tempo de hoje: Ventos Fortes!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Encontre a caixa\n" "para liberar o seu caminho." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Bem-vindo ao\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Somente Spaz consegue chegar\n" "à sala que fica no topo à esquerda.\n" "Ele pode precisar de alguma coisa\n" "para subir em cima." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Não derrote Nigel na sinuca\n" "Você foi avisado. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Comer muitos ovos de chocolate\n" "pode te deixar doente :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Entrada do Túnel Inclinado" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Para acessar os túneis acima\n" "encontre o teletransporte de acesso." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Um caminho leva aos ricos.\n" "Outro leva à batalha." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Apenas aqueles com pulo-duplo\n" "podem chegar às guloseimas!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Encontre a caixa para fazer\n" "os blocos de escalada aparecerem" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Destruir essa caixa também\n" "liberta alguns inimigos :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Entre na casa com cuidado..." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "As Caixas Prateadas não podem ser quebradas debaixo d'água..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "A Caixa de controle do nível da água está logo acima." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Destruir caixas pode ser bom e ruim..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Mas você precisa de uma maneira para chegar lá em cima..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Eu vou amar você para o infinito e além :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Leve para os telhados!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Os céus acima vão recompensar\n" "aqueles que destroem..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Pule para a direita o mais longe\n" "que você conseguir..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Não consegue, né? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Muito bem!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Use Movimentos Especiais para pegar as moedas no ar!\n" "Para o Jazz, pressione para Baixo e então Pulo.\vPara o Spaz, pressione o " "botão de Pulo duas vezes!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Encontre a caixa e as jóias serão suas!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "As molas não funcionam quando estão congeladas..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Coletar 20 moedas é muito mais recompensador..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Encontre a caixa para acabar com os blocos..." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "CUIDADO! Bandos de Corvos podem\n" "ser muito perigosos." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Para acabar com os blocos\n" "olhe para o ponto mais alto." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Escolha um disfarce e detone tudo!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Oi GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Acesse www.project2.com\n" "use a\n" "senha: BUNNYLOVER\n" "\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Atire nestes blocos!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Quando estiver no ar, pressione para Baixo\n" "para destruir com a sua bunda." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Para passar por essa área, destrua\n" "a caixa secreta de metal." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Você precisa de vinte moedas para passar \n" "por esse teletransporte secreto!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "As moedas dão acesso para \n" "teletransportes que aparecerem depois." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Destrua no lugar certo e\n" "você pode encontrar uma surpresa!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Algumas caixas contêm\n" "bombas ou inimigos!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Você precisa de vinte moedas para passar \n" "por esse teletransporte secreto!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Um lança-chamas funciona bem \n" "contra insetos nojentos." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Argolas de fumaça vão lhe deixar\n" "muito tonto!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Cuidado com a bruxa! Ela pode\n" "lhe transformar em um sapo!" #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Se você for transformado em sapo,\n" "a Eva Earlong pode ajudar!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Você conseguiu! Esse é o fim\n" " da versão de testes.\n" "Cheque a versão completa\n" "para MUITO MAIS!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Preste atenção nos espinhos abaixo!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Você pode dar bundadas\n" "em alguns pontos fracos\n" "nos caminhos!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Alguns blocos só podem\n" "ser quebrados usando\n" "uma arma específica." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Bem vindo ao Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Boas Festas da\n" "Epic MegaGames\n" "Orange Games e\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Você pode ficar em cima\n" "de algumas das árvores!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Você pode dar bundadas\n" "em alguns pontos fracos\n" "nos caminhos!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Dar porradas nos blocos acima de você\n" "pode ser recompensador." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Caixa de Entrada da Trilha de Jóias." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Entrada da Trilha de Jóias.\n" "Escale as árvores para encontrar\n" "e destruir a Caixa de Entrada." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Olá, Piggy!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Você mudou a minha vida de tantas formas.\n" "Eu dedico esse projeto a você.\n" "Eu amo muito você." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Um milhão de coisas quero dizer para você\n" "Mas as minhas palavras sempre acabam da mesma forma\n" "Eu amo você, eu amo você." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Por favor, use as suas Dinamites com sabedoria." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Não perca o controle!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Bem-vindo à Burrowsville!\n" "Por favor, dirija com cuidado." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Essa ponte não\n" "parece muito segura..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert and Craig:\n" "Molas SÃO DEMAIS! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Saindo de Burrowsville.\n" "Volte sempre." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Senha: xmasbunny\n" "Por favor, visite:\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Fase \"{}\" iniciada" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Para mais informações, visite o site oficial:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Trapaças não são permitidos neste contexto" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "O jogo já vai começar!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] foi torrado pelo \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] foi torrado pelo cenário" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] saiu" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] entrou" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "O vencedor é {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Comando desconhecido" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Reimplementação do jogo \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] lançado em " "1998. Suporta várias versões do jogo (Versão DEMO, Holiday Hare '98, The " "Secret Files e Christmas Chronicles). Também compatível com algumas " "funcionalidades da extensão JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Desenvolvedores" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Colaboradores" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Tradutores" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Esse projeto usa modificações da \f[c:#9e7056]nCine\f[/c] engine do jogo e " "as seguintes bibliotecas:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Jogar Modo História" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Jogar a Versão DEMO" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importar Episódios" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Continuar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Jogar online" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Recordes" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Opções" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Sobre" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Sair" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Para mais informações, visite {} e o  Discord!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Acesso ao dispositivo de armazenamento externo permitido com sucesso!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Reinicie o jogo para ler os arquivos de \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#337233] corretamente." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Esse jogo requer os arquivos originais de \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Tenha certeza de que os arquivos de Jazz Jackrabbit 2 estão no seguinte " "caminho:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Permitir acesso ao dispositivo de armazenamento externo" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Remapear controles para o jogador {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Remapear controles" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Controles de Toque" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Alternar correr" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Ícones de botões do controle" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibração do controle" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Suporte de PlayStation™ extendido" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Botão nativo de voltar" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnósticos do controle" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Redefinir ao padrão" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Controles" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Forte" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Fraco" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Desabilitado" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Habilitado" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Personagem" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Modo de jogo" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Criar servidor" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Batalha" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Batalha de equipe" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Corrida" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Corrida de equipe" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Caça ao tesouro" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Caça ao tesouro de equipe" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Captura Bandeira" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Cooperativo" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Não foi possível criar o servidor!\f[/c]\n" "\n" "\n" "Lista de jogo não está configurada.\n" "Por favor, revise a configuração do servidor e tente novamente." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Não foi possível criar o servidor!\f[/c]\n" "\n" "\n" "Por favor, verifique se não tem outro\n" "servidor rodando nesta porta e tente novamente." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Jogar fases personalizadas" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nenhuma fase personalizada encontrada!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Criar servidor partir da lista de jogo" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Criar servidor privado" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Criar servidor público" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nenhum episódio encontrado!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Recomeçar episódio" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Você deve completar \"{}\" primeiro!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episódio bloqueado!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Clássico" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Quero jogar como era antes." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Reforged" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Quero jogar com algo novo." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Bem-vindo à reimplementação de \f[c:#9e7056]Jazz Jackrabbit 2\f[/c]!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Jogabilidade" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Melhorias" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Você pode escolher o estilo de jogo preferido.\n" "Esta opção pode ser alterada a qualquer hora em \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "Para mais informações, visite {} e o  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Jogabilidade da Reforged" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "HUD da Reforged" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Menu principal da Reforged" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Escalada na Beirada" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Roda de Armas" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Você pode ativar melhorias que foram adicionadas neste remake." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Habilitado com a contagem de munição" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Idioma" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Scripting" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Trocar para nova arma" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Permitir trapaças" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Substituir conclusão de episódio" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Explorar o diretório \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Atualizar Cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Sim" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Não" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Apenas sem trapaças" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Apenas pontuações altas" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Sempre" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Modo de Reescala" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Resolução" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Tela cheia" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antisserrilhamento" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Suavização de cores no fundo" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Qualidade da água" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Mostrar traços do jogador" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Preferência de tela dividida" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Prefirir menos zoom" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Manter a Proporção nas Cinemáticas" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Campo de visão desalinhado" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Métricas de performance" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Gráficos" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Baixo" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Alto" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertical" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Horizontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Habilitado \f[c:#d0705d](Experimental)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Curto" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detalhado" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Recordes do \f[c:#d0705d]jogo base\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Recordes do \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Selecione arquivos do seu jogo original para desbloquear episódios adicionais" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Processando o arquivo {}..." msgstr[1] "Processando os arquivos {}..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Esperando pelos arquivos..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Nenhum arquivo foi selecionado!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Nenhum episódio novo foi importado!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Nenhum controle foi detectado!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Selecionar o modo de jogo" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Áudio" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Perfil do usuário" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Voltar ao jogo" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Observar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Salvar e Sair" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Desconectar e sair" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Conectar ao servidor" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Processando os arquivos do diretório \f[c:#9e7056]\"Source\"\f[/c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Novas fases e episódios estarão disponíveis em breve." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Esquerda" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Direita" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Cima" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Baixo" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Bundada" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Atirar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Pular" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Correr" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Trocar de arma" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Voltar" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Alternar o console" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Arma {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Pressione qualquer tecla ou botão para vincular" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "para remover a vinculação" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Nenhum / Pixel-perfeito" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT Scanlines" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT Shadow Mask" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT Aperture Grille" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monocromático" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Selecionar Modo de Reescala" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Nenhum servidor foi encontrado, mas ainda 'tamo procurando!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Pressione \f[c:#d0705d]Atirar\f[/c] para continuar" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Volume Principal" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Efeitos Sonoros" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Música" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Número de jogadores locais" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Dificuldade" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Começar" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Fácil" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Médio" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Difícil" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Você pode ajustar a posição dos controles tocando e arrastando-os." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integração do Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Nome do jogador" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "ID de jogador único" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Pontos: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "O jogo começará em {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "Esperando por {} mais jogador" msgstr[1] "Esperando por {} mais jogadores" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Ache a saída!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Não foi possível carregar a fase!\f[/c]\n" "\n" "\n" "Tenha certeza de que todos os arquivos necessários\n" "estão acessíveis e tente novamente." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Não foi possível carregar o estado salvo!\f[/c]\n" "\n" "\n" "Tenha certeza de que todos os arquivos necessários\n" "estão acessíveis e tente novamente." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Servidor sem nome" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "Parâmetro inválido." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "A versão do seu cliente não é compatível com o servidor." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "Autenticação fracassada.\n" "Contate o administrador do servidor para mais informações." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "Senha inválida." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "Nome do jogador inválido.\n" "Por favor, cheque o seu perfil e tente novamente." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "Este cliente não está na lista branca do servidor.\n" "Contate o administrador do servidor para mais informações." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "O servidor requer uma autenticação de terceiro.\n" "Contate o administrador do servidor para mais informações." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "O servidor está cheio.\n" "Por favor, tente novamente mais tarde." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "O servidor não está em um estado que pode processar o seu pedido.\n" "Por favor, tente novamente mais tarde." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "O servidor está sendo desligado.\n" "Por favor, tente novamente mais tarde." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "O servidor está sendo desligado por manutenção.\n" "Por favor, tente novamente mais tarde." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "O servidor está sendo desligado para ser reconfigurado.\n" "Por favor, tente novamente mais tarde." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "O servidor está sendo desligado para ser atualizado.\n" "Por favor, tente novamente mais tarde." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "Por favor, tente novamente e se o problema persistir,\n" "cheque a conexão da sua rede." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "O servidor não está respondendo pelo pedido de conexão." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "Você foi \f[c:#907050]expulso\f[/c] do servidor.\n" "Contate o administrador do servidor para mais informações." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "Você foi \f[c:#725040]banido\f[/c] do servidor.\n" "Contate o administrador do servidor para mais informações." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "Trapaça detectado." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "O seu cliente não contém os arquivos necessários.\n" "Por favor, baixe os arquivos e tente novamente." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]A conexão foi cancelada!\f[/c]\n" "\n" "\n" "O servidor te expulsou por inatividade." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Não foi possível conectar-se ao servidor!\f[/c]\n" "\n" "\n" "O seu cliente não contém a fase \"{}\".\n" "Por favor, baixe os arquivos necessários e tente novamente." #~ msgid "Error" #~ msgstr "Erro" deathkiller-jazz2-native-2a7ccef/Content/Translations/ro.mo000066400000000000000000001113021512772601700241560ustar00rootroot00000000000000FL |XYx_{McL{^zieg g!!y"n"l"A^#r#v$$\%ay%%t&| 'X'' (6"(.Y((,( ( (( (())) 8)B) V)d)t) ) )))))) )) **** ?*M*i* r* }*******'** ++)+ I+T+1Y+/+ + ++++,,,,#, 5,)@,"j, ,.,+,,---:-C-R-W-^-u-@y- -- - --7-2.5.D.[.m...&...//7/K/ ^/j/ ~/ / ///'/!/=02N000000 00 0011 2!2 22=2M2T2Z2 ^2 i2 u222@22223333 03 <3F3VY33 33 3 3344'464 94F4O46d4 44 4 4?45< 5F5; 6G6(e6 6,6.6& 7+27)^7(7#7,7*8&-8AT8>8%8(86$99[99&9<9A:'[:D:(:+:@;/^;);6;L;?<<F|<,<)<S=Fn=(=2=(>L:>N>E>0?,M?(z?>?4?0@9H@7@.@A@*+A<VA'AIADB.JB3yBDB6BC)CdmC1CCD<HDFDID+EEBEFEJECF:^F6F@F;G5MGDG.GFG(>H@gH}H&I:CI8~I3IAI0-J8^JAJ JEJC@K$K6KHKA)LEkLIL?L6;MIrM@MDMrBNN^NF)O0pO1OQO+%PQPvPZRQ'Q@QQR;hR&R=RB S,LS3yS4S@S#T">VaVh{VVQtWQWXX*YY`ZZ[N\m\i]^^%_;__p``maebb%cc_cddd9d;9eue+|ee$e e eee f$fAfTfmfff ff-ff g g "g 0g ;gEg^gwg"gg g g ggghh h'h=hXhihhhh8h;h *i 8iEi%Ziiiiiiii/i#j *j"4j Wjxjjj-jjjjkk +kR5kkkk kkGkll&.lUl#rll%l,lmm6m!?mam}m mmmm mmn2 n1SnAnVno&o,oCoRo houoo op(pp q +q6qKq^qfqkq~q qqqNq!r'rBr HrRrXrar|rr!rnr5sHs\s lsxssssssss!td(tttttEtu?uEuK#v(ovvvv7v'w!Awcw#ww"ww%wA%x>gxxxFx6yPy$fy-yRy zC+zozzGzz{5*{?`{R{={ 1|(R|^{|E| }$8}]}Pw}H}\~-n~G~~R5S:65D1FvL; "FKi@*!5>-tBh/N6~KA1C!uEA݄@G`7$.74-l4#φ<0BH !:/7j98܈#.9;hELJ,dLNފ=-NkP' N3CJƌtP5#)'MDuӎsc~׏3V;HƐ0@EZ=9ޑ8%QJwYu y}'q3 /P  .*O?@$>*w##%[JnA<x%rE )7V"82zo{)=s;Mh$R0>l\'+Ue= 2D &4CI" TW8SHB3KL9<+:!(p-b5GCj7 /64a5DfQ X@N9d`_-E]~,F^i&1 |:.kc6!F(Z?vBmA1gt0 ;, The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContinuous JumpContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: 1NSH4N3 & Akfiz Language: ro_RO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 || (n!=1 && n%100>=1 && n%100<=19) ? 1 : 2); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Jocul va începe în scurt timp! Câștigătorul este {} [c:#337233]Repornește jocul pentru a citi corect fișierele [c:#9e7056]Jazz Jackrabbit 2 [c:#337233]. [c:#704a4a]Nu se poate conecta la server! [/c] Autentificarea a eșuat. Contactați administratorii serverului pentru mai multe informații. [c:#704a4a]Nu se poate conecta la server! [/c] Parolă specificată nevalidă. [c:#704a4a]Nu se poate conecta la server! [/c] Parolă specificată nevalidă. [c:#704a4a]Nu se poate conecta la server! [/c] Nume de jucător specificat nevalid. Te rugăm să verifici profilul și să încerci din nou. [c:#704a4a]Nu se poate conecta la server! [/c] Capacitatea serverului este completă. Vă rugăm să încercați mai târziu. [c:#704a4a]Nu se poate conecta la server! [/c] Serverul nu se află într-o stare în care să poată procesa cererea dumneavoastră. Vă rugăm să încercați din nou în câteva secunde. [c:#704a4a]Nu se poate conecta la server! [/c] Serverul necesită furnizorul de autentificare 3rd party. Contactați administratorii serverului pentru mai multe informații. [c:#704a4a]Nu se poate conecta la server! [/c] Serverul nu răspunde la cererea de conectare. [c:#704a4a]Nu se poate conecta la server! [/c] Acest client nu este în lista albă a serverului. Contactați administratorii serverului pentru mai multe informații. [c:#704a4a]Nu se poate conecta la server! [/c] Clientul dvs. nu conține nivelul "{}". Vă rugăm să descărcați fișierele necesare și să încercați din nou. [c:#704a4a]Nu se poate conecta la server! [/c] Clientul dvs. nu conține resursele necesare. Vă rugăm să descărcați fișierele necesare și să încercați din nou. [c:#704a4a]Nu se poate conecta la server! [/c] Versiunea clientului dvs. nu este compatibilă cu serverul. [c:#704a4a]Nu se poate crea serverul! [/c] Lista de joc nu este configurată corect. Vă rugăm să verificați configurația serverului și să încercați din nou. [c:#704a4a]Nu se poate crea serverul! [/c] Verificați dacă niciun alt server nu rulează pe acel port și încercați din nou. [c:#704a4a]Nu se poate încărca nivelul specificat! [/c] Asigură-te că toate fișierele necesare sunt accesibile și încearcă din nou. [c:#704a4a]Nu se poate relua starea salvată! [/c] Asigurați-vă că toate fișierele necesare sunt accesibile și încercați din nou. Conexiunea a fost închisă! [/c] S-a detectat trișare. [c:#704a4a]Conexiunea a fost închisă! [/c] Serverul se oprește pentru întreținere. Vă rugăm să încercați mai târziu. [c:#704a4a]Conexiunea a fost închisă! [/c] Serverul se oprește pentru reconfigurare. Vă rugăm să încercați mai târziu. [c:#704a4a]Conexiunea a fost închisă! [/c] Serverul se oprește pentru actualizare. Vă rugăm să verificați versiunea clientului dvs. și să încercați din nou într-un minut. [c:#704a4a]Conexiunea a fost închisă! [/c] Serverul se închide. Vă rugăm să încercați mai târziu. [c:#704a4a]Conexiunea a fost întreruptă! [/c] Serverul v-a deconectat din cauza inactivității. [c:#704a4a]Conexiunea a fost închisă! [/c] Ați fost [c:#725040]banat [/c] de pe server. Contactați administratorii serverului pentru mai multe informații. [c:#704a4a]Conexiunea a fost închisă! [/c] Ați fost [c:#90705050]dat afară [/c] de pe server. Contactați administratorii serverului pentru mai multe informații. [c:#704a4a]Conexiunea a fost pierdută! [/c] Vă rugăm să încercați din nou și dacă problema persistă, verificați conexiunea de rețea. [c:#704a4a]Acest joc necesită fișierele originale [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]! [c:#d0705d]{} [/c] conectat [c:#d0705d]{} [/c] deconectat [c:#d0705d]{} [/c] a fost prăjit de [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] a fost prăjit de mediul înconjurătorDespreAccesul la memoria externă a fost acordat!Permite CoduriPermite accesul la stocarea externăÎntotdeaunaAntialiasareÎnapoiDithering de fundalBătălieRăsfoiți directorul sursa "Source"Sparge cu șezutulGrilă de Deschidere CRTLinii de Scanare CRTMască de Umbră CRTCapturați SteagulSchimbă ArmaCaracterCodurile nu sunt permise în contextul actualConectare la serverContinuăSăritură continuăContribuitoriControluriCooperareCreați un server privatCreați un server publicCreați un serverCreați un server din lista de jocDetaliatDezvoltatoriDificultateDezactivatDeconectare & IeșireIntegrare cu DiscordJosUsorActivatActivat [c:#d0705d](Experimental) [/c]Activat cu Număr GloanțeÎmbunătățiriEpisodul este blocat!Suport PlayStation™ extinsGăsiți ieșirea!TragePentru mai multe informații, vizitați site-ul oficial:Pentru mai multe informații, vizitați {} și  Discord!Ecran CompletModul de jocJocul începe în {}Etichete pentru butoanele gamepaduluiVibrațiile GamepaduluiGameplayGraficăGreuÎnaltăDoar scorul mai marePunctajePunctaje pentru [c:#d0705d]Jocul de bază [/c]Punctaje pentru [c:#d0705d]{} [/c]OrizontalVreau să joc așa cum era odată.Vreau să joc jocul cu ceva nou.Importează episoadeDiagnosticarea intrărilorSăriPăstrează Raportul de Aspect în CinematiceLimbăCățărare pe MarginiStângaJocul OriginalNivelul "{}" inițializatScăzutăAsigură-te că fișierele Jazz Jackrabbit 2 sunt prezente în următorul fișier:Volum PrincipalMediuMonocromVolum MuzicăButonul Nativ de ÎntoarcereNivelurile și episoadele nou adăugate vor fi disponibile în curând.NuDoar fara coduriNu s-a găsit niciun nivel customizat!Nu s-a găsit niciun episod!Nu a fost selectat nici un fișier!Nu sunt detectate gamepad-uri!Niciun episod nou nu a fost importat!Nu s-au găsit servere, dar încă mai caut!Nimic / Pixel-perfectNumar local de jucatoriOpțiuniSuprascrie sfârșitul episoduluiIndicatori de PerformanțăJoacă Niveluri CustomizateJoacă onlineJoacă Versiunea DemoJoacă PovesteaNumele JucătoruluiPuncte: {}Preferat ecran micșoratPreferat ecranul imparțitApăsați [c:#d0705d]Trage [/c] pentru a continuaApăsați orice tastă sau buton pentru a atribuiProcesarea fișierelor [c:#9e7056]"Sursa" [/c] din directorul...Procesarea fișierului {}...Procesarea fișierelor {}...Procesarea fișierelor {}...IeșireCursaSuport Razer Chroma™Jocul ReforjatModul de joc ReforjatHUD ReforjatMeniul Principal ReforjatReîncarcă Memoria CacheRe-Implementare a jocului [c:#9e7056]Jazz Jackrabbit 2 [/c] lansat în 1998. Suportă diferite versiuni ale jocului (Shareware Demo, Holiday Hare '98, The Secret Files și Christmas Chronicles). De asemenea, suportă parțial unele caracteristici ale extensiei JJ2+.Remapați comenzileRemapați comenzile pentru jucătorul {}Moduri de RedimensionareResetați la valorile impliciteRezoluțieRepornește episodulReluare activitateDreaptaFugiVolum SFX (Efecte)Salvează și IeșiScripturiSelectați Modul de JocAlege Modul de RedimensionareSelectează fișiere din jocul original pentru a debloca episoade suplimentareScurtArată Urmele JucătorilorSunetSpectatorStartPuternicComutați la o armă nouăBătălie în EchipăCursa în echipăVânătoare de comori în echipăAcest proiect folosește o versiune modificata a motorului [c:#9e7056]nCine [/c] și următoarele biblioteci:Comutator ConsolăComutați AlergareaComenzi TactileTraducatoriVânătoare de ComoriVizualizare nealiniatăID Unic al JucătoruluiComandă necunoscutăServer fără numeSusProfil utilizatorVerticalÎn așteptare pentru fișiere...Se așteaptă încă {} jucătorSe așteaptă încă {} jucătoriSe așteaptă încă {} jucătoriCalitatea ApeiSlabRoata Pentru ArmeArmă {}Bine ați venit la [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementare!DaPoți ajusta poziția zonelor tactile prin a le trage și fixa.Puteți alege stilul de joc preferat. Această opțiune poate fi schimbată în orice moment în [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. Pentru mai multe informații, vizitați {} și  Discord!Poți activa îmbunătățirile care au fost adăugate la acestă refacere.Trebuie să finalizezi mai întâi "{}"! Dragonii locuiesc în Burbank. Găsește popândăul. Mark poartă boxeri. Hoo Hah! Nick iubește tot ce este strălucitor. Mereu a iubit! Spaz a mâncat peștele. Feriți-vă de drujbele schmalz. Nu-i da lui Mark un burrito. Trimite-i lui Nigel o carte verde. Trimite-i lui Tim șosete noi. Ferește-te de inamicii care cad. Craig e tot un clown! Este timpul pentru nivelul secret!!! Sparge cu șezutul o cutie argintie Pentru a iți elibera calea De asemenea, lădițele pot face ca platformele să apară... Leh este un camper Topește trambulina... Un aruncător de flăcări funcționează bine împotriva insectelor. Bolovanii în cădere pot să iți dea dureri de cap. Noapte bună, frate! Să trăiască nivelul de gheață. Ce naiba? Aaaah! Nu! Asta NU s-a terminat! Colectează monedele pentru a activa dispozitive de teleportare in zonele bonus. Nu este nimic de văzut aici. Bările de metal te învârt ca să te poți duce și mai repede. Camera secretă a comorii. Ai găsit o zonă secretă. Sparge cu șezutul cutia de metal pentru a deschide cuburile cu chei! Cașcavalul este verde marți. Craig este regele clown. Bună treabă! Acum du-te și adu-l pe Devan Shell! Apasă în jos și sari sub aceste blocuri pentru a le sparge! Pentru a o învinge pe regină împușc-o până cade de pe marginea platformei. Pentru a trece prin aceste blocuri, apasă în jos și sari! Mişcă-ţi fundul spre iesire. Această trambulină este înghețată. Scuturile îți vor oferi muniție nelimitată specială pentru o perioadă scurtă de timp. Cronometrele iți vor adăuga timp la durata de viață a unui scut. Un secret foarte mare. Acest schwartzenguard este prăjit! Ack! Am plecat de aici! Nu mă poți învinge, Jazz! Pregătește-te să înfrunți super robotul meu! Aceste cuburi sunt cuburi de viteză. Alergă în ele cu toată viteza! După ce ai sărit, apasă pe butonul de sărit din nou pentru a face o mișcare specială. Atentie la lucrurile ascuțite. Acestea dor. Pietrele prețioase albastre se consideră ca zece pietre prețioase. Morcovii îți dau viața. Punctele de salvare iți salvează progresul în cazul în care pierzi o viață. Colectează monede pentru a debloca camerele bonus. Colectează pietre prețioase pentru o viață în plus. Colectează bunătățuri pentru puncte și surprize. Bună treabă. Nu uita să să cauți după secrete. Pietrele prețioase verzi se consideră ca cinci pietre prețioase. Acum ești pregătit să joci. Mult noroc și distracție plăcută. Pietrele prețioase roșii se consideră ca o singură piatră prețioasă. Secretele sunt abundente în Jazz 2. Verifică pereții. Unii pereți pot fi împușcați. Bine ai venit la Jazz Jackrabbit 2. Acesta este un nivel de antrenament. Când ești în aer, apasă în jos pentru a sparge cu fundul. Sparge cu șezutul capacul de canalizare! Inelele de fum te amețesc. Nu-l bate pe Nigel la biliard. Ai fost avertizat. :) Găsește cutia pentru a iți elibera calea. Nu există recompense pentru cei care au mâncărimi pe trăgaci. Doar Spaz poate ajunge la camera sus în stânga. S-ar putea să aibă nevoie de ceva pe care să stea. Previziuni pentru astăzi: Vânturi puternice! Bine ai venit la Jazz Jackrabbit 2: The Secret Files! Nu poți să le spargi cu șezutul așa că mergi în sus și ocoleștele!Mâncatul prea multor ouă de ciocolată te poate îmbolnăvi :p O cale duce la bogăție. O cale duce la luptă. Intrarea înclinată a tunelului Pentru a accesa tunelurile de mai sus găsește zona de teleportare. Găsește cutia pentru a face cuburile de cățărare să apară Doar cei care pot sări de două ori pot ajunge la bunătăți! Săritul cu fundul pe această cutie eliberează și niște inamici :) Dar ai nevoie de o modalitate de a ajunge acolo sus... Intră în casă cu precauție..... Cutiile argintii nu pot fi sparte sub apă... Săritul cu fundul pe o cutie poate fi bun și rău... Nivelul apei controlează cutia de deasupra. Michelle, Te voi iubi mereu și pentru totdeauna :) Nu ai făcut saltul, nu-i așa? :) Sari cât mai departe spre dreapta pe cât de mult poți... Du-te pe acoperișuri! Cerul de sus îi va răsplăti pe cei care vor sări cu fundul... Folosește-ți mișcările speciale pentru a te ridica în curenții de aer! Pentru Jazz, apasă în jos și sări. Pentru Spaz, sări de două ori! Bine lucrat! Să colectezi 20 de monede este mult mai răsplătitor... Găsește cutia și pietrele prețioase sunt ale tale! Trambulinele nu funcționează când sunt înghețate... ATENȚIE! Stolurile de corbi pot fi foarte periculoase. Alege un blocaj și dă-i bătaie! Găsește cutia pentru a elimina cuburile.... Intră pe www.project2.com utilizează parola: BUNNYLOVER Salut GeoBunny :) Pentru a îndepărta cuburile uită-te la cea mai înaltă clădire. Monedele îți oferă acces la zonele de teleportare care apar mai târziu. Trage în aceste cuburi! Unele cutii conțin bombe sau lucruri rele! Lovește cu fundul locul potrivit și s-ar putea să găsești o surpriză! Pentru a trece de această zonă, lovește cu fundul cutia de metal secretă. Când ești în aer, apasă în jos pentru a lovi cu fundul. Ai nevoie de 20 de monede pentru a trece prin această teleportare secretă! Un aruncător de flăcări funcționează bine împotriva insectelor enervante. Inelele de fum te amețesc foarte rau! Ai nevoie de 20 de monede pentru a trece prin această teleportare secretă! Ai grijă la vrăjitoare! Ea te poate transforma într-o broască. Dacă ești transformat într-o broască Eva Urechi-Lungi te poate ajuta! Ai reușit! Ăsta este sfârșitul versiunii shareware. Acum verifică informațiile de comandă pentru MAI MULTE!pentru a elimina atribuirea Salutări de sezon de la Epic MegaGames Orange Games și Project 2 Interactive! Unele cuburi pot fi sparte doar cu o anumită armă. Atenție la țepușele de mai jos! Bine ai venit la Christmas Chronicles! Poți sparge cu șezutul prin unele din punctele slabe din drumuri! Bună, Piggy! - Poopy Kassi Nicole: Am un milion de lucruri pe care doresc să ți le spun Dar cuvintele mele duc mereu la același sfârșit Te iubesc, te iubesc. Michelle: Mi-ai schimbat viața în atât de multe feluri. Îți dedic ție acest proiect. Te iubesc foarte mult. Intrarea în traseul pietrelor prețioase. Urcă-te în vârful copacilor pentru a găsi și lovi cu fundul cutia de intrare. Cutia de intrare în traseul pietrelor prețioase. Lovirea cuburilor de deasupra ta poate fi răsplătitoare. Poți sparge cu șezutul prin unele din punctele slabe din căile tale! Poți să stai în vârful unora dintre copaci! Nu-ți pierde aderența! Acum părăsiți "Burrowsville" Vă rugăm să ne vizitați din nou. Parolă: xmasbunny Vă rugăm să vizitați www.project2.com Te rog să îți folosești dinamita cu înțelepciune. Robert și Craig: Trambulinele sunt cele mai mișto! :) Podul acela nu nu pare prea sigur... Bine ați venit în "Burrowsville" Vă rugăm să conduceți cu atenție.deathkiller-jazz2-native-2a7ccef/Content/Translations/ro.po000066400000000000000000002064031512772601700241700ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:59+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: 1NSH4N3 & Akfiz\n" "Language: ro_RO\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 || (n!=1 && n%100>=1 && " "n%100<=19) ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz a mâncat peștele." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Găsește popândăul." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Dragonii locuiesc în Burbank." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark poartă boxeri.\n" "Hoo Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick iubește tot ce este strălucitor.\n" "Mereu a iubit!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Trimite-i lui Tim șosete noi." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Trimite-i lui Nigel o carte verde." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Feriți-vă de drujbele schmalz." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Nu-i da lui Mark un burrito." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Ferește-te de inamicii care cad." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig e tot un clown!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Este timpul pentru nivelul secret!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Sparge cu șezutul o cutie argintie \n" "Pentru a iți elibera calea" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "De asemenea, lădițele pot face ca platformele să apară..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "Topește trambulina..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh este un camper" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Bolovanii în cădere pot\n" "să iți dea dureri de cap." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Un aruncător de flăcări funcționează\n" "bine împotriva insectelor." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Să trăiască nivelul de gheață." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "Noapte bună, frate!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Ce naiba? Aaaah! Nu! \n" "Asta NU s-a terminat!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Bările de metal te învârt ca să \n" "te poți duce și mai repede." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Ai găsit o zonă secretă." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Camera secretă a comorii." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Nu este nimic de văzut aici." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Colectează monedele pentru a activa \n" "dispozitive de teleportare in zonele bonus." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "Cașcavalul este verde marți." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig este regele clown." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Pentru a o învinge pe regină \n" "împușc-o până cade de pe marginea platformei." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Bună treabă! \n" "Acum du-te și adu-l pe Devan Shell!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Pentru a trece prin aceste\n" "blocuri, apasă în jos și sari!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Apasă în jos și sari sub\n" "aceste blocuri pentru a le sparge!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Sparge cu șezutul cutia de metal \n" "pentru a deschide cuburile cu chei!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Mişcă-ţi fundul spre iesire." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Această trambulină este înghețată." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Un secret foarte mare." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Scuturile îți vor oferi muniție nelimitată \n" "specială pentru o perioadă scurtă de timp." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Cronometrele iți vor adăuga timp la\n" "durata de viață a unui scut." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Acest schwartzenguard este prăjit!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Nu mă poți învinge, Jazz!\n" "Pregătește-te să înfrunți super robotul meu!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Ack! Am plecat de aici!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Aceste cuburi sunt cuburi de viteză.\n" "Alergă în ele cu toată viteza!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Bine ai venit la Jazz Jackrabbit 2. \n" " Acesta este un nivel de antrenament." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Colectează bunătățuri pentru\n" "puncte și surprize." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "După ce ai sărit, apasă pe butonul de sărit\n" "din nou pentru a face o mișcare specială." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Unii pereți pot fi împușcați." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Când ești în aer, apasă în\n" " jos pentru a sparge cu fundul." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Secretele sunt abundente în Jazz 2. \n" " Verifică pereții." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Bună treabă. Nu uita să\n" "să cauți după secrete." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Colectează pietre prețioase pentru \n" "o viață în plus." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Pietrele prețioase roșii se consideră\n" "ca o singură piatră prețioasă." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Pietrele prețioase verzi se consideră\n" "ca cinci pietre prețioase." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Pietrele prețioase albastre se consideră \n" "ca zece pietre prețioase." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Morcovii îți dau viața." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Punctele de salvare iți salvează\n" "progresul în cazul în care pierzi o viață." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Colectează monede pentru \n" "a debloca camerele bonus." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Atentie la lucrurile ascuțite.\n" "Acestea dor." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Acum ești pregătit să joci.\n" " Mult noroc și distracție plăcută." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Sparge cu șezutul capacul de canalizare!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Inelele de fum\n" "te amețesc." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Nu poți să le spargi cu șezutul\n" "așa că mergi în sus și ocoleștele!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Nu există recompense pentru cei\n" "care au mâncărimi pe trăgaci." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Previziuni pentru astăzi: Vânturi puternice!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Găsește cutia pentru\n" "a iți elibera calea." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Bine ai venit la\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Doar Spaz poate ajunge la\n" "camera sus în stânga.\n" "S-ar putea să aibă nevoie de ceva\n" "pe care să stea." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Nu-l bate pe Nigel la biliard.\n" "Ai fost avertizat. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Mâncatul prea multor ouă de \n" "ciocolată te poate îmbolnăvi :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Intrarea înclinată a tunelului" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Pentru a accesa tunelurile de mai sus\n" "găsește zona de teleportare." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "O cale duce la bogăție.\n" "O cale duce la luptă." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Doar cei care pot sări de două ori\n" "pot ajunge la bunătăți!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Găsește cutia pentru a face\n" "cuburile de cățărare să apară" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Săritul cu fundul pe această cutie\n" "eliberează și niște inamici :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Intră în casă cu precauție....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Cutiile argintii nu pot fi sparte sub apă..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Nivelul apei controlează cutia de deasupra." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Săritul cu fundul pe o cutie poate fi bun și rău..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Dar ai nevoie de o modalitate de a ajunge acolo sus..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Te voi iubi mereu și pentru totdeauna :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Du-te pe acoperișuri!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Cerul de sus îi va răsplăti pe cei\n" "care vor sări cu fundul..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Sari cât mai departe spre\n" "dreapta pe cât de mult poți..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Nu ai făcut saltul, nu-i așa? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Bine lucrat!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Folosește-ți mișcările speciale pentru a te ridica în curenții de aer!\n" "Pentru Jazz, apasă în jos și sări.\n" "Pentru Spaz, sări de două ori!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Găsește cutia și pietrele prețioase sunt ale tale!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Trambulinele nu funcționează când sunt înghețate..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "Să colectezi 20 de monede este mult mai răsplătitor..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Găsește cutia pentru a elimina cuburile...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "ATENȚIE! Stolurile de corbi pot\n" "fi foarte periculoase." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Pentru a îndepărta cuburile\n" "uită-te la cea mai înaltă clădire." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Alege un blocaj și dă-i bătaie!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Salut GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "Intră pe www.project2.com\n" "utilizează\n" "parola: BUNNYLOVER\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Trage în aceste cuburi!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Când ești în aer, apasă în\n" "jos pentru a lovi cu fundul." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Pentru a trece de această zonă, lovește\n" "cu fundul cutia de metal secretă." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Ai nevoie de 20 de monede pentru a trece \n" "prin această teleportare secretă!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Monedele îți oferă acces la zonele\n" "de teleportare care apar mai târziu." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Lovește cu fundul locul potrivit și\n" "s-ar putea să găsești o surpriză!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Unele cutii conțin\n" "bombe sau lucruri rele!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Ai nevoie de 20 de monede pentru a trece \n" "prin această teleportare secretă!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Un aruncător de flăcări funcționează\n" "bine împotriva insectelor enervante." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Inelele de fum te\n" "amețesc foarte rau!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Ai grijă la vrăjitoare! Ea te poate\n" "transforma într-o broască." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Dacă ești transformat într-o broască\n" "Eva Urechi-Lungi te poate ajuta!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Ai reușit! Ăsta este sfârșitul \n" "versiunii shareware.\n" "Acum verifică informațiile de comandă\n" "pentru MAI MULTE!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Atenție la țepușele de mai jos!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Poți sparge cu șezutul prin\n" "unele din punctele slabe\n" "din drumuri!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Unele cuburi pot fi\n" "sparte doar cu o\n" "anumită armă." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Bine ai venit la Christmas Chronicles!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Salutări de sezon de la\n" "Epic MegaGames\n" "Orange Games și\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Poți să stai în vârful\n" "unora dintre copaci!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Poți sparge cu șezutul prin\n" "unele din punctele slabe\n" "din căile tale!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Lovirea cuburilor de deasupra ta\n" "poate fi răsplătitoare." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Cutia de intrare în\n" "traseul pietrelor prețioase." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Intrarea în traseul pietrelor prețioase.\n" "Urcă-te în vârful copacilor pentru a găsi\n" "și lovi cu fundul cutia de intrare." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Bună, Piggy!\n" "\n" "- Poopy" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Mi-ai schimbat viața în atât de multe feluri.\n" "Îți dedic ție acest proiect.\n" "Te iubesc foarte mult." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Am un milion de lucruri pe care doresc să ți le spun\n" "Dar cuvintele mele duc mereu la același sfârșit\n" "Te iubesc, te iubesc." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Te rog să îți folosești dinamita\n" " cu înțelepciune." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Nu-ți pierde aderența!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Bine ați venit în \"Burrowsville\"\n" "Vă rugăm să conduceți cu atenție." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "Podul acela nu\n" "nu pare prea sigur..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert și Craig:\n" "Trambulinele sunt cele mai mișto! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Acum părăsiți \"Burrowsville\"\n" "Vă rugăm să ne vizitați din nou." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Parolă: xmasbunny\n" "Vă rugăm să vizitați\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "Nivelul \"{}\" inițializat" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Pentru mai multe informații, vizitați site-ul oficial:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Codurile nu sunt permise în contextul actual" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Jocul va începe în scurt timp!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] a fost prăjit de \f[c:#d0705d]{}\f[/c]" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c] a fost prăjit de mediul înconjurător" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] deconectat" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] conectat" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "Câștigătorul este {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Comandă necunoscută" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "Re-Implementare a jocului \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] lansat în " "1998. Suportă diferite versiuni ale jocului (Shareware Demo, Holiday Hare " "'98, The Secret Files și Christmas Chronicles). De asemenea, suportă parțial " "unele caracteristici ale extensiei JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Dezvoltatori" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Contribuitori" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Traducatori" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Acest proiect folosește o versiune modificata a motorului " "\f[c:#9e7056]nCine\f[/c] și următoarele biblioteci:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Joacă Povestea" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Joacă Versiunea Demo" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Importează episoade" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Continuă" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Joacă online" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Punctaje" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Opțiuni" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Despre" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Ieșire" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Pentru mai multe informații, vizitați {} și  Discord!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Accesul la memoria externă a fost acordat!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]Repornește jocul pentru a citi corect fișierele " "\f[c:#9e7056]Jazz Jackrabbit 2\f[c:#337233]." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Acest joc necesită fișierele originale \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "Asigură-te că fișierele Jazz Jackrabbit 2 sunt prezente în următorul fișier:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Permite accesul la stocarea externă" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Remapați comenzile pentru jucătorul {}" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Remapați comenzile" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Comenzi Tactile" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Comutați Alergarea" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Etichete pentru butoanele gamepadului" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Vibrațiile Gamepadului" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Suport PlayStation™ extins" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Butonul Nativ de Întoarcere" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Diagnosticarea intrărilor" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Resetați la valorile implicite" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Controluri" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Puternic" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Slab" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Dezactivat" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Activat" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Caracter" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Modul de joc" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Creați un server" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Bătălie" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Bătălie în Echipă" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Cursa" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Cursa în echipă" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Vânătoare de Comori" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Vânătoare de comori în echipă" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Capturați Steagul" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Cooperare" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Nu se poate crea serverul!\f[/c]\n" "\n" "\n" "Lista de joc nu este configurată corect.\n" "Vă rugăm să verificați configurația serverului și să încercați din nou." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Nu se poate crea serverul!\f[/c]\n" "\n" "\n" "Verificați dacă niciun alt server nu rulează pe acel port și încercați din " "nou." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Joacă Niveluri Customizate" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Nu s-a găsit niciun nivel customizat!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Creați un server din lista de joc" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Creați un server privat" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Creați un server public" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Nu s-a găsit niciun episod!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Repornește episodul" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Trebuie să finalizezi mai întâi \"{}\"!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Episodul este blocat!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Jocul Original" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Vreau să joc așa cum era odată." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Jocul Reforjat" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Vreau să joc jocul cu ceva nou." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Bine ați venit la \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementare!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Gameplay" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Îmbunătățiri" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Puteți alege stilul de joc preferat.\n" "Această opțiune poate fi schimbată în orice moment în \f[c:#707070]{}\f[/c] " "> \f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "Pentru mai multe informații, vizitați {} și  Discord!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Modul de joc Reforjat" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "HUD Reforjat" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Meniul Principal Reforjat" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Cățărare pe Margini" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Roata Pentru Arme" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Poți activa îmbunătățirile care au fost adăugate la acestă refacere." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Activat cu Număr Gloanțe" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Limbă" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Scripturi" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "Săritură continuă" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Comutați la o armă nouă" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Permite Coduri" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Suprascrie sfârșitul episodului" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Suport Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "Răsfoiți directorul sursa \"Source\"" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Reîncarcă Memoria Cache" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Da" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Nu" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Doar fara coduri" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Doar scorul mai mare" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Întotdeauna" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Moduri de Redimensionare" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Rezoluție" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Ecran Complet" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Antialiasare" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Dithering de fundal" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Calitatea Apei" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Arată Urmele Jucătorilor" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Preferat ecranul imparțit" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Preferat ecran micșorat" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Păstrează Raportul de Aspect în Cinematice" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Vizualizare nealiniată" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Indicatori de Performanță" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafică" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Scăzută" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Înaltă" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Vertical" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Orizontal" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Activat \f[c:#d0705d](Experimental)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Scurt" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detaliat" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "Punctaje pentru \f[c:#d0705d]Jocul de bază\f[/c]" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "Punctaje pentru \f[c:#d0705d]{}\f[/c]" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "" "Selectează fișiere din jocul original pentru a debloca episoade suplimentare" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "Procesarea fișierului {}..." msgstr[1] "Procesarea fișierelor {}..." msgstr[2] "Procesarea fișierelor {}..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "În așteptare pentru fișiere..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Nu a fost selectat nici un fișier!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Niciun episod nou nu a fost importat!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Nu sunt detectate gamepad-uri!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Selectați Modul de Joc" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Sunet" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Profil utilizator" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Reluare activitate" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "Spectator" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Salvează și Ieși" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Deconectare & Ieșire" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Conectare la server" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "Procesarea fișierelor \f[c:#9e7056]\"Sursa\"\f[/c] din directorul..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Nivelurile și episoadele nou adăugate vor fi disponibile în curând." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Stânga" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Dreapta" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Sus" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Jos" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Sparge cu șezutul" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Trage" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Sări" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Fugi" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Schimbă Arma" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Înapoi" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Comutator Consolă" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Armă {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Apăsați orice tastă sau buton pentru a atribui" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "pentru a elimina atribuirea" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Nimic / Pixel-perfect" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "Linii de Scanare CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "Mască de Umbră CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "Grilă de Deschidere CRT" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Monocrom" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Alege Modul de Redimensionare" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Nu s-au găsit servere, dar încă mai caut!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Apăsați \f[c:#d0705d]Trage\f[/c] pentru a continua" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Volum Principal" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Volum SFX (Efecte)" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Volum Muzică" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Numar local de jucatori" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Dificultate" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Start" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Usor" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Mediu" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Greu" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "Poți ajusta poziția zonelor tactile prin a le trage și fixa." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Integrare cu Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Numele Jucătorului" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "ID Unic al Jucătorului" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Puncte: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Jocul începe în {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "Se așteaptă încă {} jucător" msgstr[1] "Se așteaptă încă {} jucători" msgstr[2] "Se așteaptă încă {} jucători" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Găsiți ieșirea!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nu se poate încărca nivelul specificat!\f[/c]\n" "\n" "\n" "Asigură-te că toate fișierele necesare\n" "sunt accesibile și încearcă din nou." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]Nu se poate relua starea salvată!\f[/c]\n" "\n" "\n" "Asigurați-vă că toate fișierele necesare\n" "sunt accesibile și încercați din nou." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "Server fără nume" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Parolă specificată nevalidă." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Versiunea clientului dvs. nu este compatibilă cu serverul." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Autentificarea a eșuat.\n" "Contactați administratorii serverului pentru mai multe informații." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Parolă specificată nevalidă." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Nume de jucător specificat nevalid.\n" "Te rugăm să verifici profilul și să încerci din nou." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Acest client nu este în lista albă a serverului.\n" "Contactați administratorii serverului pentru mai multe informații." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Serverul necesită furnizorul de autentificare 3rd party.\n" "Contactați administratorii serverului pentru mai multe informații." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Capacitatea serverului este completă.\n" "Vă rugăm să încercați mai târziu." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Serverul nu se află într-o stare în care să poată procesa cererea " "dumneavoastră.\n" "Vă rugăm să încercați din nou în câteva secunde." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Conexiunea a fost închisă!\f[/c]\n" "\n" "\n" "Serverul se închide.\n" "Vă rugăm să încercați mai târziu." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Conexiunea a fost închisă!\f[/c]\n" "\n" "\n" "Serverul se oprește pentru întreținere.\n" "Vă rugăm să încercați mai târziu." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Conexiunea a fost închisă!\f[/c]\n" "\n" "\n" "Serverul se oprește pentru reconfigurare.\n" "Vă rugăm să încercați mai târziu." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]Conexiunea a fost închisă!\f[/c]\n" "\n" "\n" "Serverul se oprește pentru actualizare.\n" "Vă rugăm să verificați versiunea clientului dvs. și să încercați din nou " "într-un minut." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]Conexiunea a fost pierdută!\f[/c]\n" "\n" "\n" "Vă rugăm să încercați din nou și dacă problema persistă,\n" "verificați conexiunea de rețea." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Serverul nu răspunde la cererea de conectare." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Conexiunea a fost închisă!\f[/c]\n" "\n" "\n" "Ați fost \f[c:#90705050]dat afară\f[/c] de pe server.\n" "Contactați administratorii serverului pentru mai multe informații." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Conexiunea a fost închisă!\f[/c]\n" "\n" "\n" "Ați fost \f[c:#725040]banat\f[/c] de pe server.\n" "Contactați administratorii serverului pentru mai multe informații." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\fConexiunea a fost închisă!\f[/c]\n" "\n" "\n" "S-a detectat trișare." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Clientul dvs. nu conține resursele necesare.\n" "Vă rugăm să descărcați fișierele necesare și să încercați din nou." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]Conexiunea a fost întreruptă!\f[/c]\n" "\n" "\n" "Serverul v-a deconectat din cauza inactivității." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Nu se poate conecta la server!\f[/c]\n" "\n" "\n" "Clientul dvs. nu conține nivelul \"{}\".\n" "Vă rugăm să descărcați fișierele necesare și să încercați din nou." #~ msgid "Unknown" #~ msgstr "Necunoscut" #~ msgid "Play Custom Game" #~ msgstr "Joacă un joc personalizat" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "Conectare la {}:{}..." #~ msgid "Create Custom Server" #~ msgstr "Creați un Server Personalizat" #~ msgid "Connect to Server" #~ msgstr "Conectare la server" #~ msgid "or" #~ msgstr "sau" #~ msgid "Creating server..." #~ msgstr "Crearea serverului..." #~ msgid "Error" #~ msgstr "Eroare" deathkiller-jazz2-native-2a7ccef/Content/Translations/ru.mo000066400000000000000000000672541512772601700242040ustar00rootroot00000000000000d  _nAX , < ]j o }  1 !,5>CSXx@   7New =OT dr v   @!'69 N<[;( ,<.i&+)(#>,b*&A>#%b(69"&B<iA'D(U+~@/)6EL|?F ,P)}SF(B2k(LN Ec 0 , (!>0!4o!0!9!7".G"Av"*"<"' #IH#D#.#3$D:$6$C$d$1_%C%<%F&IY&+&E&F'J\'C':'6&(@](;(5(D).U)F)()@)}5**:*8 +3D+Ax+0+8+A$, f,E,C,$-66-Hm-A-E-I>.?.6.I.@I/D/r/^B0F00011QK1+11vS2Z2'%3@M3Q3;3&4=C4B4,4344%5@Z5)57`8{09 9M9<:E: \:g::::::: ::;&#; J;RU;%;;; ;; <>&<e<"n< <<8= V=c=z=4=]=)->5W>(>=>)>?41?#f?%?(?J? $@/@?@KW@A#AA AB#B"+BNB.]BpBB C'C 9C DC&eCoC}C%zD/DD+D<E&UE<|E-E9E3!F:UFJF?FfGDGG#GFHQLH'H;HHIRKI IvI+6J3bJJ+&K*RKh}KVKb=LVL<L,4MaM>M$6N2[N NuNa%OyOOP:QP8PfPl,QkQ`RTfR=R;R=5SJsSRShT^zT1TE UiQUHUpVuVA6W]xWrWoIXdX*Y_IYXYZ{ZP [/[[Q[m[SK\>\+\@ ]:K][]]^@^8_WA_n_8`VA`s`# ao0aoa)bO:bwbqc^tcnc`BdEdndhXeme/fefPg2gTh{]h&hiijPGkYk{kZnl"lGlC4mIxm:m@mm>n9aSM0H"KuLT ^%4toy\Qx5N)_p7VvARnq@}[3BX;s#k'YiDI{.U8|$Jc /m wld, e2 Er&G=!WzPC]j?>~6`b+*< fO1hgFZ:-( [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files!AboutAccess to external storage has been granted!Allow access to external storageAntialiasingBackChange WeaponCharacterControlsDifficultyDisabledDiscord IntegrationDownEasyEnabledEnhancementsEpisode is locked!FireFor more information, visit the official website:FullscreenGameplayGraphicsHardImport EpisodesJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.No custom level found!No episode found!No files were selected!No new episodes were imported!None / Pixel-perfectOptionsPerformance MetricsPlay Custom LevelsPlay Shareware DemoPlay StoryProcessing of files in [c:#9e7056]"Source" [/c] directory...QuitRazer Chroma™Refresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Rescale ModeRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Rescale ModeSelect files of your original game to unlock additional episodesSoundsStartTouch ControlsUpWaiting for files...Weapon WheelYou can adjust position of the touch zones by drag and drop.You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!xmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: MAN-biker Language: ru_RU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp [c:#337233]ПЕРЕЗАПУСТИТЕ ИГРУ [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] ДЛЯ КОРРЕКТНОГО СЧИТЫВАНИЯ ФАЙЛОВ. [c:#704a4a]НЕ МОГУ ЗАГРУЗИТЬ ВЫБРАННЫЙ УРОВЕНЬ! [/c] УБЕДИТЕСЬ, ЧТО ВСЕ НЕОБХОДИМЫЕ ФАЙЛЫ ДОСТУПНЫ И ПОПРОБУЙТЕ СНОВА. [c:#704a4a]ИГРА ТРЕБУЕТ ФАЙЛЫ ОРИГИНАЛЬНОЙ ИГРЫ [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a]!ОБ ИГРЕПРЕДОСТАВЛЕН ДОСТУП К ВНЕШНЕМУ ХРАНИЛИЩУ!ДАТЬ ДОСТУП К ВНЕШНЕМУ ХРАНИЛИЩУСГЛАЖИВАНИЕНАЗАДСМЕНА ОРУЖИЯПЕРСОНАЖУПРАВЛЕНИЕСЛОЖНОСТЬВЫКЛЮЧЕНОИНТЕГРАЦИЯ С DiscordВНИЗЛЕГКОВКЛЮЧЕНОУЛУЧШЕНИЯЭПИЗОД ЗАБЛОКИРОВАН!ОГОНЬПОЛНАЯ ИНФОРМАЦИЯ ЕСТЬ НА ОФИЦИАЛЬНОМ САЙТЕ:ПОЛНОЭКРАННЫЙ РЕЖИМОСНОВНЫЕГРАФИКАСЛОЖНОИМПОРТ ЭПИЗОДОВПРЫЖОКСОХРАНЯТЬ ПРОПОРЦИИ ВИДЕОРОЛИКОВЯЗЫКХВАТАНИЕ ЗА УСТУПЫВЛЕВОУБЕДИТЕСЬ, ЧТО ФАЙЛЫ ОРИГИНАЛЬНОЙ КОПИИ ИГРЫ Jazz Jackrabbit 2 НАХОДЯТСЯ ПО СЛЕДУЮЩЕМУ ПУТИ:ОБЩАЯ ГРОМКОСТЬСРЕДНЕМОНОХРОМНОЕГРОМКОСТЬ МУЗЫКИОРИГИНАЛЬНАЯ КНОПКА «НАЗАД»ДОБАВЛЕННЫЕ УРОВНИ И ЭПИЗОДЫ СКОРО БУДУТ ДОСТУПНЫ.ДОП. УРОВНИ НЕ НАЙДЕНЫ!НЕ НАЙДЕНО НИ ОДНОГО ЭПИЗОДА!ФАЙЛЫ НЕ БЫЛИ ВЫБРАНЫ!НЕ БЫЛО ДОБАВЛЕНО НОВЫХ ЭПИЗОДОВ!НЕТ / ПИКСЕЛЬ В ПИКСЕЛЬНАСТРОЙКИОТОБРАЖЕНИЕ СЧЁТЧИКА КАДРОВИГРАТЬ [ДОП. УРОВНИ]ИГРАТЬ В ДЕМО-ВЕРСИЮИГРАТЬ [ОСНОВНАЯ ИГРА]ОБРАБОТКА ФАЙЛОВ В ПАПКЕ [c:#9e7056]"Source" [/c]...ВЫХОДRazer Chroma™ОБНОВИТЬ КЕШПЕРЕИЗДАНИЕ ИГРЫ [c:#9e7056]Jazz Jackrabbit 2 [/c], ВЫПУЩЕННОЙ В 1998. ПОДДЕРЖИВАЮТСЯ РАЗЛИЧНЫЕ ВЕРСИИ ИГРЫ (Shareware Demo, Holiday Hare '98, The Secret Files, Christmas Chronicles). ТАКЖЕ ЧАСТИЧНО ПОДДЕРЖИВАЕТЮТСЯ РАСШИРЕНИЯ JJ2+.ТИП ШЕЙДЕРАПЕРЕЗАПУСК ЭПИЗОДАПРОДОЛЖИТЬВПРАВОБЕГГРОМКОСТЬ ЭФФЕКТОВСОХРАНЕНИЕ И ВЫХОДСКРИПТЫВЫБОР ШЕЙДЕРНОГО ЭФФЕКТАВЫБЕРИТЕ ФАЙЛЫ ОРИГИНАЛЬНОЙ ИГРЫ ДЛЯ РАЗБЛОКИРОВКИ ЭПИЗОДОВЗВУКСТАРТСЕНСОРНОЕ УПРАВЛЕНИЕВВЕРХОЖИДАНИЕ ФАЙЛОВ...КОЛЕСО ВЫБОРА ОРУЖИЯПЕРЕТАСКИВАЯ ЗОНЫ КАСАНИЯ, ВЫ МОЖЕТЕ НАСТРОИТЬ ИХ ПОЛОЖЕНИЕ.ВЫ МОЖЕТЕ ВКЛЮЧИТЬ УЛУЧШЕНИЯ, КОТОРЫЕ МЫ ДОБАВИЛИ В ЭТО ПЕРЕИЗДАНИЕ.СНАЧАЛА ПРОЙДИТЕ "{}"! ДРАКОНЫ ЖИВУТ В БУРБАНКЕ. НАЙДИ СУСЛИКА. МАРК НОСИТ ТРУСЫ. ХА-ХА! НИК ПО ПРЕЖНЕМУ ЛЮБИТ БЛЕСТЯШКИ! СПЭЗ СОЖРАЛ ДОПФИША. ОСТЕРЕГАЙТЕСЬ БЕНЗОПИЛЫ ШМАЛЬЦ! МАРКУ БУРРИТО НЕ ДАВАТЬ! ОТПРАВЬТЕ НАЙДЖЕЛУ ГРИН-КАРТУ. ОТПРАВЬТЕ ТИМУ НОВЫЕ НОСКИ. ОСТЕРЕГАЙТЕСЬ ПАДАЮЩИХ ВРАГОВ. КРЕЙГ КАК БЫЛ ДУРАЧКОМ, ТАК ИМ И ОСТАЛСЯ! НАСТАЛО ВРЕМЯ СЕКРЕТНОГО УРОВНЯ!!! ОБРУШТЕСЬ НА СЕРЕБРЯНЫЙ ЯЩИК, ЧТОБЫ ОЧИСТИТЬ СЕБЕ ПУТЬ. ЯЩИКИ ТОЖЕ МОГУТ СКРЫВАТЬ ПЛАТФОРМЫ. А ЛЕХ - КЕМПЕР РАЗМРОЗЬ ПРУЖИНУ... ОГНЕМЁТ ОТЛИЧНО СПРАВЛЯЕТСЯ С ЖУКАМИ. ПАДАЮЩИЕ КАМНИ МОГУТ СОЗДАТЬ ВАМ ГОЛОВНЯКА. СПОКОЙНОЙ НОЧИ, БУБА! ДА ЗДРАВСТВУЕТ ЛЕДЯНОЙ УРОВЕНЬ! ЧО ЗА НАФИГ? АААА! НЕТ! ЭТО ЕЩЁ НЕ КОНЕЦ?! СОБИРАЙТЕ МОНЕТКИ ДЛЯ АКТИВАЦИИ ТЕЛЕПОРТОВ. ЗДЕСЬ НИЧЕГО НЕТ. ПОПАДАЯ НА ШЕСТЫ, ВЫ РАСКРУЧИВАЕТЕСЬ, ЧТО ПРИДАЁТ ВАМ УСКОРЕНИЕ. ПОТАЙНАЯ СОКРОВИЩНИЦА. ВЫ НАШЛИ СЕКРЕТНУЮ ОБЛАСТЬ. ОБРУШЬТЕСЬ НА ЯЩИК, ЧТОБЫ СЛОМАТЬ ЕГО. ЭТО ОТКРОЕТ БЛОКИ С ЗАМОЧНОЙ СКВАЖИНОЙ! ВО ВТОРНИК СЫР ЗЕЛЕНЫЙ. КРЕЙГ - КОРОЛЬ ДУРАКОВ. ОТЛИЧНАЯ РАБОТА! А ТЕПЕРЬ ОТПРАВЛЯЙТЕСЬ ЗА ДЕВАН ШЕЛЛОМ! НАЖМИТЕ ВНИЗ И ПРЫЖОК, ЧТОБЫ РАЗБИТЬ ЭТИ БЛОКИ! ДЛЯ ПОБЕДЫ НАД КОРОЛЕВОЙ СБЕЙ ЕЁ ВЫСТРЕЛАМИ С УСТУПА. ЧТОБЫ РАЗБИТЬ ЭТИ БЛОКИ, НАЖМИТЕ ВНИЗ И ПРЫЖОК! ДЛЯ ВЫХОДА РАСТОПЧИ СВОЮ ДОБЫЧУ. ЭТА ПРУЖИНА ЗАМОРОЖЕНА. ЩИТЫ НА НЕКОТОРОЕ ВРЕМЯ ДАДУТ ВАМ НЕОГРАНИЧЕННОЕ КОЛИЧЕСТВО ОСОБЫХ БОЕПРИПАСОВ. ЧАСЫ ПРОДЛЯТ ВРЕМЯ ДЕЙСТВИЯ ЩИТА. СУПЕР-ПУПЕР СЕКРЕТ. ЭТОТ ШВАРЦЕНГАРД ПОДЖАРЕН! БЛИН! Я СВАЛИВАЮ! ТЫ НЕ ПОБЕДИШЬ МЕНЯ, ДЖАЗ! ГОТОВЬСЯ К ВСТРЕЧЕ С МОИМ СУПЕРБОТОМ! ЭТО БЛОКИ УСКОРЕНИЯ. БЕГИТЕ К НИМ НА ПОЛНОЙ СКОРОСТИ! ПОСЛЕ ПРЫЖКА НАЖМИТЕ ПРЫЖОК СНОВА, ЧТОБЫ СДЕЛАТЬ ОСОБОЕ ДВИЖЕНИЕ. ОСТЕРЕГАЙТЕСЬ ОСТРЫХ ПРЕДМЕТОВ, ОНИ РАНЯТ. СИНИЕ САМОЦВЕТЫ СЧИТАЮТСЯ ЗА 10. МОРКОВКИ ВОСПОЛНЯЮТ ЗДОРОВЬЕ. ВЫ ОКАЖЕТЕСЬ НА МЕСТЕ СОХРАНЕНИЯ, ЕСЛИ ПОТЕРЯЕТЕ ЖИЗНЬ. СОБИРАЙТЕ МОНЕТКИ, ЧТОБЫ РАЗБЛОКИРОВАТЬ БОНУСНЫЕ КОМНАТЫ. СОБИРАЙТЕ САМОЦВЕТЫ ДЛЯ ПОЛУЧЕНИЯ ДОПОЛНИТЕЛЬНЫХ ЖИЗНЕЙ. СОБИРАЙТЕ НИШТЯКИ ДЛЯ НАКОПЛЕНИЯ ОЧКОВ И СЮРПРИЗОВ. ОТЛИЧНАЯ РАБОТА. НЕ ЗАБЫВАЙТЕ ИСКАТЬ СЕКРЕТЫ. ЗЕЛЁНЫЕ САМОЦВЕТЫ СЧИТАЮТСЯ ЗА 5. ТЕПЕРЬ ВЫ ГОТОВЫ. ПРИЯТНОЙ ИГРЫ. КРАСНЫЕ САМОЦВЕТЫ СЧИТАЮТСЯ ЗА 1. В ИГРЕ ПОЛНО СЕКРЕТОВ. ПРОВЕРЯЙТЕ СТЕНЫ. НЕКОТОРЫЕ СТЕНЫ МОЖНО РАЗРУШИТЬ ВЫСТРЕЛАМИ. ДОБРО ПОЖАЛОВАТЬ В Jazz Jackrabbit 2. ЭТО ТРЕНИРОВОЧНЫЙ УРОВЕНЬ. НАХОДЯСЬ В ВОЗДУХЕ, НАЖМИТЕ ВНИЗ, ЧТОБЫ ОБРУШИТЬСЯ. ОБРУШЬТЕСЬ НА КРЫШКУ ЛЮКА! КОЛЬЦА ДЫМА ВЫЗЫВАЮТ ГОЛОВОКРУЖЕНИЕ. НЕ ОБЫГРЫВАЙТЕ НАЙДЖЕЛА В БИЛЬЯРД. МЫ ВАС ПРЕДУПРЕДИЛИ. :) НАЙДИТЕ ЯЩИК, ЧТОБЫ ОЧИСТИТЬ СЕБЕ ПУТЬ. НИКАКИХ НАГРАД ТЕМ, У КОГО ДРОЖАТ ПАЛЬЦЫ НА СПУСКОВОМ КРЮЧКЕ. ТОЛЬКО СПАЗ МОЖЕТ ДОБРАТЬСЯ ДО ВЕРХНЕЙ КОМНАТЫ СЛЕВА. ЕМУ МОЖЕТ ПОНАДОБИТЬСЯ НА ЧТО МОЖНО ВСТАТЬ ПОВЫШЕ. ПО ПРОГНОЗУ, СЕГОДНЯ СИЛЬНЫЙ ВЕТЕР! ДОБРО ПОЖАЛОВАТЬ В Jazz Jackrabbit 2: СЕКРЕТНЫЕ МАТЕРИАЛЫ! ВЫ НЕ В СОССТОЯНИИ ТУТ ОБРУШИТЬСЯ ТАК ЧТО ОБХОДИТЕ ЧЕРЕЗ ВЕРХ!ЕСЛИ ЕСТЬ СЛИШКОМ МНОГО ШОКОЛАДНЫХ ЯИЦ, ТО МОЖНО И ЗАБОЛЕТЬ :p ОДИН ПУТЬ ВЕДЕТ К БОГАТСТВУ. ДРУГОЙ ПУТЬ ВЕДЕТ К БИТВЕ. ВХОД В ПОЛОГИЙ ТУННЕЛЬ ДЛЯ ДОСТУП К ТУННЕЛЯМ ВЫШЕ НАЙДИТЕ ТОЧКУ ТЕЛЕПОРТА. НАЙДИТЕ ЯЩИК, ЧТОБЫ ПОЯВИЛИСЬ БЛОКИ ДЛЯ ЛАЗАНИЯ ТОЛЬКО ТОТ, КТО МОЖЕТ СДЕЛАТЬ ДВОЙНОЙ ПРЫЖОК СМОЖЕТ ДОБРАТЬСЯ ДО ВКУСНЯШЕК! РАЗРУШЕНИЕ ЭТОГО ЯЩИКА ПРИВЕДЁТ К ОСВОБОЖДЕНИЮ НЕКОТОРЫХ ВРАГОВ :) НО ТЕБЕ НУЖНО НАЙТИ СПОСОБ ПОДНЯТЬСЯ ТУДА... ВХОДИТЕ В ДОМ ОСТОРОЖНО... СЕРЕБРЯНЫЕ ЯЩИКИ НЕЛЬЗЯ РАЗБИТЬ ПОД ВОДОЙ... РАЗРУШЕНИЕ ЯЩИКОВ МОЖЕТ БЫТЬ КАК ПОЛЕЗНО, ТАК И НАВРЕДИТЬ... ЯЩИК УПРАВЛЕНИЯ УРОВНЕМ ВОДЫ НАХОДИТСЯ ВЫШЕ. МИШЕЛЬ, Я БУДУ ЛЮБИТЬ ТЕБЯ ВЕЧНО :) ЧТО, НЕ СМОГ ПРЫГНУТЬ? :) ПРЫГНИТЕ КАК МОЖНО ДАЛЬШЕ ВПРАВО... ЗАБЕРИТЕСЬ НА САМЫЙ ВЕРХ КРЫШИ! НЕБЕСА ВЫШЕ ВОЗНАГРАДЯТ ТЕХ, КТО ОБРУШИТСЯ ВНИЗ... ИСПОЛЬЗУЙТЕ СВОИ СПЕЦИАЛЬНЫЕ ПРИЕМЫ! ДЛЯ ДЖАЗА НАЖМИТЕ «ВНИЗ И ПРЫЖОК». ДЛЯ СПЭЗА ДВАЖДЫ НАЖМИТЕ «ПРЫЖОК»! ОТЛИЧНАЯ РАБОТА! СОБРАТЬ 20 МОНЕТ НАМНОГО ВЫГОДНЕЕ... НАЙДИТЕ ЯЩИК И САМОЦВЕТЫ ВАШИ! ЗАМЁРЗШИЕ ПРУЖИНЫ ПЕРЕСТАЮТ ФУНКЦИОНИРОВАТЬ... ОСТЕРЕГАЙТЕСЬ ВОРОНЬИХ СТАЙ, ОНИ МОГУТ БЫТЬ ОЧЕНЬ ОПАСНЫМИ. ВЫБИРАЙ УКРЫТИЕ И ТОПАЙ ПРОЧЬ! НАЙДИТЕ ЯЩИК ЧТОБЫ РАСЧИСТИТЬ ПУТЬ ОТ БЛОКОВ... ОТПРАВЛЯЙТЕСЬ НА САЙТ www.project2.com И ИСПОЛЬЗУЙТЕ ПАРОЛЬ: «BUNNYLOVER» ПРИВЕТ ГЕОКРОЛИК :) ЧТОБЫ РАСЧИСТИТЬ БЛОКИ ВЗГЛЯНИТЕ НА САМОЕ ВЫСОКОЕ СТРОЕНИЕ. МОНЕТЫ ДАЮТ ВАМ ДОСТУП К ПОРТАЛАМ, КОТОРЫЕ ПОЯВЛЯЮТСЯ ПОЗЖЕ. СТРЕЛЯЙТЕ В ЭТИ БЛОКИ! НЕКОТОРЫЕ ЯЩИКИ СОДЕРЖАТ БОМБЫ ИЛИ ВРАГОВ! ОБРУШИВШИСЬ СВЕРХУ В НУЖНОМ МЕСТЕ, ВЫ МОЖЕТЕ ОБНАРУЖИТЬ СЮРПРИЗ! ЧТОБЫ ПРОЙТИ ЭТУ ОБЛАСТЬ, ОБРУШТЕСЬ СВЕРХУ НА СЕКРЕТНЫЙ ЯЩИК. НАХОДЯСЬ В ВОЗДУХЕ, НАЖМИТЕ ВНИЗ, ЧТОБЫ ОБРУШИТЬСЯ. ВАМ НУЖНО 20 МОНЕТ, ЧТОБЫ ПРОЙТИ ЧЕРЕЗ ЭТОТ СЕКРЕТНЫЙ ПОРТАЛ! ОГНЁМЕТ ОЧЕНЬ ЭФФЕКТИВЕН ПРОТИВ ЭТИХ МЕРЗКИХ ЖУКОВ. КОЛЬЦА ДЫМА ВЫЗЫВАЮТ ГОЛОВОКРУЖЕНИЕ! ВАМ НУЖНО 20 МОНЕТ, ЧТОБЫ ПРОЙТИ ЧЕРЕЗ ЭТОТ СЕКРЕТНЫЙ ПОРТАЛ! ОСТОРЕГАЙСЯ ВЕДЬМЫ! ОНА МОЖЕТ ПРЕВРАТИТЬ ТЕБЯ В ЛЯГУШКУ. ЕСЛИ ВЫ ПРЕВРАТИЛИСЬ В ЛЯГУШКУ, ЕВА ДЛИННОУХ СМОЖЕТ ПОМОЧЬ! ВЫ СМОГЛИ ПРОЙТИ ДЕМО-ВЕРСИЮ ИГРЫ! А ТЕПЕРЬ ОЗНАКОМЬТЕСЬ С ИНФОРМАЦИЕЙ, КАК ПРИОБРЕСТИ ПОЛНУЮ ВЕРСИЮ! СЕЗОННЫЕ ПОЗДРАВЛЕНИЯ ОТ Epic MegaGames Orange Games И Project 2 Interactive! НЕКОТОРЫЕ БЛОКИ МОГУТ БЫТЬ РАЗРУШЕНЫ ТОЛЬКО ОПРЕДЕЛЕННЫМ ТИПОМ ОРУЖИЯ. ОСТЕРЕГАЙТЕСЬ ШИПОВ ВНИЗУ! ДОБРО ПОЖАЛОВАТЬ В «РОЖДЕСТВЕНСКИЕ ХРОНИКИ»! ВЫ МОЖЕТЕ ЛОМАТЬ НЕКОТОРЫЕ ПРЕПЯТСТВИЯ, ОБРУШИВАЯСЬ НА НИХ СВЕРХУ! ПРИВЕТ, ПИГГИ! - ПУПИ КАССИ НИКОЛЬ: МНЕ ТАК МНОГО ХОЧЕТСЯ ТЕБЕ СКАЗАТЬ. НО ВСЕ СЛОВА СВОДЯТСЯ К ОДНОМУ: Я ЛЮБЛЮ ТЕБЯ, Я ТАК ТЕБЯ ЛЮБЛЮ! МИШЕЛЬ, ТЫ ИЗМЕНИЛА МОЮ ЖИЗНЬ ВО ВСЕХ ОТНОШЕНИЯХ, ПОЭТОМУ ДАННЫЙ ПРОЕКТ Я ПОСВЯЩАЮ ТЕБЕ. Я ТАК СИЛЬНО ТЕБЯ ЛЮБЛЮ! ВХОД НА ТРОПУ САМОЦВЕТОВ. ЗАБЕРИТЕСЬ НА МАКУШКИ ДЕРЕВЬЕВ, ЧТОБЫ НАЙТИ ЯЩИК, ОТКРЫВАЮЩИЙ ВХОД. ЯЩИК, СКРЫВАЮЩИЙ ПРОХОД К ТРОПЕ САМОЦВЕТОВ. ВЫ МОЖЕТЕ ПОЛУЧИТЬ НАГРАДУ, РАЗРУШИВ БЛОКИ ВЫШЕ, ВЫ МОЖЕТЕ ЛОМАТЬ НЕКОТОРЫЕ ПРЕПЯТСТВИЯ, ОБРУШИВАЯСЬ НА НИХ СВЕРХУ. ВЫ МОЖЕТЕ СТОЯТЬ НА МАКУШКАХ НЕКОТОРЫХ ДЕРЕВЬЕВ! НЕ ТЕРЯЙТЕ ХВАТКУ! ПОКИДАЕМ БЕРРОУСВИЛЛ ПРИХОДИТЕ СНОВА. ПАРОЛЬ: xmasbunny ПОСЕТИТЕ САЙТ www.project2.com ИСПОЛЬЗУЙТЕ ДИНАМИТ С УМОМ, ПОЖАЛУЙСТА. РОБЕРТ И КРЕЙГ: ПРУЖИНЫ РУЛЯТ! :) ЭТОТ МОСТ НЕ ВЫГЛЯДИТ БЕЗОПАСНЫМ... ДОБРО ПОЖАЛОВАТЬ В БЕРРОУСВИЛЛ ПОЖАЛУЙСТА, СОБЛЮДАЙТЕ ПДД.deathkiller-jazz2-native-2a7ccef/Content/Translations/ru.po000066400000000000000000002020111512772601700241650ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:59+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: MAN-biker\n" "Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " "n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "СПЭЗ СОЖРАЛ ДОПФИША." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "НАЙДИ СУСЛИКА." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "ДРАКОНЫ ЖИВУТ В БУРБАНКЕ." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "МАРК НОСИТ ТРУСЫ.\n" "ХА-ХА!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "НИК ПО ПРЕЖНЕМУ ЛЮБИТ БЛЕСТЯШКИ!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "ОТПРАВЬТЕ ТИМУ НОВЫЕ НОСКИ." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "ОТПРАВЬТЕ НАЙДЖЕЛУ ГРИН-КАРТУ." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "ОСТЕРЕГАЙТЕСЬ БЕНЗОПИЛЫ ШМАЛЬЦ!" #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "МАРКУ БУРРИТО НЕ ДАВАТЬ!" #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "ОСТЕРЕГАЙТЕСЬ ПАДАЮЩИХ ВРАГОВ." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "КРЕЙГ КАК БЫЛ ДУРАЧКОМ,\n" "ТАК ИМ И ОСТАЛСЯ!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "НАСТАЛО ВРЕМЯ СЕКРЕТНОГО УРОВНЯ!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "ОБРУШТЕСЬ НА СЕРЕБРЯНЫЙ ЯЩИК,\n" "ЧТОБЫ ОЧИСТИТЬ СЕБЕ ПУТЬ." #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "ЯЩИКИ ТОЖЕ МОГУТ СКРЫВАТЬ ПЛАТФОРМЫ." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "РАЗМРОЗЬ ПРУЖИНУ..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "А ЛЕХ - КЕМПЕР" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "ПАДАЮЩИЕ КАМНИ МОГУТ\n" "СОЗДАТЬ ВАМ ГОЛОВНЯКА." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "ОГНЕМЁТ ОТЛИЧНО\n" "СПРАВЛЯЕТСЯ С ЖУКАМИ." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "ДА ЗДРАВСТВУЕТ ЛЕДЯНОЙ УРОВЕНЬ!" #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "СПОКОЙНОЙ НОЧИ, БУБА!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "ЧО ЗА НАФИГ? АААА! НЕТ!\n" "ЭТО ЕЩЁ НЕ КОНЕЦ?!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "ПОПАДАЯ НА ШЕСТЫ, ВЫ РАСКРУЧИВАЕТЕСЬ,\n" "ЧТО ПРИДАЁТ ВАМ УСКОРЕНИЕ." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "ВЫ НАШЛИ СЕКРЕТНУЮ ОБЛАСТЬ." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "ПОТАЙНАЯ СОКРОВИЩНИЦА." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "ЗДЕСЬ НИЧЕГО НЕТ." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "СОБИРАЙТЕ МОНЕТКИ ДЛЯ\n" "АКТИВАЦИИ ТЕЛЕПОРТОВ." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "ВО ВТОРНИК СЫР ЗЕЛЕНЫЙ." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "КРЕЙГ - КОРОЛЬ ДУРАКОВ." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "ДЛЯ ПОБЕДЫ НАД КОРОЛЕВОЙ\n" "СБЕЙ ЕЁ ВЫСТРЕЛАМИ С УСТУПА." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "ОТЛИЧНАЯ РАБОТА!\n" "А ТЕПЕРЬ ОТПРАВЛЯЙТЕСЬ ЗА ДЕВАН ШЕЛЛОМ!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "ЧТОБЫ РАЗБИТЬ ЭТИ БЛОКИ,\n" "НАЖМИТЕ ВНИЗ И ПРЫЖОК!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "НАЖМИТЕ ВНИЗ И ПРЫЖОК,\n" "ЧТОБЫ РАЗБИТЬ ЭТИ БЛОКИ!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "ОБРУШЬТЕСЬ НА ЯЩИК, ЧТОБЫ СЛОМАТЬ ЕГО.\n" "ЭТО ОТКРОЕТ БЛОКИ С ЗАМОЧНОЙ СКВАЖИНОЙ!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "ДЛЯ ВЫХОДА РАСТОПЧИ СВОЮ ДОБЫЧУ." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "ЭТА ПРУЖИНА ЗАМОРОЖЕНА." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "СУПЕР-ПУПЕР СЕКРЕТ." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "ЩИТЫ НА НЕКОТОРОЕ ВРЕМЯ ДАДУТ ВАМ\n" "НЕОГРАНИЧЕННОЕ КОЛИЧЕСТВО ОСОБЫХ БОЕПРИПАСОВ." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "ЧАСЫ ПРОДЛЯТ ВРЕМЯ\n" "ДЕЙСТВИЯ ЩИТА." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "ЭТОТ ШВАРЦЕНГАРД ПОДЖАРЕН!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "ТЫ НЕ ПОБЕДИШЬ МЕНЯ, ДЖАЗ!\n" "ГОТОВЬСЯ К ВСТРЕЧЕ С МОИМ СУПЕРБОТОМ!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "БЛИН! Я СВАЛИВАЮ!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "ЭТО БЛОКИ УСКОРЕНИЯ.\n" "БЕГИТЕ К НИМ НА ПОЛНОЙ СКОРОСТИ!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "ДОБРО ПОЖАЛОВАТЬ В Jazz Jackrabbit 2.\n" "ЭТО ТРЕНИРОВОЧНЫЙ УРОВЕНЬ." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "СОБИРАЙТЕ НИШТЯКИ ДЛЯ\n" "НАКОПЛЕНИЯ ОЧКОВ И СЮРПРИЗОВ." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "ПОСЛЕ ПРЫЖКА НАЖМИТЕ ПРЫЖОК\n" "СНОВА, ЧТОБЫ СДЕЛАТЬ ОСОБОЕ ДВИЖЕНИЕ." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "НЕКОТОРЫЕ СТЕНЫ МОЖНО РАЗРУШИТЬ ВЫСТРЕЛАМИ." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "НАХОДЯСЬ В ВОЗДУХЕ, НАЖМИТЕ ВНИЗ,\n" "ЧТОБЫ ОБРУШИТЬСЯ." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "В ИГРЕ ПОЛНО СЕКРЕТОВ.\n" "ПРОВЕРЯЙТЕ СТЕНЫ." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "ОТЛИЧНАЯ РАБОТА.\n" "НЕ ЗАБЫВАЙТЕ ИСКАТЬ СЕКРЕТЫ." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "СОБИРАЙТЕ САМОЦВЕТЫ ДЛЯ\n" "ПОЛУЧЕНИЯ ДОПОЛНИТЕЛЬНЫХ ЖИЗНЕЙ." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "КРАСНЫЕ САМОЦВЕТЫ\n" "СЧИТАЮТСЯ ЗА 1." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "ЗЕЛЁНЫЕ САМОЦВЕТЫ\n" "СЧИТАЮТСЯ ЗА 5." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "СИНИЕ САМОЦВЕТЫ\n" "СЧИТАЮТСЯ ЗА 10." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "МОРКОВКИ ВОСПОЛНЯЮТ ЗДОРОВЬЕ." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "ВЫ ОКАЖЕТЕСЬ НА МЕСТЕ СОХРАНЕНИЯ,\n" "ЕСЛИ ПОТЕРЯЕТЕ ЖИЗНЬ." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "СОБИРАЙТЕ МОНЕТКИ, ЧТОБЫ\n" "РАЗБЛОКИРОВАТЬ БОНУСНЫЕ КОМНАТЫ." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "ОСТЕРЕГАЙТЕСЬ ОСТРЫХ ПРЕДМЕТОВ,\n" "ОНИ РАНЯТ." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "ТЕПЕРЬ ВЫ ГОТОВЫ.\n" "ПРИЯТНОЙ ИГРЫ." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "ОБРУШЬТЕСЬ НА КРЫШКУ ЛЮКА!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "КОЛЬЦА ДЫМА\n" "ВЫЗЫВАЮТ ГОЛОВОКРУЖЕНИЕ." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "ВЫ НЕ В СОССТОЯНИИ ТУТ ОБРУШИТЬСЯ\n" "ТАК ЧТО ОБХОДИТЕ ЧЕРЕЗ ВЕРХ!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "НИКАКИХ НАГРАД ТЕМ, У КОГО ДРОЖАТ\n" "ПАЛЬЦЫ НА СПУСКОВОМ КРЮЧКЕ." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "ПО ПРОГНОЗУ,\n" "СЕГОДНЯ СИЛЬНЫЙ ВЕТЕР!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "НАЙДИТЕ ЯЩИК, ЧТОБЫ\n" "ОЧИСТИТЬ СЕБЕ ПУТЬ." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "ДОБРО ПОЖАЛОВАТЬ В\n" "Jazz Jackrabbit 2:\n" "СЕКРЕТНЫЕ МАТЕРИАЛЫ!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "ТОЛЬКО СПАЗ МОЖЕТ ДОБРАТЬСЯ ДО\n" "ВЕРХНЕЙ КОМНАТЫ СЛЕВА.\n" "ЕМУ МОЖЕТ ПОНАДОБИТЬСЯ\n" "НА ЧТО МОЖНО ВСТАТЬ ПОВЫШЕ." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "НЕ ОБЫГРЫВАЙТЕ НАЙДЖЕЛА В БИЛЬЯРД.\n" "МЫ ВАС ПРЕДУПРЕДИЛИ. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "ЕСЛИ ЕСТЬ СЛИШКОМ МНОГО ШОКОЛАДНЫХ\n" "ЯИЦ, ТО МОЖНО И ЗАБОЛЕТЬ :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "ВХОД В ПОЛОГИЙ ТУННЕЛЬ" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "ДЛЯ ДОСТУП К ТУННЕЛЯМ ВЫШЕ\n" "НАЙДИТЕ ТОЧКУ ТЕЛЕПОРТА." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "ОДИН ПУТЬ ВЕДЕТ К БОГАТСТВУ.\n" "ДРУГОЙ ПУТЬ ВЕДЕТ К БИТВЕ." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "ТОЛЬКО ТОТ, КТО МОЖЕТ СДЕЛАТЬ ДВОЙНОЙ ПРЫЖОК\n" "СМОЖЕТ ДОБРАТЬСЯ ДО ВКУСНЯШЕК!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "НАЙДИТЕ ЯЩИК, ЧТОБЫ ПОЯВИЛИСЬ\n" "БЛОКИ ДЛЯ ЛАЗАНИЯ" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "РАЗРУШЕНИЕ ЭТОГО ЯЩИКА ПРИВЕДЁТ\n" "К ОСВОБОЖДЕНИЮ НЕКОТОРЫХ ВРАГОВ :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "ВХОДИТЕ В ДОМ ОСТОРОЖНО..." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "СЕРЕБРЯНЫЕ ЯЩИКИ НЕЛЬЗЯ РАЗБИТЬ ПОД ВОДОЙ..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "ЯЩИК УПРАВЛЕНИЯ УРОВНЕМ ВОДЫ\n" "НАХОДИТСЯ ВЫШЕ." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "РАЗРУШЕНИЕ ЯЩИКОВ МОЖЕТ БЫТЬ\n" "КАК ПОЛЕЗНО, ТАК И НАВРЕДИТЬ..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "НО ТЕБЕ НУЖНО НАЙТИ СПОСОБ ПОДНЯТЬСЯ ТУДА..." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "МИШЕЛЬ,\n" "Я БУДУ ЛЮБИТЬ ТЕБЯ ВЕЧНО :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "ЗАБЕРИТЕСЬ НА САМЫЙ ВЕРХ КРЫШИ!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "НЕБЕСА ВЫШЕ ВОЗНАГРАДЯТ ТЕХ,\n" "КТО ОБРУШИТСЯ ВНИЗ..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "ПРЫГНИТЕ КАК МОЖНО ДАЛЬШЕ ВПРАВО..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "ЧТО, НЕ СМОГ ПРЫГНУТЬ? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "ОТЛИЧНАЯ РАБОТА!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "ИСПОЛЬЗУЙТЕ СВОИ СПЕЦИАЛЬНЫЕ ПРИЕМЫ!\n" "ДЛЯ ДЖАЗА НАЖМИТЕ «ВНИЗ И ПРЫЖОК».\n" "ДЛЯ СПЭЗА ДВАЖДЫ НАЖМИТЕ «ПРЫЖОК»!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "НАЙДИТЕ ЯЩИК И САМОЦВЕТЫ ВАШИ!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "ЗАМЁРЗШИЕ ПРУЖИНЫ ПЕРЕСТАЮТ ФУНКЦИОНИРОВАТЬ..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "СОБРАТЬ 20 МОНЕТ НАМНОГО ВЫГОДНЕЕ..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "НАЙДИТЕ ЯЩИК ЧТОБЫ РАСЧИСТИТЬ ПУТЬ ОТ БЛОКОВ..." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "ОСТЕРЕГАЙТЕСЬ ВОРОНЬИХ СТАЙ,\n" "ОНИ МОГУТ БЫТЬ ОЧЕНЬ ОПАСНЫМИ." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "ЧТОБЫ РАСЧИСТИТЬ БЛОКИ\n" "ВЗГЛЯНИТЕ НА САМОЕ ВЫСОКОЕ СТРОЕНИЕ." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "ВЫБИРАЙ УКРЫТИЕ И ТОПАЙ ПРОЧЬ!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "ПРИВЕТ ГЕОКРОЛИК :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "ОТПРАВЛЯЙТЕСЬ НА САЙТ www.project2.com\n" "И ИСПОЛЬЗУЙТЕ\n" "ПАРОЛЬ: «BUNNYLOVER»\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "СТРЕЛЯЙТЕ В ЭТИ БЛОКИ!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "НАХОДЯСЬ В ВОЗДУХЕ, НАЖМИТЕ ВНИЗ,\n" "ЧТОБЫ ОБРУШИТЬСЯ." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "ЧТОБЫ ПРОЙТИ ЭТУ ОБЛАСТЬ,\n" "ОБРУШТЕСЬ СВЕРХУ НА СЕКРЕТНЫЙ ЯЩИК." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "ВАМ НУЖНО 20 МОНЕТ, ЧТОБЫ ПРОЙТИ\n" "ЧЕРЕЗ ЭТОТ СЕКРЕТНЫЙ ПОРТАЛ!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "МОНЕТЫ ДАЮТ ВАМ ДОСТУП К ПОРТАЛАМ,\n" "КОТОРЫЕ ПОЯВЛЯЮТСЯ ПОЗЖЕ." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "ОБРУШИВШИСЬ СВЕРХУ В НУЖНОМ МЕСТЕ,\n" "ВЫ МОЖЕТЕ ОБНАРУЖИТЬ СЮРПРИЗ!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "НЕКОТОРЫЕ ЯЩИКИ СОДЕРЖАТ\n" "БОМБЫ ИЛИ ВРАГОВ!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "ВАМ НУЖНО 20 МОНЕТ, ЧТОБЫ ПРОЙТИ\n" "ЧЕРЕЗ ЭТОТ СЕКРЕТНЫЙ ПОРТАЛ!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "ОГНЁМЕТ ОЧЕНЬ ЭФФЕКТИВЕН\n" "ПРОТИВ ЭТИХ МЕРЗКИХ ЖУКОВ." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "КОЛЬЦА ДЫМА\n" "ВЫЗЫВАЮТ ГОЛОВОКРУЖЕНИЕ!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "ОСТОРЕГАЙСЯ ВЕДЬМЫ!\n" "ОНА МОЖЕТ ПРЕВРАТИТЬ ТЕБЯ В ЛЯГУШКУ." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "ЕСЛИ ВЫ ПРЕВРАТИЛИСЬ В ЛЯГУШКУ,\n" "ЕВА ДЛИННОУХ СМОЖЕТ ПОМОЧЬ!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "ВЫ СМОГЛИ ПРОЙТИ\n" "ДЕМО-ВЕРСИЮ ИГРЫ!\n" "А ТЕПЕРЬ ОЗНАКОМЬТЕСЬ С ИНФОРМАЦИЕЙ,\n" "КАК ПРИОБРЕСТИ ПОЛНУЮ ВЕРСИЮ!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "ОСТЕРЕГАЙТЕСЬ ШИПОВ ВНИЗУ!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "ВЫ МОЖЕТЕ ЛОМАТЬ\n" "НЕКОТОРЫЕ ПРЕПЯТСТВИЯ,\n" "ОБРУШИВАЯСЬ НА НИХ СВЕРХУ!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "НЕКОТОРЫЕ БЛОКИ МОГУТ\n" "БЫТЬ РАЗРУШЕНЫ ТОЛЬКО\n" "ОПРЕДЕЛЕННЫМ ТИПОМ ОРУЖИЯ." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "ДОБРО ПОЖАЛОВАТЬ В\n" "«РОЖДЕСТВЕНСКИЕ ХРОНИКИ»!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "СЕЗОННЫЕ ПОЗДРАВЛЕНИЯ ОТ\n" "Epic MegaGames\n" "Orange Games И\n" "Project 2 Interactive!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "ВЫ МОЖЕТЕ СТОЯТЬ НА\n" "МАКУШКАХ НЕКОТОРЫХ ДЕРЕВЬЕВ!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "ВЫ МОЖЕТЕ ЛОМАТЬ\n" "НЕКОТОРЫЕ ПРЕПЯТСТВИЯ,\n" "ОБРУШИВАЯСЬ НА НИХ СВЕРХУ." #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "ВЫ МОЖЕТЕ ПОЛУЧИТЬ НАГРАДУ,\n" "РАЗРУШИВ БЛОКИ ВЫШЕ," #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "ЯЩИК, СКРЫВАЮЩИЙ ПРОХОД\n" "К ТРОПЕ САМОЦВЕТОВ." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "ВХОД НА ТРОПУ САМОЦВЕТОВ.\n" "ЗАБЕРИТЕСЬ НА МАКУШКИ ДЕРЕВЬЕВ,\n" "ЧТОБЫ НАЙТИ ЯЩИК, ОТКРЫВАЮЩИЙ ВХОД." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "ПРИВЕТ, ПИГГИ!\n" "\n" "- ПУПИ" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "МИШЕЛЬ,\n" "ТЫ ИЗМЕНИЛА МОЮ ЖИЗНЬ ВО ВСЕХ ОТНОШЕНИЯХ,\n" "ПОЭТОМУ ДАННЫЙ ПРОЕКТ Я ПОСВЯЩАЮ ТЕБЕ.\n" "Я ТАК СИЛЬНО ТЕБЯ ЛЮБЛЮ!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "КАССИ НИКОЛЬ:\n" "МНЕ ТАК МНОГО ХОЧЕТСЯ ТЕБЕ СКАЗАТЬ.\n" "НО ВСЕ СЛОВА СВОДЯТСЯ К ОДНОМУ:\n" "Я ЛЮБЛЮ ТЕБЯ, Я ТАК ТЕБЯ ЛЮБЛЮ!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "ИСПОЛЬЗУЙТЕ ДИНАМИТ С УМОМ, ПОЖАЛУЙСТА." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "НЕ ТЕРЯЙТЕ ХВАТКУ!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "ДОБРО ПОЖАЛОВАТЬ В БЕРРОУСВИЛЛ\n" "ПОЖАЛУЙСТА, СОБЛЮДАЙТЕ ПДД." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "ЭТОТ МОСТ НЕ ВЫГЛЯДИТ\n" "БЕЗОПАСНЫМ..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "РОБЕРТ И КРЕЙГ:\n" "ПРУЖИНЫ РУЛЯТ! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "ПОКИДАЕМ БЕРРОУСВИЛЛ\n" "ПРИХОДИТЕ СНОВА." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "ПАРОЛЬ: xmasbunny\n" "ПОСЕТИТЕ САЙТ\n" "www.project2.com" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "ПОЛНАЯ ИНФОРМАЦИЯ ЕСТЬ НА ОФИЦИАЛЬНОМ САЙТЕ:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "ПЕРЕИЗДАНИЕ ИГРЫ \f[c:#9e7056]Jazz Jackrabbit 2\f[/c], ВЫПУЩЕННОЙ В 1998. " "ПОДДЕРЖИВАЮТСЯ РАЗЛИЧНЫЕ ВЕРСИИ ИГРЫ (Shareware Demo, Holiday Hare '98, The " "Secret Files, Christmas Chronicles). ТАКЖЕ ЧАСТИЧНО ПОДДЕРЖИВАЕТЮТСЯ " "РАСШИРЕНИЯ JJ2+." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "ИГРАТЬ [ОСНОВНАЯ ИГРА]" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "ИГРАТЬ В ДЕМО-ВЕРСИЮ" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "ИМПОРТ ЭПИЗОДОВ" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "НАСТРОЙКИ" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "ОБ ИГРЕ" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "ВЫХОД" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "ПРЕДОСТАВЛЕН ДОСТУП К ВНЕШНЕМУ ХРАНИЛИЩУ!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#337233]ПЕРЕЗАПУСТИТЕ ИГРУ \f[c:#9e7056]Jazz Jackrabbit 2\f[c:#337233] " "ДЛЯ КОРРЕКТНОГО СЧИТЫВАНИЯ ФАЙЛОВ." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]ИГРА ТРЕБУЕТ ФАЙЛЫ ОРИГИНАЛЬНОЙ ИГРЫ \f[c:#9e7056]Jazz " "Jackrabbit 2\f[c:#704a4a]!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "" "УБЕДИТЕСЬ, ЧТО ФАЙЛЫ ОРИГИНАЛЬНОЙ КОПИИ ИГРЫ Jazz Jackrabbit 2 НАХОДЯТСЯ ПО " "СЛЕДУЮЩЕМУ ПУТИ:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "ДАТЬ ДОСТУП К ВНЕШНЕМУ ХРАНИЛИЩУ" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "СЕНСОРНОЕ УПРАВЛЕНИЕ" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "ОРИГИНАЛЬНАЯ КНОПКА «НАЗАД»" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "УПРАВЛЕНИЕ" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "ВЫКЛЮЧЕНО" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "ВКЛЮЧЕНО" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "ПЕРСОНАЖ" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "ИГРАТЬ [ДОП. УРОВНИ]" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "ДОП. УРОВНИ НЕ НАЙДЕНЫ!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "НЕ НАЙДЕНО НИ ОДНОГО ЭПИЗОДА!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "ПЕРЕЗАПУСК ЭПИЗОДА" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "СНАЧАЛА ПРОЙДИТЕ \"{}\"!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "ЭПИЗОД ЗАБЛОКИРОВАН!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "" #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "ОСНОВНЫЕ" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "УЛУЧШЕНИЯ" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "ХВАТАНИЕ ЗА УСТУПЫ" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "КОЛЕСО ВЫБОРА ОРУЖИЯ" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "ВЫ МОЖЕТЕ ВКЛЮЧИТЬ УЛУЧШЕНИЯ, КОТОРЫЕ МЫ ДОБАВИЛИ В ЭТО ПЕРЕИЗДАНИЕ." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "ЯЗЫК" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "СКРИПТЫ" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "ОБНОВИТЬ КЕШ" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "ТИП ШЕЙДЕРА" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "ПОЛНОЭКРАННЫЙ РЕЖИМ" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "СГЛАЖИВАНИЕ" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "СОХРАНЯТЬ ПРОПОРЦИИ ВИДЕОРОЛИКОВ" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "ОТОБРАЖЕНИЕ СЧЁТЧИКА КАДРОВ" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "ГРАФИКА" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "ВЫБЕРИТЕ ФАЙЛЫ ОРИГИНАЛЬНОЙ ИГРЫ ДЛЯ РАЗБЛОКИРОВКИ ЭПИЗОДОВ" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "" msgstr[1] "" msgstr[2] "" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "ОЖИДАНИЕ ФАЙЛОВ..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "ФАЙЛЫ НЕ БЫЛИ ВЫБРАНЫ!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "НЕ БЫЛО ДОБАВЛЕНО НОВЫХ ЭПИЗОДОВ!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "ЗВУК" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "ПРОДОЛЖИТЬ" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "СОХРАНЕНИЕ И ВЫХОД" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "ОБРАБОТКА ФАЙЛОВ В ПАПКЕ \f[c:#9e7056]\"Source\"\f[/c]..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "ДОБАВЛЕННЫЕ УРОВНИ И ЭПИЗОДЫ СКОРО БУДУТ ДОСТУПНЫ." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "ВЛЕВО" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "ВПРАВО" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "ВВЕРХ" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "ВНИЗ" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "ОГОНЬ" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "ПРЫЖОК" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "БЕГ" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "СМЕНА ОРУЖИЯ" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "НАЗАД" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "НЕТ / ПИКСЕЛЬ В ПИКСЕЛЬ" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "МОНОХРОМНОЕ" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "ВЫБОР ШЕЙДЕРНОГО ЭФФЕКТА" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "ОБЩАЯ ГРОМКОСТЬ" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "ГРОМКОСТЬ ЭФФЕКТОВ" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "ГРОМКОСТЬ МУЗЫКИ" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "СЛОЖНОСТЬ" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "СТАРТ" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "ЛЕГКО" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "СРЕДНЕ" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "СЛОЖНО" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "ПЕРЕТАСКИВАЯ ЗОНЫ КАСАНИЯ, ВЫ МОЖЕТЕ НАСТРОИТЬ ИХ ПОЛОЖЕНИЕ." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "ИНТЕГРАЦИЯ С Discord" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "" msgstr[1] "" msgstr[2] "" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c:#704a4a]НЕ МОГУ ЗАГРУЗИТЬ ВЫБРАННЫЙ УРОВЕНЬ!\f[/c]\n" "\n" "\n" "УБЕДИТЕСЬ, ЧТО ВСЕ НЕОБХОДИМЫЕ ФАЙЛЫ\n" "ДОСТУПНЫ И ПОПРОБУЙТЕ СНОВА." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" #~ msgid "Error" #~ msgstr "ОШИБКА" deathkiller-jazz2-native-2a7ccef/Content/Translations/tr.mo000066400000000000000000001054111512772601700241670ustar00rootroot00000000000000ED l01P__{M;L{^RAe?^ g Y!y!nZ"l"A6#rx#v#b$\$aQ%%L&|&Xb'' '6'.1(`(,f( ( (( ((((( )) .)<)L) ]) k))u))) )) ))) **1* :* E*P*Y*k****'** *** ++1!+/S+ + +++++++++ +),"2, U,.`,+,,,,,- ---&-=-@A- -- - --7--- .#.5.M.g.&....../ &/2/ F/ Q/ ]/h/x/'/!/=/20I0N0S0c0l0 ~00 0011 11 1222"2 &2 12 =2G2X2@l22222222 2 33V!3x3 33 3 333333 4446,4 c4q4 v4 4?44<45;56(-6 V6,w6.6&6+6)&7(P7#y7,7*7&7A8>^8%8(8689#9]9&}9<9A9'#:DK:(:+:@:/&;)V;6;L;?<FD<,<)<S<F6=(}=2=(=L>NO>E>0>,?(B?>k?4?0?9@7J@.@A@*@<A'[AIADA.B3ABDuB6BCBd5C1CCC<DFMDID+DE EFPEJECE:&F6aF@F;F5GDKG.GFG(H@/H}pHH: I8FI3IAI0I8&JA_J JEJCK$LK6qKHKAKE3LIyL?L6MI:M@MDMr N}N^NFN08O1iOQO+OPvPZQ'uQ@QQQ;0R&lR=RBR,S3AS4uS@SS*U U]U\VNVKGWWUXnXXZYZZ.[Z[\\~9]}]>6^]u^]^]1_]_f_T``ya`bfbb@b1b c' cHc#[ccccccc cccdd#d4d.=dld}ddd ddd d"dee,e3e;eVeketeze!eeee"ee f8f8If f ffffffg gg(g/8g)hgg0g,gghh$&h KhYhshwhhh=hhhhi i4iSiZiiiiii)i'i&j9jOjWjojjjj j j jj%j4%k Zk;gk5k kkk kll1lGlZlxm'mmmmm nnnn5n DnNn]nE{nnn nn no oo*o;o]Noooo o opp8pIpYpapup{p3p ppppFp:qO?qq:or%rrrr3sQshssss sst()t6Rttt<t2tu5u-PuA~u!u<uv5vEOv'vv+vHwGLwIw"wxDx&Wx~xxx@x3 yF?y.y.yy6y36z4jz>z&z/{:5{2p{4{#{F{AC||(|5||!}f=},}3}:~<@~G}~~I~6'9^G52/2b76̀,K)\|/ (<e1%*݂?H6]=҃:5!+W9M3 (?Th5Gj;G1":$],χkV`ˆ#4B,w+Љ8߉A$Z'(8Њ?Kd:TpA ]e$zV 3*a SqRx[ >$nDW8 J-\2G  m%C5<i6 (0QH -=2%}`5, sL!'!>tFb^.NY'9 =j~+@4BZ<4 7)E&9/07kl;|w.3Ihy*DfEMu(B#r);_{"6c,U8O&C?1":+X@o1vAP/#g The game will begin shortly! Winner is {} [c:#337233]Restart the game to read [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] files correctly. [c:#704a4a]Cannot connect to the server! [/c] Authentication failed. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Invalid parameter specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid password specified. [c:#704a4a]Cannot connect to the server! [/c] Invalid player name specified. Please check your profile and try it again. [c:#704a4a]Cannot connect to the server! [/c] Server capacity is full. Please try it later. [c:#704a4a]Cannot connect to the server! [/c] Server is not in a state where it can process your request. Please try again in a few seconds. [c:#704a4a]Cannot connect to the server! [/c] Server requires 3rd party authentication provider. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] The server is not responding for connection request. [c:#704a4a]Cannot connect to the server! [/c] This client is not in the server whitelist. Contact server administrators for more information. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain level "{}". Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client doesn't contain required assets. Please download the required files and try it again. [c:#704a4a]Cannot connect to the server! [/c] Your client version is not compatible with the server. [c:#704a4a]Cannot create the server! [/c] Playlist is not properly configured. Please review server configuration and try it again. [c:#704a4a]Cannot create the server! [/c] Please verify that no other server is running on that port and try it again. [c:#704a4a]Cannot load specified level! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Cannot resume saved state! [/c] Make sure all necessary files are accessible and try it again. [c:#704a4a]Connection has been closed! [/c] Cheating detected. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for maintenance. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for reconfiguration. Please try it again later. [c:#704a4a]Connection has been closed! [/c] Server is shutting down for update. Please check your client version and try it again in a minute. [c:#704a4a]Connection has been closed! [/c] Server is shutting down. Please try it later. [c:#704a4a]Connection has been closed! [/c] The server has disconnected you due to inactivity. [c:#704a4a]Connection has been closed! [/c] You have been [c:#725040]banned [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been closed! [/c] You have been [c:#907050]kicked [/c] off the server. Contact server administrators for more information. [c:#704a4a]Connection has been lost! [/c] Please try it again and if the problem persists, check your network connection. [c:#704a4a]This game requires original [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] files! [c:#d0705d]{} [/c] connected [c:#d0705d]{} [/c] disconnected [c:#d0705d]{} [/c] was roasted by [c:#d0705d]{} [/c] [c:#d0705d]{} [/c] was roasted by environmentAboutAccess to external storage has been granted!Allow CheatsAllow access to external storageAlwaysAntialiasingBackBackground DitheringBattleBrowse "Source" DirectoryButtstompCRT Aperture GrilleCRT ScanlinesCRT Shadow MaskCapture The FlagChange WeaponCharacterCheats are not allowed in current contextConnect To ServerContinueContributorsControlsCooperationCreate Private ServerCreate Public ServerCreate ServerCreate server from playlistDetailedDevelopersDifficultyDisabledDisconnect & ExitDiscord IntegrationDownEasyEnabledEnabled [c:#d0705d](Experimental) [/c]Enabled With Ammo CountEnhancementsEpisode is locked!Extended PlayStation™ SupportFind exit!FireFor more information, visit the official website:For more information, visit {} and  Discord!FullscreenGame ModeGame starts in {}Gamepad Button LabelsGamepad RumbleGameplayGraphicsHardHighHigher Score OnlyHighscoresHighscores for [c:#d0705d]Base game [/c]Highscores for [c:#d0705d]{} [/c]HorizontalI want to play the game the way it used to be.I want to play the game with something new.Import EpisodesInput DiagnosticsJumpKeep Aspect Ratio In CinematicsLanguageLedge ClimbingLeftLegacyLevel "{}" initializedLowMake sure Jazz Jackrabbit 2 files are present in following path:Master VolumeMediumMonochromeMusic VolumeNative Back ButtonNewly added levels and episodes will be available soon.NoNo Cheats OnlyNo custom level found!No episode found!No files were selected!No gamepads are detected!No new episodes were imported!No servers found, but still searchin'!None / Pixel-perfectNumber of Local PlayersOptionsOverwrite Episode CompletionPerformance MetricsPlay Custom LevelsPlay OnlinePlay Shareware DemoPlay StoryPlayer NamePoints: {}Prefer Zoom OutPreferred SplitscreenPress [c:#d0705d]Fire [/c] to continuePress any key or button to assignProcessing of files in [c:#9e7056]"Source" [/c] directory...Processing of {} file...Processing of {} files...QuitRaceRazer Chroma™ReforgedReforged GameplayReforged HUDReforged Main MenuRefresh CacheReimplementation of the game [c:#9e7056]Jazz Jackrabbit 2 [/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension.Remap ControlsRemap Controls for Player {}Rescale ModeReset To DefaultResolutionRestart episodeResumeRightRunSFX VolumeSave & ExitScriptingSelect Game ModeSelect Rescale ModeSelect files of your original game to unlock additional episodesShortShow Player TrailsSoundsSpectateStartStrongSwitch To New WeaponTeam BattleTeam RaceTeam Treasure HuntThis project uses modified [c:#9e7056]nCine [/c] game engine and following libraries:Toggle ConsoleToggle RunTouch ControlsTranslatorsTreasure HuntUnaligned ViewportUnique Player IDUnknown commandUnnamed serverUpUser ProfileVerticalWaiting for files...Waiting for {} more playerWaiting for {} more playersWater QualityWeakWeapon WheelWeapon {}Welcome to [c:#9e7056]Jazz Jackrabbit 2 [/c] reimplementation!YesYou can adjust position of the touch zones by drag and drop.You can choose your preferred play style. This option can be changed at any time in [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]. For more information, visit {} and  Discord!You can enable enhancements that were added to this remake.You must complete "{}" first!flash/01_diam1 Dragons live in burbank.flash/01_diam1 Find the gopher.flash/01_diam1 Mark wears briefs. Hoo Hah!flash/01_diam1 Nick loves shiny. Always has!flash/01_diam1 Spaz ate the dopefish.flash/02_diam3 Beware of chainsaw schmalz.flash/02_diam3 Dont give mark a burrito.flash/02_diam3 Send Nigel a green card.flash/02_diam3 Send Tim new socks.flash/05_medivo1 Beware of falling enemies.flash/05_medivo1 Craig is still a doofus!flash/05_medivo1 Secret Level Time!!!flash/bonus_garglair Buttstomp A Silver Crate To Clear Your Pathflash/bonus_garglair Crates can also make platforms appear...flash/bonus_garglair Leh is a Camperflash/bonus_garglair Melt the Spring...monk/01_jung1 A Flamethrower works well against bugs.monk/01_jung1 Falling boulders can give you a headache.monk/03_hell Goodnight, bubba!monk/03_hell Long live the ice level.monk/06_damn2 What the heck? Aaaah! No! This is NOT over!prince/01_castle1 Collect coins to activate bonus warp devices.prince/01_castle1 Nothing to see here.prince/01_castle1 Poles spin you around so you can go even faster.prince/01_castle1 Secret Treasure Room.prince/01_castle1 You found a secret area.prince/02_castle1n Buttstomp the metal box to open key blocks!prince/02_castle1n Cheese is green on tuesday.prince/02_castle1n Craig is king doofus.prince/02_castle1n Good job! Now go get Devan Shell!prince/02_castle1n Press down and jump beneath these blocks to break them!prince/02_castle1n To beat the queen shoot her off her ledge.prince/02_castle1n To kick through these blocks, press down and jump!prince/03_carrot1 Stomp your booty to exit.prince/03_carrot1 This spring is frozen.prince/04_carrot1n Shields will give you unlimited special ammo for a short time.prince/04_carrot1n Stopwatches will add time to the life of a shield.prince/04_carrot1n Super dooper secret.prince/04_carrot1n This schwartzenguard is toast!prince/06_labrat2 Ack! I'm outta here!prince/06_labrat2 You cannot defeat me, Jazz! Prepare to face my superbot!prince/06_labrat2 These blocks are speed blocks. Run into them at full speed!prince/trainer After jumping, press jump again to do a special move.prince/trainer Beware of sharp stuff. It hurts.prince/trainer Blue gems count as ten gems.prince/trainer Carrots give you health.prince/trainer Checkpoints save your spot if you lose a life.prince/trainer Collect coins to unlock bonus rooms.prince/trainer Collect gems for an extra life.prince/trainer Collect goodies for points and surprises.prince/trainer Good job. Remember to look for secrets.prince/trainer Green gems count as five gems.prince/trainer Now youre ready to play. Good luck and have fun.prince/trainer Red Gems count as one gem.prince/trainer Secrets abound in Jazz 2. Check the walls.prince/trainer Some walls can be shot.prince/trainer Welcome to Jazz Jackrabbit 2. This is a training level.prince/trainer When in the air, press down to stomp with your butt.rescue/01_colon1 Buttstomp the manhole cover!rescue/03_psych1 Smoke rings will make you dizzy.secretf/01_easter1 Don't beat Nigel at pool. You've veen warned. :)secretf/01_easter1 Find the crate to clear your path.secretf/01_easter1 No rewards to those with itchy trigger fingers.secretf/01_easter1 Only Spaz can get to the room up on the left. He may need something to stand on.secretf/01_easter1 Todays Forcast: Strong Winds!secretf/01_easter1 Welcome to Jazz Jackrabbit 2: The Secret Files!secretf/01_easter1 You can't buttstomp so go up and around!secretf/01_easter1Eating too many chocolate eggs can make you sick :psecretf/02_easter2 One route leads to riches. One route leads to battle.secretf/02_easter2 Sloping Tunnel Entrancesecretf/02_easter2 To access the tunnels above find the access warp.secretf/03_easter3 Find the crate to make your climbing blocks appearsecretf/03_easter3 Only those who can double-jump can get to the goodies!secretf/03_easter3 Stomping this crate also free's some enemies :)secretf/04_haunted1 But you need a way to get up there...secretf/04_haunted1 Enter the house with caution.....secretf/04_haunted1 Silver Crates can't be broken underwater...secretf/04_haunted1 Stomping crates can be good and bad...secretf/04_haunted1 Water Level control crate above.secretf/06_haunted3 Michelle, I will love you always and forever :)secretf/07_town1 Didn't make the jump huh? :)secretf/07_town1 Jump as far over to the right as you possibly can...secretf/07_town1 Take to the roof tops!secretf/07_town1 The skies above will reward those who stomp...secretf/07_town1 Use your Special Moves to get up the air cons! For Jazz, press Crouch and Jump. For Spaz, Press Jump Twice!secretf/07_town1 Well Done!secretf/08_town2 Collecting 20 coins is more rewarding...secretf/08_town2 Find the crate and the gems are yours!secretf/08_town2 Springs Don't Work When Frozen...secretf/09_town3 BEWARE! Flocks of Ravens can be very dangerous.secretf/09_town3 Choose a cover and stomp away!secretf/09_town3 Find the crate to clear the blocks....secretf/09_town3 Goto www.project2.com use password: BUNNYLOVER secretf/09_town3 Hi GeoBunny :)secretf/09_town3 The remove the blocks look to the tallest building.share/01_share1 Coins give you access to warps that appear later.share/01_share1 Shoot these blocks!share/01_share1 Some crates contain bombs or baddies!share/01_share1 Stomp in the right place and you might find a surprise!share/01_share1 To pass this area, stomp the secret metal crate.share/01_share1 When in the air, press down to stomp with your butt.share/01_share1 You need twenty coins to pass through this secret warp!share/02_share2 A flamethrower works well against nasty bugs.share/02_share2 Smoke rings will make you very dizzy!share/02_share2 You need twenty coins to pass through this secret warp!share/03_share3 Beware the witch! She can turn you into a frog.share/03_share3 If you are turned into a frog Eva Earlong can help!share/03_share3 You made it! This is the end of the shareware version. Now check out the order info for M O R E!to remove assignmentxmas99/01_xmas1 Seasons Greetings from Epic MegaGames Orange Games and Project 2 Interactive!xmas99/01_xmas1 Some blocks can only be broken with a certain weapon.xmas99/01_xmas1 Watch out for the spikes below!xmas99/01_xmas1 Welcome to Christmas Chronicles!xmas99/01_xmas1 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 Hi There, Piggy! - Poopyxmas99/02_xmas2 Kassi Nicole: A million things I long to say to you But my words always lead to the same ending I love you, I love you.xmas99/02_xmas2 Michelle: You've changed my life in so many ways I dedicate this project to you. I love you so much.xmas99/02_xmas2 Gem Trail Entrance. Climb the treetops to find and stomp the Entry Crate.xmas99/02_xmas2 Gem Trail Entry Crate.xmas99/02_xmas2 Punching the blocks above you can be rewarding.xmas99/02_xmas2 You can buttstomp through some of the weakspots in the pathways!xmas99/02_xmas2 You can stand on top of some of the trees!xmas99/03_xmas3 Don't lose your grip!xmas99/03_xmas3 Now leaving Burrowsville Please visit again.xmas99/03_xmas3 Password: xmasbunny Please visit www.project2.comxmas99/03_xmas3 Please use your TNT wisely.xmas99/03_xmas3 Robert and Craig: Springs RULE! :)xmas99/03_xmas3 That bridge doesnt look too safe...xmas99/03_xmas3 Welcome to Burrowsville Please drive carefully.Project-Id-Version: jazz2-resurrection PO-Revision-Date: Last-Translator: Language-Team: Consul Language: tr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plural-Forms: nplurals=2; plural=(n != 1); X-Generator: Poedit 3.8 X-Poedit-Basepath: ../.. X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2 X-Poedit-SearchPath-0: Sources/Jazz2 X-Poedit-SearchPath-1: .fake/Translations X-Poedit-SearchPath-2: Sources/Main.cpp Oyun kısa süre içerisinde başlıyor! Kazanan {} [c:#9e7056]Jazz Jackrabbit 2 [c:#337233] dosyalarını okumak için oyunu tekrar başlatın. [c:#704a4a]Sunucuya bağlanılamıyor! [/c]\f[/c] Kimlik doğrulama başarısız oldu. Daha fazla bilgi için sunucu yöneticileriyle iletişime geçin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Geçersiz parametre belirtildi. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Geçersiz parola belirtildi. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Geçersiz oyuncu adı belirtildi. Lütfen profilinizi kontrol edin ve tekrar deneyin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Kapasite dolu. Lütfen tekrar deneyin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Sunucu isteğinizi gerçekleştirebilecek durumda değil. Lütfen daha sonra tekrar deneyin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Sunucu 3. taraf kimlik doğrulama sağlayıcısı gerektiriyor. Daha fazla bilgi için sunucu yöneticileriyle iletişime geçin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Sunucu bağlantı isteğine cevap vermiyor. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Bu istemci sunucu beyaz listede değil. Daha fazla bilgi için sunucu yöneticileriyle iletişime geçin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] İstemciniz "{}" bölümünü içermiyor. Lütfen gerekli dosyaları indirin ve tekrar deneyin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] İstemcinizde gerekli dosyalar bulunmuyor. Lütfen gerekli dosyaları indirin ve tekrar deneyin. [c:#704a4a]Sunucuya bağlanılamıyor! [/c] Oyun sürümünüz sunucuyla uyumlu değil. [c:#704a4a]Sunucu kurulamıyor! [/c] Çalma listesi düzgün yapılandırılmamış. Lütfen sunucu yapılandırmasını gözden geçirip tekrar deneyin. [c:#704a4a]Sunucu kurulamıyor! [/c] Lütfen o portta başka bir sunucunun çalışmadığını doğrulayın ve tekrar deneyin. [c0x804a4a]İstenilen bölüm yüklenemedi! [/c] Gerekli dosyaların erişilebilir olduğundan emin olun ve tekrar deneyin. [c0x804a4a]İstenilen kayıt yüklenemedi! [/c] Gerekli dosyaların erişilebilir olduğundan emin olun ve tekrar deneyin. [c:#704a4a]Bağlantı kapatıldı! [/c] Hile tespit edildi. [c:#704a4a]Bağlantı kapandı! [/c] Sunucu kapanıyor. Lütfen daha sonra tekrar deneyin. [c:#704a4a]Bağlantı kapandı! [/c] Sunucu kapanıyor. Lütfen daha sonra tekrar deneyin. [c:#704a4a]Bağlantı kapandı! [/c] Sunucu kapanıyor. Lütfen daha sonra tekrar deneyin. [c:#704a4a]Bağlantı kapandı! [/c] Sunucu kapanıyor. Lütfen daha sonra tekrar deneyin. [c:#704a4a]Bağlantı kapatıldı! [/c] Sunucu, etkin olmadığınız için bağlantınızı kesti. [c:#704a4a]Bağlantı koptu! [/c] Sunucudan [c:#725040]yasaklandınız [/c]. Daha fazla bilgi için sunucu yöneticileri ile iletişime geçin. [c:#704a4a]Bağlantı koptu! [/c] Sunucudan [c:#907050]atıldınız [/c]. Daha fazla bilgi için sunucu yöneticileri ile iletişime geçin. [c:#704a4a]Bağlantı koptu! [/c] Lütfen daha sonra tekrar deneyin. Problem devam ediyorsa, internet bağlantınızı gözden geçirin. [c:#704a4a]Bu oyun orijinal [c:#9e7056]Jazz Jackrabbit 2 [c:#704a4a] dosyalarını gerektirir! [c:#d0705d]{} [/c] bağlandı [c:#d0705d]{} [/c] ayrıldı [c:#d0705d]{} [/c], [c:#d0705d]{} [/c] tarafından yakıldı. [c:#d0705d]{} [/c], çevre tarafından yakıldıHakkındaHarici depolamaya erişim izni verildi!Hilelere İzin VerHarici depolama erişimine izin verHepKenar YumuşatmaGeriArkaplan TitremesiSavaş"Source" Dizinine GözatPopo vuruşuCRT Açıklık IzgarasıCRT ÇizgileriCRT Gölge MaskesiBayrak KapmacaSilah DeğiştirKarakterMevcut bağlamda hile kullanılması yasaktırSunucuya BağlanDevam EtKatkıda BulunanlarKontrol AyarlarıEşli OyunÖzel Sunucu OluşturGenel Sunucu OluşturSunucu YaratÇalma listesinden sunucu oluşturDetaylıYazılımcılarZorlukKapalıBağlantıyı Kes ve ÇıkDiscord EntegrasyonuAşağıKolayAçıkEtkin [c:#d0705d](Deneysel) [/c]Mermi ile EtkinGeliştirmelerBu bölüm kilitli!Gelişmiş Playstation™ DesteğiÇıkışı bul!AteşDaha fazla bilgi için, resmi web sitesini ziyaret edin:Daha fazla bilgi için {} ve  Discord'u ziyaret edin!Tam ekranOyun ModuOyun {} içinde başlıyorGamepad Tuşu İkonlarıGamepad Titreşim SeviyesiOyun AyarlarıGrafik AyarlarıZorYüksekSadece en yüksek skorYüksek puanlar [c:#d0705d]Ana oyun [/c] için yüksek puanlar [c:#d0705d]{} [/c] için yüksek puanlarYatayOyunu geçmişte olduğu gibi oynamak istiyorum.Oyunu yenilenmiş haliyle oynamak istiyorum.Bölümleri İçe AktarGiriş TanılamaZıplaSinematiklerde En-Boy Oranını KoruDil AyarlarıÇıkıntılara TırmanmaSolOrijinal"{}" bölümü başlatıldıDüşükJazz Jackrabbit 2 dosyalarının şurada olduğundan emin ol:Ana Ses SeviyesiOrtaTek RenkMüzik SeviyesiGeri TuşuYeni bölümler yakında erişilebilir hale gelecek.HayırSadece hilesizÖzel bölüm bulunamadı!Hiçbir bölüm bulunamadı!Hiçbir dosya seçilmedi!Oyun kolu algılanmadı!Hiçbir yeni bölüm içeriye alınmadı!Sunucu bulunamadı, ama hala aranıyor!Kapalı / OrijinalYerel Oyuncu SayısıAyarlarBölümün üzerine yazPerformans DeğerleriÖzel Bölüm SeçÇevrimiçi OynaDeneme Sürümünü OynaHikaye ModuOyuncu AdıPuanlar: {}Uzaklaştırmayı AyarlaTercih Edilen Bölünmüş Ekran ModuDevam etmek için [c:#d0705d]Ateş [/c] tuşuna basTuş atayın[c:#9e7056]"Source" [/c] dizinindeki dosyalar işleniyor...{} dosyası işleniyor...{} dosyaları işleniyor...ÇıkışYarışRazer Chroma™YenilenmişYenilenmiş OynanışYenilenmiş ArayüzYenilenmiş Ana MenüÖnbelleği Yenile1998 yılında çıkmış [c:#9e7056]Jazz Jackrabbit 2 [/c] oyununun yeniden düzenlenmiş halidir. Oyunun farklı versiyonlarını destekler (Shareware Demo, Holiday Hare '98, The Secret Files ve Christmas Chronicles). Aynı zamanda, JJ2+ eklentisinin bazı özelliklerini destekler.Kontrolleri DeğiştirOyuncu {} İçin Kontrolleri DeğiştirÖlçeklendirme AyarlarıVarsayılana SıfırlaÇözünürlükBölümü TekrarlaDevam EtSağKoşSes Efektleri SeviyesiKaydet & ÇıkKomutlamaOyun Modu SeçÖlçeklendirme Modunu SeçinEkstra bölümleri orijinal oyun dosyalarının içinden seçerek açKısaKarakter İzini GösterSes AyarlarıİzleOyuna BaşlaGüçlüYeni Silaha GeçTakım SavaşıTakım YarışıTakım Hazine AvıBu proje değiştirilmiş [c:#9e7056]nCine [/c] motorunu ve şu kütüphaneleri kullanıyor:Konsolu Aç/KapatSürekli KoşmaDokunmatik Kontrol AyarlarıÇevirmenlerHazine AvıHizalanmamış GörünümBenzersiz Oyuncu KimliğiBilinmeyen komutİsimsiz sunucuYukarıKullanıcı ProfiliDikeyDosyalar bekleniyor...{} oyuncu daha bekleniyor{} oyuncu daha bekleniyorSu KalitesiZayıfSilah TekerleğiSilah {}Yenilenmiş [c:#9e7056]Jazz Jackrabbit 2 [/c] deneyimine hoş geldin!EvetDokunma bölgelerini tutup bırakarak bulundukları pozisyonu ayarlayabilirsin.Sana uygun olan oynanış biçimini seçebilirsin. Bu ayar [c:#707070]{} [/c] > [c:#707070]{} [/c] > [c:#707070]{} [/c]'da değiştirilebilir. Daha fazla bilgi için, {}'i veya  Discord sunucumuzu ziyaret edebilirsin!Bu versiyona eklenmiş geliştirmeleri aktive edebilirsin.Önce "{}" bölümünü bitirmelisin! Ejderhalar Burbank'te yaşar. Sincabı bul. Mark külot giyiyor. Hoo Hah! Nick her zaman olduğu gibi parlak şeyleri sever! Spaz dopefish'i yedi. Testere yağına dikkat et. Mark'a burrito verme. Nigel'a yeşil kart yolla. Tim'e yeni çorap yolla. Düşen düşmanlara dikkat et. Craig hâlâ bir şapşal! Gizli Bölüm Zamanı!!! Yolunu Açmak İçin Gümüş Kutuyu Ez Kutular platformların görünmesini sağlayabilir... Leh bir Pusucu İlkbaharı Erit... Bir Alev Makinesi böceklere karşı çok iyi çalışır. Düşen kaya parçaları başını ağrıtabilir. İyi geceler, canım! Buzlu bölüm çok yaşa. Nasıl ya? Aaaah! Hayır! Bu burada bitmedi! Jeton toplayarak ışınlanma cihazlarını aktive edebilirsin. Burada görülecek bir şey yok. Direkler seni döndürerek daha hızlı ilerlemeni sağlar. Gizli Hazine Odası. Gizli bir bölge buldun. Metal kutuyu ezerek bloklarla kilitlenmiş bölgeleri açabilirsin! Peynirler, Salı günleri yeşil olur. Craig şapşal bir kral. Aferin! Şimdi gidip Devan Shell'i yakala! Kutuları kırmak için onların altındayken aşağıya bas ve zıpla! Kraliçe'yi yenebilmek için ona ateş ederek çıkıntıdan düşür. Bu blokların içinden geçmek için aşağıya bas ve ardından zıpla! Buradan çıkmak için bloğu ez. Bu yay donmuş. Kalkanlar sana belli bir süre için sınırsız özel mermi verir. Saatler senin kalkanına süre ekler. Müthiş gizli oda. Bu gardiyan yalan oldu! Aman be! Buradan gidiyorum! Beni yenemezsin, Jazz! Süperbotum ile yüzleşmeye hazır ol! Bu bloklar hız bloklarıdır. Onlara doğru koş! Zıpladıktan sonra tekrar zıplayarak özel hareketini yapabilirsin. Keskin şeylere dikkat et. Canını acıtır. Mavi mücevherler de on mücevher'e eşittir. Havuçlar sana can verir. Kontrol noktaları, ölmen halinde seni geri getirir. Jetonları toplayarak gizli odaları açabilirsin. Mücevherleri toplayarak ekstra can kazanabilirsin. Şekerlemeleri toplayarak puan ve sürprizler kazanabilirsin. Bravo. Gizli yerleri aramayı unutma. Yeşil mücevherler beş mücevher'e eşittir. Şimdi oynamaya hazırsın. Bol şans ve iyi eğlenceler. Kırmızı mücevherler bir mücevher'e eşittir. Jazz 2'de gizli yerler bol. Duvarları kontrol et. Bazı duvarlara ateş edebilirsin. Jazz Jackrabbit 2'ye hoş geldin. Bu bir alıştırma bölümüdür. Zıpladıktan sonra aşağı tuşuna basarak zemini ezebilirsin. Rögar kapağını ez! Duman halkaları başını döndürür. Nigel'ı bilardoda yenme. Ben uyarımı yapayım. :) Yolu açmak için kutuyu bul. Aceleci davrananlara ödül yok. Sol üstteki odaya sadece Spaz ulaşabilir. Üstünde durması gereken bir şeye ihtiyacı olabilir. Bugünün Hava Durumu: Güçlü Rüzgarlar! Jazz Jackrabbit 2: The Secret Files'a hoş geldin! Burada ezme hareketini yapamazsın. Yukarıdan devam et!Çok fazla çikolatalı yumurta yemek seni rahatsız eder :p Yollardan biri zenginliğe götürür. Diğeri ise savaşa götürür. Eğimli Tünel Girişi Yukarıdaki tünele ulaşabilmek için oraya ışınlanılacak yeri bul. Tırmanma bloklarını ortaya çıkartacak kutuyu bul Sadece iki kere zıplayabilenler ödüllere erişebilir! Bu kutuyu ezmek aynı zamanda bazı düşmanları ortaya çıkarır :) Ama yukarıya gitmek için bir yola ihtiyacın var.. Eve dikkatlice gir..... Suyun altında Gümüş Kutuları kıramazsın... Kutuları ezmek hem iyi hem de kötü olabilir... Su seviyesini kontrol eden kutu yakınlarda bir yerde. Michelle, Seni her zaman ve sonsuza dek seveceğim :) Zıplayamadın di'mi? :) Mümkün olduğu kadar sağa doğru atla... Çatıya çık! Gökyüzü, ezenleri ödüllendirecek... Havalandırmaya çıkmak için Özel Hareketini kullan! Jazz isen, önce Eğil ve sonra Zıpla. Spaz isen, iki kere Zıpla! Aferin! 20 adet jeton toplamak daha ödüllendirici... Kutuyu bul ve mücevherler senin olsun! Yaylar Donukken Çalışmaz... DİKKAT! Bir grup karga çok tehlikeli olabilir. Saklanacak yer bul ve ezmeye başla! Blokları temizlemek için kutuyu bul.... www.project2.com adresine git ve BUNNYLOVER şifresini kullan Merhaba GeoBunny :) Blokları temizlemek için en uzun binaya doğru bak. Jetonlar senin ışınlanma cihazlarından geçmeni sağlar. Bloklara ateş et! Bazı kutularda bombalar ve daha kötü şeyler olabilir! Doğru yeri ezersen belki bir sürpriz bulabilirsin! Burayı geçebilmek için metal kutuyu ez. Zıpladıktan sonra aşağıya basarak yeri ezebilirsin. Bu ışınlanma cihazından geçebilmek için 20 adet jetona ihtiyacın var! Alev makinesi, böceklere karşı iyi çalışır. Duman halkaları başını döndürür! Bu gizli ışınlanma cihazından geçebilmek için 20 adet jetona ihtiyacın var! Cadıya dikkat et! Seni bir kurbağaya çevirebilir. Eğer kurbağaya dönüştüysen Eva Earlong sana yardımcı olabilir! Başardın! Bu demo sürümünün sonuydu. Daha fazlası için ana menüden tam sürümü oynayabilirsin!ile atanmış tuşu silin Epic MegaGames Orange Games ve Project 2 Interactive'den İyi Noeller! Bazı bloklar spesifik silahlarla kırılabilir. Aşağıdaki iğnelere dikkat et! Christmas Chronicles'a hoş geldin! Yolundaki bazı hassas yerleri ezebilirsin! Merhaba Domuzcuk! - Kakacık Kassi Nicole: Sana söylemek istediğim milyonlarca şey var Ama sözlerim hep aynı yola çıkıyor Seni Seviyorum, Seni Seviyorum. Michelle: Hayatımı pek çok yönden değiştirdin. Bu projeyi sana ithaf ediyorum. Seni çok seviyorum. Mücevher İzi Girişi. Giriş Sandığını bulup ezmek için ağaçların tepelerine tırman. Mücevher İzi Giriş Kutusu. Yukardaki blokları ezmek ödüllendirici olabilir. Yolundaki bazı hassas yerleri ezebilirsin! Bazı ağaçların üzerinde durabilirsin! Sıkı tutun! Burrowsville'den çıkıyorsunuz. Lütfen tekrar gelin. Şifre: xmasbunny Lütfen www.project2.com adresini ziyaret edin Lütfen TNT'lerini dikkatli kullan. Robert ve Craig: Bahar EN İYİSİ! :) O köprü pek güvenli gözükmüyor... Burrowsville'e hoş geldiniz. Lütfen dikkatli sürün.deathkiller-jazz2-native-2a7ccef/Content/Translations/tr.po000066400000000000000000002024451512772601700241770ustar00rootroot00000000000000msgid "" msgstr "" "Project-Id-Version: jazz2-resurrection\n" "POT-Creation-Date: 2025-11-09 15:59+0100\n" "PO-Revision-Date: \n" "Last-Translator: \n" "Language-Team: Consul\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.8\n" "X-Poedit-Basepath: ../..\n" "X-Poedit-KeywordsList: _;_n:1,2;_x:1c,2;_nx:1c,2,3;_f;_fn:1,2\n" "X-Poedit-SearchPath-0: Sources/Jazz2\n" "X-Poedit-SearchPath-1: .fake/Translations\n" "X-Poedit-SearchPath-2: Sources/Main.cpp\n" #: .fake/Translations/flash/01_diam1.j2l.h:1 msgctxt "flash/01_diam1" msgid "" "\n" "Spaz ate the dopefish." msgstr "" "\n" "Spaz dopefish'i yedi." #: .fake/Translations/flash/01_diam1.j2l.h:2 msgctxt "flash/01_diam1" msgid "" "\n" "Find the gopher." msgstr "" "\n" "Sincabı bul." #: .fake/Translations/flash/01_diam1.j2l.h:3 msgctxt "flash/01_diam1" msgid "" "\n" "Dragons live in burbank." msgstr "" "\n" "Ejderhalar Burbank'te yaşar." #: .fake/Translations/flash/01_diam1.j2l.h:4 msgctxt "flash/01_diam1" msgid "" "\n" "Mark wears briefs. \n" "Hoo Hah!" msgstr "" "\n" "Mark külot giyiyor.\n" "Hoo Hah!" #: .fake/Translations/flash/01_diam1.j2l.h:5 msgctxt "flash/01_diam1" msgid "" "\n" "Nick loves shiny. \n" "Always has!" msgstr "" "\n" "Nick her zaman olduğu\n" "gibi parlak şeyleri sever!" #: .fake/Translations/flash/02_diam3.j2l.h:1 msgctxt "flash/02_diam3" msgid "" "\n" "Send Tim new socks." msgstr "" "\n" "Tim'e yeni çorap yolla." #: .fake/Translations/flash/02_diam3.j2l.h:2 msgctxt "flash/02_diam3" msgid "" "\n" "Send Nigel a green card." msgstr "" "\n" "Nigel'a yeşil kart yolla." #: .fake/Translations/flash/02_diam3.j2l.h:3 msgctxt "flash/02_diam3" msgid "" "\n" "Beware of chainsaw schmalz." msgstr "" "\n" "Testere yağına dikkat et." #: .fake/Translations/flash/02_diam3.j2l.h:4 msgctxt "flash/02_diam3" msgid "" "\n" "Dont give mark a burrito." msgstr "" "\n" "Mark'a burrito verme." #: .fake/Translations/flash/05_medivo1.j2l.h:1 msgctxt "flash/05_medivo1" msgid "" "\n" "Beware of falling enemies." msgstr "" "\n" "Düşen düşmanlara dikkat et." #: .fake/Translations/flash/05_medivo1.j2l.h:2 msgctxt "flash/05_medivo1" msgid "" "\n" "Craig is still a doofus!" msgstr "" "\n" "Craig hâlâ bir şapşal!" #: .fake/Translations/flash/05_medivo1.j2l.h:3 msgctxt "flash/05_medivo1" msgid "" "\n" "Secret Level Time!!!" msgstr "" "\n" "Gizli Bölüm Zamanı!!!" #: .fake/Translations/flash/bonus_garglair.j2l.h:1 msgctxt "flash/bonus_garglair" msgid "" "\n" "Buttstomp A Silver Crate\n" "To Clear Your Path" msgstr "" "\n" "Yolunu Açmak İçin\n" "Gümüş Kutuyu Ez" #: .fake/Translations/flash/bonus_garglair.j2l.h:2 msgctxt "flash/bonus_garglair" msgid "" "\n" "Crates can also make platforms appear..." msgstr "" "\n" "Kutular platformların görünmesini sağlayabilir..." #: .fake/Translations/flash/bonus_garglair.j2l.h:3 msgctxt "flash/bonus_garglair" msgid "" "\n" "Melt the Spring..." msgstr "" "\n" "İlkbaharı Erit..." #: .fake/Translations/flash/bonus_garglair.j2l.h:4 msgctxt "flash/bonus_garglair" msgid "" "\n" "Leh is a Camper" msgstr "" "\n" "Leh bir Pusucu" #: .fake/Translations/monk/01_jung1.j2l.h:1 msgctxt "monk/01_jung1" msgid "" "\n" "Falling boulders can \n" "give you a headache." msgstr "" "\n" "Düşen kaya parçaları\n" "başını ağrıtabilir." #: .fake/Translations/monk/01_jung1.j2l.h:2 msgctxt "monk/01_jung1" msgid "" "\n" "A Flamethrower works\n" "well against bugs." msgstr "" "\n" "Bir Alev Makinesi \n" "böceklere karşı çok iyi çalışır." #: .fake/Translations/monk/03_hell.j2l.h:1 msgctxt "monk/03_hell" msgid "" "\n" "Long live the ice level." msgstr "" "\n" "Buzlu bölüm çok yaşa." #: .fake/Translations/monk/03_hell.j2l.h:2 msgctxt "monk/03_hell" msgid "" "\n" "Goodnight, bubba!" msgstr "" "\n" "İyi geceler, canım!" #: .fake/Translations/monk/06_damn2.j2l.h:2 msgctxt "monk/06_damn2" msgid "" "\n" "What the heck? Aaaah! No! \n" "This is NOT over!" msgstr "" "\n" "Nasıl ya? Aaaah! Hayır!\n" "Bu burada bitmedi!" #: .fake/Translations/prince/01_castle1.j2l.h:1 msgctxt "prince/01_castle1" msgid "" "\n" "Poles spin you around so\n" " you can go even faster." msgstr "" "\n" "Direkler seni döndürerek\n" "daha hızlı ilerlemeni sağlar." #: .fake/Translations/prince/01_castle1.j2l.h:2 msgctxt "prince/01_castle1" msgid "" "\n" "You found a secret area." msgstr "" "\n" "Gizli bir bölge buldun." #: .fake/Translations/prince/01_castle1.j2l.h:3 msgctxt "prince/01_castle1" msgid "" "\n" "Secret Treasure Room." msgstr "" "\n" "Gizli Hazine Odası." #: .fake/Translations/prince/01_castle1.j2l.h:4 msgctxt "prince/01_castle1" msgid "" "\n" "Nothing to see here." msgstr "" "\n" "Burada görülecek bir şey yok." #: .fake/Translations/prince/01_castle1.j2l.h:5 msgctxt "prince/01_castle1" msgid "" "\n" "Collect coins to activate \n" "bonus warp devices." msgstr "" "\n" "Jeton toplayarak ışınlanma \n" "cihazlarını aktive edebilirsin." #: .fake/Translations/prince/02_castle1n.j2l.h:1 msgctxt "prince/02_castle1n" msgid "" "\n" "Cheese is green on tuesday." msgstr "" "\n" "Peynirler, Salı günleri yeşil olur." #: .fake/Translations/prince/02_castle1n.j2l.h:2 msgctxt "prince/02_castle1n" msgid "" "\n" "Craig is king doofus." msgstr "" "\n" "Craig şapşal bir kral." #: .fake/Translations/prince/02_castle1n.j2l.h:3 msgctxt "prince/02_castle1n" msgid "" "\n" "To beat the queen \n" "shoot her off her ledge." msgstr "" "\n" "Kraliçe'yi yenebilmek için\n" "ona ateş ederek çıkıntıdan düşür." #: .fake/Translations/prince/02_castle1n.j2l.h:4 msgctxt "prince/02_castle1n" msgid "" "\n" "Good job! \n" "Now go get Devan Shell!" msgstr "" "\n" "Aferin!\n" "Şimdi gidip Devan Shell'i yakala!" #: .fake/Translations/prince/02_castle1n.j2l.h:5 msgctxt "prince/02_castle1n" msgid "" "\n" "To kick through these\n" "blocks, press down and jump!" msgstr "" "\n" "Bu blokların içinden geçmek için\n" "aşağıya bas ve ardından zıpla!" #: .fake/Translations/prince/02_castle1n.j2l.h:6 msgctxt "prince/02_castle1n" msgid "" "\n" "Press down and jump beneath \n" "these blocks to break them!" msgstr "" "\n" "Kutuları kırmak için onların\n" "altındayken aşağıya bas ve zıpla!" #: .fake/Translations/prince/02_castle1n.j2l.h:7 msgctxt "prince/02_castle1n" msgid "" "\n" "Buttstomp the metal box \n" "to open key blocks!" msgstr "" "\n" "Metal kutuyu ezerek bloklarla \n" "kilitlenmiş bölgeleri açabilirsin!" #: .fake/Translations/prince/03_carrot1.j2l.h:1 msgctxt "prince/03_carrot1" msgid "" "\n" "Stomp your booty to exit." msgstr "" "\n" "Buradan çıkmak için bloğu ez." #: .fake/Translations/prince/03_carrot1.j2l.h:2 msgctxt "prince/03_carrot1" msgid "" "\n" "This spring is frozen." msgstr "" "\n" "Bu yay donmuş." #: .fake/Translations/prince/04_carrot1n.j2l.h:1 msgctxt "prince/04_carrot1n" msgid "" "\n" "Super dooper secret." msgstr "" "\n" "Müthiş gizli oda." #: .fake/Translations/prince/04_carrot1n.j2l.h:2 msgctxt "prince/04_carrot1n" msgid "" "\n" "Shields will give you unlimited \n" "special ammo for a short time." msgstr "" "\n" "Kalkanlar sana belli bir süre\n" "için sınırsız özel mermi verir." #: .fake/Translations/prince/04_carrot1n.j2l.h:4 msgctxt "prince/04_carrot1n" msgid "" "\n" "Stopwatches will add time to\n" "the life of a shield." msgstr "" "\n" "Saatler senin kalkanına\n" "süre ekler." #: .fake/Translations/prince/04_carrot1n.j2l.h:5 msgctxt "prince/04_carrot1n" msgid "" "\n" "This schwartzenguard is toast!" msgstr "" "\n" "Bu gardiyan yalan oldu!" #: .fake/Translations/prince/06_labrat2.j2l.h:1 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "You cannot defeat me, Jazz!\n" "Prepare to face my superbot!" msgstr "" "\n" "\n" "Beni yenemezsin, Jazz!\n" "Süperbotum ile yüzleşmeye hazır ol!" #: .fake/Translations/prince/06_labrat2.j2l.h:2 msgctxt "prince/06_labrat2" msgid "" "\n" "\n" "Ack! I'm outta here!" msgstr "" "\n" "\n" "Aman be! Buradan gidiyorum!" #: .fake/Translations/prince/06_labrat2.j2l.h:3 msgctxt "prince/06_labrat2" msgid "" "\n" "These blocks are speed blocks.\n" "Run into them at full speed!" msgstr "" "\n" "Bu bloklar hız bloklarıdır.\n" "Onlara doğru koş!" #: .fake/Translations/prince/trainer.j2l.h:1 msgctxt "prince/trainer" msgid "" "\n" "Welcome to Jazz Jackrabbit 2. \n" " This is a training level." msgstr "" "\n" "Jazz Jackrabbit 2'ye hoş geldin.\n" " Bu bir alıştırma bölümüdür." #: .fake/Translations/prince/trainer.j2l.h:2 msgctxt "prince/trainer" msgid "" "\n" "Collect goodies for\n" "points and surprises." msgstr "" "\n" "Şekerlemeleri toplayarak\n" "puan ve sürprizler kazanabilirsin." #: .fake/Translations/prince/trainer.j2l.h:3 msgctxt "prince/trainer" msgid "" "\n" "After jumping, press jump\n" "again to do a special move." msgstr "" "\n" "Zıpladıktan sonra tekrar zıplayarak\n" "özel hareketini yapabilirsin." #: .fake/Translations/prince/trainer.j2l.h:4 msgctxt "prince/trainer" msgid "" "\n" "Some walls can be shot." msgstr "" "\n" "Bazı duvarlara ateş edebilirsin." #: .fake/Translations/prince/trainer.j2l.h:5 msgctxt "prince/trainer" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Zıpladıktan sonra aşağı tuşuna basarak\n" "zemini ezebilirsin." #: .fake/Translations/prince/trainer.j2l.h:6 msgctxt "prince/trainer" msgid "" "\n" "Secrets abound in Jazz 2. \n" " Check the walls." msgstr "" "\n" "Jazz 2'de gizli yerler bol.\n" " Duvarları kontrol et." #: .fake/Translations/prince/trainer.j2l.h:7 msgctxt "prince/trainer" msgid "" "\n" "Good job. Remember to\n" "look for secrets." msgstr "" "\n" "Bravo. Gizli yerleri\n" "aramayı unutma." #: .fake/Translations/prince/trainer.j2l.h:8 msgctxt "prince/trainer" msgid "" "\n" "Collect gems for \n" "an extra life." msgstr "" "\n" "Mücevherleri toplayarak\n" "ekstra can kazanabilirsin." #: .fake/Translations/prince/trainer.j2l.h:9 msgctxt "prince/trainer" msgid "" "\n" "Red Gems count\n" "as one gem." msgstr "" "\n" "Kırmızı mücevherler\n" "bir mücevher'e eşittir." #: .fake/Translations/prince/trainer.j2l.h:10 msgctxt "prince/trainer" msgid "" "\n" "Green gems count\n" "as five gems." msgstr "" "\n" "Yeşil mücevherler\n" "beş mücevher'e eşittir." #: .fake/Translations/prince/trainer.j2l.h:11 msgctxt "prince/trainer" msgid "" "\n" "Blue gems count\n" "as ten gems." msgstr "" "\n" "Mavi mücevherler de\n" "on mücevher'e eşittir." #: .fake/Translations/prince/trainer.j2l.h:12 msgctxt "prince/trainer" msgid "" "\n" "Carrots give you health." msgstr "" "\n" "Havuçlar sana can verir." #: .fake/Translations/prince/trainer.j2l.h:13 msgctxt "prince/trainer" msgid "" "\n" "Checkpoints save your\n" "spot if you lose a life." msgstr "" "\n" "Kontrol noktaları, ölmen\n" "halinde seni geri getirir." #: .fake/Translations/prince/trainer.j2l.h:14 msgctxt "prince/trainer" msgid "" "\n" "Collect coins to\n" "unlock bonus rooms." msgstr "" "\n" "Jetonları toplayarak\n" "gizli odaları açabilirsin." #: .fake/Translations/prince/trainer.j2l.h:15 msgctxt "prince/trainer" msgid "" "\n" "Beware of sharp stuff.\n" "It hurts." msgstr "" "\n" "Keskin şeylere dikkat et.\n" "Canını acıtır." #: .fake/Translations/prince/trainer.j2l.h:16 msgctxt "prince/trainer" msgid "" "\n" "Now youre ready to play.\n" " Good luck and have fun." msgstr "" "\n" "Şimdi oynamaya hazırsın.\n" "Bol şans ve iyi eğlenceler." #: .fake/Translations/rescue/01_colon1.j2l.h:1 msgctxt "rescue/01_colon1" msgid "" "\n" "Buttstomp the manhole cover!" msgstr "" "\n" "Rögar kapağını ez!" #: .fake/Translations/rescue/03_psych1.j2l.h:1 msgctxt "rescue/03_psych1" msgid "" "\n" "Smoke rings will \n" "make you dizzy." msgstr "" "\n" "Duman halkaları\n" "başını döndürür." #: .fake/Translations/secretf/01_easter1.j2l.h:1 msgctxt "secretf/01_easter1" msgid "" "\n" "You can't buttstomp\n" "so go up and around!" msgstr "" "\n" "Burada ezme hareketini \n" "yapamazsın. Yukarıdan devam et!" #: .fake/Translations/secretf/01_easter1.j2l.h:2 msgctxt "secretf/01_easter1" msgid "" "\n" "No rewards to those\n" "with itchy trigger fingers." msgstr "" "\n" "Aceleci davrananlara\n" "ödül yok." #: .fake/Translations/secretf/01_easter1.j2l.h:3 msgctxt "secretf/01_easter1" msgid "" "\n" "Todays Forcast: Strong Winds!" msgstr "" "\n" "Bugünün Hava Durumu: Güçlü Rüzgarlar!" #: .fake/Translations/secretf/01_easter1.j2l.h:4 msgctxt "secretf/01_easter1" msgid "" "\n" "Find the crate\n" "to clear your path." msgstr "" "\n" "Yolu açmak için\n" "kutuyu bul." #: .fake/Translations/secretf/01_easter1.j2l.h:5 msgctxt "secretf/01_easter1" msgid "" "\n" "Welcome to\n" "Jazz Jackrabbit 2:\n" "The Secret Files!" msgstr "" "\n" "Jazz Jackrabbit 2:\n" "The Secret Files'a hoş geldin!" #: .fake/Translations/secretf/01_easter1.j2l.h:6 msgctxt "secretf/01_easter1" msgid "" "\n" "Only Spaz can get to\n" "the room up on the left.\n" "He may need something\n" "to stand on." msgstr "" "\n" "Sol üstteki odaya sadece\n" "Spaz ulaşabilir. Üstünde durması \n" "gereken bir şeye ihtiyacı olabilir." #: .fake/Translations/secretf/01_easter1.j2l.h:7 msgctxt "secretf/01_easter1" msgid "" "\n" "Don't beat Nigel at pool.\n" "You've veen warned. :)" msgstr "" "\n" "Nigel'ı bilardoda yenme.\n" "Ben uyarımı yapayım. :)" #: .fake/Translations/secretf/01_easter1.j2l.h:16 msgctxt "secretf/01_easter1" msgid "" "Eating too many chocolate\n" "eggs can make you sick :p" msgstr "" "Çok fazla çikolatalı yumurta\n" "yemek seni rahatsız eder :p" #: .fake/Translations/secretf/02_easter2.j2l.h:1 msgctxt "secretf/02_easter2" msgid "" "\n" "Sloping Tunnel Entrance" msgstr "" "\n" "Eğimli Tünel Girişi" #: .fake/Translations/secretf/02_easter2.j2l.h:2 msgctxt "secretf/02_easter2" msgid "" "\n" "To access the tunnels above\n" "find the access warp." msgstr "" "\n" "Yukarıdaki tünele ulaşabilmek için\n" "oraya ışınlanılacak yeri bul." #: .fake/Translations/secretf/02_easter2.j2l.h:3 msgctxt "secretf/02_easter2" msgid "" "\n" "One route leads to riches.\n" "One route leads to battle." msgstr "" "\n" "Yollardan biri zenginliğe götürür.\n" "Diğeri ise savaşa götürür." #: .fake/Translations/secretf/03_easter3.j2l.h:1 msgctxt "secretf/03_easter3" msgid "" "\n" "Only those who can double-jump\n" "can get to the goodies!" msgstr "" "\n" "Sadece iki kere zıplayabilenler\n" "ödüllere erişebilir!" #: .fake/Translations/secretf/03_easter3.j2l.h:2 msgctxt "secretf/03_easter3" msgid "" "\n" "Find the crate to make\n" "your climbing blocks appear" msgstr "" "\n" "Tırmanma bloklarını\n" "ortaya çıkartacak kutuyu bul" #: .fake/Translations/secretf/03_easter3.j2l.h:3 msgctxt "secretf/03_easter3" msgid "" "\n" "Stomping this crate also\n" "free's some enemies :)" msgstr "" "\n" "Bu kutuyu ezmek aynı zamanda\n" "bazı düşmanları ortaya çıkarır :)" #: .fake/Translations/secretf/04_haunted1.j2l.h:1 msgctxt "secretf/04_haunted1" msgid "" "\n" "Enter the house with caution....." msgstr "" "\n" "Eve dikkatlice gir....." #: .fake/Translations/secretf/04_haunted1.j2l.h:3 msgctxt "secretf/04_haunted1" msgid "" "\n" "Silver Crates can't be broken underwater..." msgstr "" "\n" "Suyun altında Gümüş Kutuları kıramazsın..." #: .fake/Translations/secretf/04_haunted1.j2l.h:4 msgctxt "secretf/04_haunted1" msgid "" "\n" "Water Level control crate above." msgstr "" "\n" "Su seviyesini kontrol eden kutu\n" "yakınlarda bir yerde." #: .fake/Translations/secretf/04_haunted1.j2l.h:5 msgctxt "secretf/04_haunted1" msgid "" "\n" "Stomping crates can be good and bad..." msgstr "" "\n" "Kutuları ezmek hem iyi hem de kötü olabilir..." #: .fake/Translations/secretf/04_haunted1.j2l.h:7 msgctxt "secretf/04_haunted1" msgid "" "\n" "But you need a way to get up there..." msgstr "" "\n" "Ama yukarıya gitmek için bir yola ihtiyacın var.." #: .fake/Translations/secretf/06_haunted3.j2l.h:16 msgctxt "secretf/06_haunted3" msgid "" "\n" "Michelle,\n" "I will love you always and forever :)" msgstr "" "\n" "Michelle,\n" "Seni her zaman ve sonsuza dek seveceğim :)" #: .fake/Translations/secretf/07_town1.j2l.h:1 msgctxt "secretf/07_town1" msgid "" "\n" "Take to the roof tops!" msgstr "" "\n" "Çatıya çık!" #: .fake/Translations/secretf/07_town1.j2l.h:2 msgctxt "secretf/07_town1" msgid "" "\n" "The skies above will reward\n" "those who stomp..." msgstr "" "\n" "Gökyüzü, ezenleri ödüllendirecek..." #: .fake/Translations/secretf/07_town1.j2l.h:3 msgctxt "secretf/07_town1" msgid "" "\n" "Jump as far over to the\n" "right as you possibly can..." msgstr "" "\n" "Mümkün olduğu kadar\n" "sağa doğru atla..." #: .fake/Translations/secretf/07_town1.j2l.h:4 msgctxt "secretf/07_town1" msgid "" "\n" "Didn't make the jump huh? :)" msgstr "" "\n" "Zıplayamadın di'mi? :)" #: .fake/Translations/secretf/07_town1.j2l.h:5 msgctxt "secretf/07_town1" msgid "" "\n" "Well Done!" msgstr "" "\n" "Aferin!" #: .fake/Translations/secretf/07_town1.j2l.h:6 msgctxt "secretf/07_town1" msgid "" "\n" "Use your Special Moves to get up the air cons!\n" "For Jazz, press Crouch and Jump.\n" "For Spaz, Press Jump Twice!" msgstr "" "\n" "Havalandırmaya çıkmak için Özel Hareketini kullan!\n" "Jazz isen, önce Eğil ve sonra Zıpla.\n" "Spaz isen, iki kere Zıpla!" #: .fake/Translations/secretf/08_town2.j2l.h:1 msgctxt "secretf/08_town2" msgid "" "\n" "Find the crate and the gems are yours!" msgstr "" "\n" "Kutuyu bul ve mücevherler senin olsun!" #: .fake/Translations/secretf/08_town2.j2l.h:2 msgctxt "secretf/08_town2" msgid "" "\n" "Springs Don't Work When Frozen..." msgstr "" "\n" "Yaylar Donukken Çalışmaz..." #: .fake/Translations/secretf/08_town2.j2l.h:3 msgctxt "secretf/08_town2" msgid "" "\n" "Collecting 20 coins is more rewarding..." msgstr "" "\n" "20 adet jeton toplamak daha ödüllendirici..." #: .fake/Translations/secretf/09_town3.j2l.h:1 msgctxt "secretf/09_town3" msgid "" "\n" "Find the crate to clear the blocks...." msgstr "" "\n" "Blokları temizlemek için kutuyu bul...." #: .fake/Translations/secretf/09_town3.j2l.h:2 msgctxt "secretf/09_town3" msgid "" "\n" "BEWARE! Flocks of Ravens can\n" "be very dangerous." msgstr "" "\n" "DİKKAT! Bir grup karga çok\n" "tehlikeli olabilir." #: .fake/Translations/secretf/09_town3.j2l.h:3 msgctxt "secretf/09_town3" msgid "" "\n" "The remove the blocks\n" "look to the tallest building." msgstr "" "\n" "Blokları temizlemek için\n" "en uzun binaya doğru bak." #: .fake/Translations/secretf/09_town3.j2l.h:4 msgctxt "secretf/09_town3" msgid "" "\n" "Choose a cover and stomp away!" msgstr "" "\n" "Saklanacak yer bul ve ezmeye başla!" #: .fake/Translations/secretf/09_town3.j2l.h:5 msgctxt "secretf/09_town3" msgid "" "\n" "Hi GeoBunny :)" msgstr "" "\n" "Merhaba GeoBunny :)" #: .fake/Translations/secretf/09_town3.j2l.h:6 msgctxt "secretf/09_town3" msgid "" "\n" "Goto www.project2.com\n" "use\n" "password: BUNNYLOVER\n" msgstr "" "\n" "www.project2.com adresine git\n" "ve\n" "BUNNYLOVER şifresini kullan\n" #: .fake/Translations/share/01_share1.j2l.h:1 msgctxt "share/01_share1" msgid "" "\n" "Shoot these blocks!" msgstr "" "\n" "Bloklara ateş et!" #: .fake/Translations/share/01_share1.j2l.h:2 msgctxt "share/01_share1" msgid "" "\n" "When in the air, press down\n" "to stomp with your butt." msgstr "" "\n" "Zıpladıktan sonra aşağıya basarak\n" "yeri ezebilirsin." #: .fake/Translations/share/01_share1.j2l.h:3 msgctxt "share/01_share1" msgid "" "\n" "To pass this area, stomp\n" "the secret metal crate." msgstr "" "\n" "Burayı geçebilmek için\n" "metal kutuyu ez." #: .fake/Translations/share/01_share1.j2l.h:4 msgctxt "share/01_share1" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Bu ışınlanma cihazından geçebilmek için\n" "20 adet jetona ihtiyacın var!" #: .fake/Translations/share/01_share1.j2l.h:5 msgctxt "share/01_share1" msgid "" "\n" "Coins give you access to \n" "warps that appear later." msgstr "" "\n" "Jetonlar senin ışınlanma\n" "cihazlarından geçmeni sağlar." #: .fake/Translations/share/01_share1.j2l.h:6 msgctxt "share/01_share1" msgid "" "\n" "Stomp in the right place and\n" "you might find a surprise!" msgstr "" "\n" "Doğru yeri ezersen\n" "belki bir sürpriz bulabilirsin!" #: .fake/Translations/share/01_share1.j2l.h:7 msgctxt "share/01_share1" msgid "" "\n" "Some crates contain\n" "bombs or baddies!" msgstr "" "\n" "Bazı kutularda bombalar\n" "ve daha kötü şeyler olabilir!" #: .fake/Translations/share/02_share2.j2l.h:1 msgctxt "share/02_share2" msgid "" "\n" "You need twenty coins to pass \n" "through this secret warp!" msgstr "" "\n" "Bu gizli ışınlanma cihazından geçebilmek için \n" "20 adet jetona ihtiyacın var!" #: .fake/Translations/share/02_share2.j2l.h:2 msgctxt "share/02_share2" msgid "" "\n" "A flamethrower works well \n" "against nasty bugs." msgstr "" "\n" "Alev makinesi,\n" "böceklere karşı iyi çalışır." #: .fake/Translations/share/02_share2.j2l.h:3 msgctxt "share/02_share2" msgid "" "\n" "Smoke rings will make\n" "you very dizzy!" msgstr "" "\n" "Duman halkaları\n" "başını döndürür!" #: .fake/Translations/share/03_share3.j2l.h:1 msgctxt "share/03_share3" msgid "" "\n" "Beware the witch! She can\n" "turn you into a frog." msgstr "" "\n" "Cadıya dikkat et! Seni bir\n" "kurbağaya çevirebilir." #: .fake/Translations/share/03_share3.j2l.h:2 msgctxt "share/03_share3" msgid "" "\n" "If you are turned into a frog\n" "Eva Earlong can help!" msgstr "" "\n" "Eğer kurbağaya dönüştüysen\n" "Eva Earlong sana yardımcı olabilir!" #: .fake/Translations/share/03_share3.j2l.h:3 msgctxt "share/03_share3" msgid "" "\n" "You made it! This is the end\n" " of the shareware version.\n" "Now check out the order info\n" "for M O R E!" msgstr "" "\n" "Başardın! Bu demo sürümünün\n" "sonuydu. Daha fazlası için ana menüden\n" "tam sürümü oynayabilirsin!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:1 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Watch out for the spikes below!" msgstr "" "\n" "Aşağıdaki iğnelere dikkat et!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:2 msgctxt "xmas99/01_xmas1" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Yolundaki bazı hassas yerleri\n" "ezebilirsin!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:3 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Some blocks can only\n" "be broken with a\n" "certain weapon." msgstr "" "\n" "Bazı bloklar spesifik\n" "silahlarla kırılabilir." #: .fake/Translations/xmas99/01_xmas1.j2l.h:4 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Welcome to Christmas Chronicles!" msgstr "" "\n" "Christmas Chronicles'a hoş geldin!" #: .fake/Translations/xmas99/01_xmas1.j2l.h:5 msgctxt "xmas99/01_xmas1" msgid "" "\n" "Seasons Greetings from\n" "Epic MegaGames\n" "Orange Games and\n" "Project 2 Interactive!" msgstr "" "\n" "Epic MegaGames\n" "Orange Games ve\n" "Project 2 Interactive'den\n" "İyi Noeller!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:1 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can stand on top\n" "of some of the trees!" msgstr "" "\n" "Bazı ağaçların üzerinde\n" "durabilirsin!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:2 msgctxt "xmas99/02_xmas2" msgid "" "\n" "You can buttstomp through\n" "some of the weakspots\n" "in the pathways!" msgstr "" "\n" "Yolundaki bazı hassas yerleri\n" "ezebilirsin!" #: .fake/Translations/xmas99/02_xmas2.j2l.h:3 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Punching the blocks above you\n" "can be rewarding." msgstr "" "\n" "Yukardaki blokları ezmek\n" "ödüllendirici olabilir." #: .fake/Translations/xmas99/02_xmas2.j2l.h:6 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entry Crate." msgstr "" "\n" "Mücevher İzi Giriş Kutusu." #: .fake/Translations/xmas99/02_xmas2.j2l.h:7 msgctxt "xmas99/02_xmas2" msgid "" "\n" "Gem Trail Entrance.\n" "Climb the treetops to find\n" "and stomp the Entry Crate." msgstr "" "\n" "Mücevher İzi Girişi.\n" "Giriş Sandığını bulup ezmek\n" "için ağaçların tepelerine tırman." #: .fake/Translations/xmas99/02_xmas2.j2l.h:14 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Hi There, Piggy!\n" "\n" "- Poopy" msgstr "" "\n" "\n" "Merhaba Domuzcuk!\n" "\n" "- Kakacık" #: .fake/Translations/xmas99/02_xmas2.j2l.h:15 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Michelle:\n" "You've changed my life in so many ways\n" "I dedicate this project to you.\n" "I love you so much." msgstr "" "\n" "\n" "Michelle:\n" "Hayatımı pek çok yönden değiştirdin.\n" "Bu projeyi sana ithaf ediyorum.\n" "Seni çok seviyorum." #: .fake/Translations/xmas99/02_xmas2.j2l.h:16 msgctxt "xmas99/02_xmas2" msgid "" "\n" "\n" "Kassi Nicole:\n" "A million things I long to say to you\n" "But my words always lead to the same ending\n" "I love you, I love you." msgstr "" "\n" "\n" "Kassi Nicole:\n" "Sana söylemek istediğim milyonlarca şey var\n" "Ama sözlerim hep aynı yola çıkıyor\n" "Seni Seviyorum, Seni Seviyorum." #: .fake/Translations/xmas99/03_xmas3.j2l.h:1 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Please use your TNT wisely." msgstr "" "\n" "Lütfen TNT'lerini dikkatli kullan." #: .fake/Translations/xmas99/03_xmas3.j2l.h:2 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Don't lose your grip!" msgstr "" "\n" "Sıkı tutun!" #: .fake/Translations/xmas99/03_xmas3.j2l.h:3 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Welcome to Burrowsville\n" "Please drive carefully." msgstr "" "\n" "Burrowsville'e hoş geldiniz.\n" "Lütfen dikkatli sürün." #: .fake/Translations/xmas99/03_xmas3.j2l.h:4 msgctxt "xmas99/03_xmas3" msgid "" "\n" "That bridge doesnt\n" "look too safe..." msgstr "" "\n" "O köprü pek güvenli\n" "gözükmüyor..." #: .fake/Translations/xmas99/03_xmas3.j2l.h:5 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Robert and Craig:\n" "Springs RULE! :)" msgstr "" "\n" "Robert ve Craig:\n" "Bahar EN İYİSİ! :)" #: .fake/Translations/xmas99/03_xmas3.j2l.h:6 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Now leaving Burrowsville\n" "Please visit again." msgstr "" "\n" "Burrowsville'den çıkıyorsunuz.\n" "Lütfen tekrar gelin." #: .fake/Translations/xmas99/03_xmas3.j2l.h:7 msgctxt "xmas99/03_xmas3" msgid "" "\n" "Password: xmasbunny\n" "Please visit\n" "www.project2.com" msgstr "" "\n" "Şifre: xmasbunny\n" "Lütfen www.project2.com\n" "adresini ziyaret edin" #: Sources/Jazz2/LevelHandler.cpp:162 Sources/Jazz2/LevelHandler.cpp:213 #, c++-format msgid "Level \"{}\" initialized" msgstr "\"{}\" bölümü başlatıldı" #. TRANSLATORS: Link to website under header text in About section #: Sources/Jazz2/LevelHandler.cpp:766 #: Sources/Jazz2/UI/Menu/AboutSection.cpp:145 msgid "For more information, visit the official website:" msgstr "Daha fazla bilgi için, resmi web sitesini ziyaret edin:" #: Sources/Jazz2/LevelHandler.cpp:2313 Sources/Jazz2/LevelHandler.cpp:2326 #: Sources/Jazz2/LevelHandler.cpp:2337 Sources/Jazz2/LevelHandler.cpp:2352 #: Sources/Jazz2/LevelHandler.cpp:2365 Sources/Jazz2/LevelHandler.cpp:2378 #: Sources/Jazz2/LevelHandler.cpp:2391 Sources/Jazz2/LevelHandler.cpp:2404 #: Sources/Jazz2/LevelHandler.cpp:2419 Sources/Jazz2/LevelHandler.cpp:2431 #: Sources/Jazz2/LevelHandler.cpp:2452 Sources/Jazz2/LevelHandler.cpp:2466 msgid "Cheats are not allowed in current context" msgstr "Mevcut bağlamda hile kullanılması yasaktır" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:287 msgid "" "\n" "\n" "The game will begin shortly!" msgstr "" "\n" "\n" "Oyun kısa süre içerisinde başlıyor!" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1469 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3439 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c], \f[c:#d0705d]{}\f[/c] tarafından yakıldı." #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:1486 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3442 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] was roasted by environment" msgstr "\f[c:#d0705d]{}\f[/c], çevre tarafından yakıldı" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2791 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3418 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] disconnected" msgstr "\f[c:#d0705d]{}\f[/c] ayrıldı" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:2943 #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:3416 #, c++-format msgid "\f[c:#d0705d]{}\f[/c] connected" msgstr "\f[c:#d0705d]{}\f[/c] bağlandı" #: Sources/Jazz2/Multiplayer/MpLevelHandler.cpp:5494 #, c++-format msgid "" "\n" "\n" "Winner is {}" msgstr "" "\n" "\n" "Kazanan {}" #: Sources/Jazz2/UI/InGameConsole.cpp:359 msgid "Unknown command" msgstr "Bilinmeyen komut" #. TRANSLATORS: Header text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:143 msgid "" "Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released " "in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare " "'98, The Secret Files and Christmas Chronicles). Also, it partially supports " "some features of JJ2+ extension." msgstr "" "1998 yılında çıkmış \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] oyununun yeniden " "düzenlenmiş halidir. Oyunun farklı versiyonlarını destekler (Shareware Demo, " "Holiday Hare '98, The Secret Files ve Christmas Chronicles). Aynı zamanda, " "JJ2+ eklentisinin bazı özelliklerini destekler." #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:147 msgid "Developers" msgstr "Yazılımcılar" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:149 msgid "Contributors" msgstr "Katkıda Bulunanlar" #. TRANSLATORS: Label in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:151 msgid "Translators" msgstr "Çevirmenler" #. TRANSLATORS: Footer text in About section #: Sources/Jazz2/UI/Menu/AboutSection.cpp:153 msgid "" "This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and " "following libraries:" msgstr "" "Bu proje değiştirilmiş \f[c:#9e7056]nCine\f[/c] motorunu ve şu kütüphaneleri " "kullanıyor:" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:61 #: Sources/Jazz2/UI/Menu/BeginSection.cpp:78 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:154 msgid "Play Story" msgstr "Hikaye Modu" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:64 msgid "Play Shareware Demo" msgstr "Deneme Sürümünü Oyna" #. TRANSLATORS: Menu item in main menu (Emscripten only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:69 #: Sources/Jazz2/UI/Menu/ImportSection.cpp:68 msgid "Import Episodes" msgstr "Bölümleri İçe Aktar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:74 msgid "Continue" msgstr "Devam Et" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:83 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:40 msgid "Play Online" msgstr "Çevrimiçi Oyna" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:89 msgid "Highscores" msgstr "Yüksek puanlar" #. TRANSLATORS: Menu item in main menu #. TRANSLATORS: Subheader in First Run section #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:91 #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:46 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:40 msgid "Options" msgstr "Ayarlar" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:93 msgid "About" msgstr "Hakkında" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/BeginSection.cpp:100 msgid "Quit" msgstr "Çıkış" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:202 #, c++-format msgid "For more information, visit {} and  Discord!" msgstr "Daha fazla bilgi için {} ve  Discord'u ziyaret edin!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:215 msgid "Access to external storage has been granted!" msgstr "Harici depolamaya erişim izni verildi!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:217 msgid "" "\f[c:#337233]Restart the game to read \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#337233] files correctly." msgstr "" "\f[c:#9e7056]Jazz Jackrabbit 2\f[c:#337233] dosyalarını okumak için oyunu " "tekrar başlatın." #: Sources/Jazz2/UI/Menu/BeginSection.cpp:227 msgid "" "\f[c:#704a4a]This game requires original \f[c:#9e7056]Jazz Jackrabbit " "2\f[c:#704a4a] files!" msgstr "" "\f[c:#704a4a]Bu oyun orijinal \f[c:#9e7056]Jazz Jackrabbit 2\f[c:#704a4a] " "dosyalarını gerektirir!" #: Sources/Jazz2/UI/Menu/BeginSection.cpp:229 msgid "Make sure Jazz Jackrabbit 2 files are present in following path:" msgstr "Jazz Jackrabbit 2 dosyalarının şurada olduğundan emin ol:" #. TRANSLATORS: Menu item in main menu (Android 11+ only) #: Sources/Jazz2/UI/Menu/BeginSection.cpp:237 msgid "Allow access to external storage" msgstr "Harici depolama erişimine izin ver" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:23 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:165 #, c++-format msgid "Remap Controls for Player {}" msgstr "Oyuncu {} İçin Kontrolleri Değiştir" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:27 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:168 msgid "Remap Controls" msgstr "Kontrolleri Değiştir" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:47 msgid "Touch Controls" msgstr "Dokunmatik Kontrol Ayarları" #. TRANSLATORS: Menu item in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:32 msgid "Toggle Run" msgstr "Sürekli Koşma" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:33 msgid "Gamepad Button Labels" msgstr "Gamepad Tuşu İkonları" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:35 msgid "Gamepad Rumble" msgstr "Gamepad Titreşim Seviyesi" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:38 msgid "Extended PlayStation™ Support" msgstr "Gelişmiş Playstation™ Desteği" #. TRANSLATORS: Menu item in Options > Controls section (Android only) #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:42 msgid "Native Back Button" msgstr "Geri Tuşu" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:44 #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:73 msgid "Input Diagnostics" msgstr "Giriş Tanılama" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:45 msgid "Reset To Default" msgstr "Varsayılana Sıfırla" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:78 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:28 msgid "Controls" msgstr "Kontrol Ayarları" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:126 msgid "Strong" msgstr "Güçlü" #. TRANSLATORS: Option for Gamepad Rumble in Options > Controls section #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:129 msgid "Weak" msgstr "Zayıf" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:130 #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Disabled" msgstr "Kapalı" #: Sources/Jazz2/UI/Menu/ControlsOptionsSection.cpp:142 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:128 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:133 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:153 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:156 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:162 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:382 msgid "Enabled" msgstr "Açık" #. TRANSLATORS: Menu item to select player character (Jazz, Spaz, Lori) #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:36 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:98 msgid "Character" msgstr "Karakter" #. TRANSLATORS: Menu item to select game mode #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:38 msgid "Game Mode" msgstr "Oyun Modu" #. TRANSLATORS: Menu item to create server with selected settings #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:40 msgid "Create Server" msgstr "Sunucu Yarat" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:216 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:20 msgid "Battle" msgstr "Savaş" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:217 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:22 msgid "Team Battle" msgstr "Takım Savaşı" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:218 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:24 msgid "Race" msgstr "Yarış" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:219 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:26 msgid "Team Race" msgstr "Takım Yarışı" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:220 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:28 msgid "Treasure Hunt" msgstr "Hazine Avı" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:221 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:30 msgid "Team Treasure Hunt" msgstr "Takım Hazine Avı" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:222 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:32 msgid "Capture The Flag" msgstr "Bayrak Kapmaca" #. TRANSLATORS: Menu item in Select Game Mode section #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:223 #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:34 msgid "Cooperation" msgstr "Eşli Oyun" #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:368 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Playlist is not properly configured.\n" "Please review server configuration and try it again." msgstr "" "\f[c:#704a4a]Sunucu kurulamıyor!\f[/c]\n" "\n" "\n" "Çalma listesi düzgün yapılandırılmamış.\n" "Lütfen sunucu yapılandırmasını gözden geçirip tekrar deneyin." #: Sources/Jazz2/UI/Menu/CreateServerOptionsSection.cpp:407 msgid "" "\f[c:#704a4a]Cannot create the server!\f[/c]\n" "\n" "\n" "Please verify that no other server\n" "is running on that port and try it again." msgstr "" "\f[c:#704a4a]Sunucu kurulamıyor!\f[/c]\n" "\n" "\n" "Lütfen o portta başka bir sunucunun\n" "çalışmadığını doğrulayın ve tekrar deneyin." #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:156 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:482 msgid "Play Custom Levels" msgstr "Özel Bölüm Seç" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:169 msgid "No custom level found!" msgstr "Özel bölüm bulunamadı!" #: Sources/Jazz2/UI/Menu/CustomLevelSelectSection.cpp:191 msgid "Create server from playlist" msgstr "Çalma listesinden sunucu oluştur" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:24 msgid "Create Private Server" msgstr "Özel Sunucu Oluştur" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:152 #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:22 msgid "Create Public Server" msgstr "Genel Sunucu Oluştur" #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:164 msgid "No episode found!" msgstr "Hiçbir bölüm bulunamadı!" #. TRANSLATORS: Menu subitem in Play Story section #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:209 #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:210 msgid "Restart episode" msgstr "Bölümü Tekrarla" #. TRANSLATORS: Information in Play Story section that episode is locked because the previous episode is not complete #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:230 #, c++-format msgid "You must complete \"{}\" first!" msgstr "Önce \"{}\" bölümünü bitirmelisin!" #. TRANSLATORS: Information in Play Story section that episode is locked #: Sources/Jazz2/UI/Menu/EpisodeSelectSection.cpp:234 msgid "Episode is locked!" msgstr "Bu bölüm kilitli!" #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "Legacy" msgstr "Orijinal" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:17 msgid "I want to play the game the way it used to be." msgstr "Oyunu geçmişte olduğu gibi oynamak istiyorum." #. TRANSLATORS: Menu item in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "Reforged" msgstr "Yenilenmiş" #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:19 msgid "I want to play the game with something new." msgstr "Oyunu yenilenmiş haliyle oynamak istiyorum." #. TRANSLATORS: Header in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:55 msgid "Welcome to \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] reimplementation!" msgstr "Yenilenmiş \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] deneyimine hoş geldin!" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:94 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:20 msgid "Gameplay" msgstr "Oyun Ayarları" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:72 #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:40 msgid "Enhancements" msgstr "Geliştirmeler" #. TRANSLATORS: Subheader in First Run section #: Sources/Jazz2/UI/Menu/FirstRunSection.cpp:59 #, c++-format msgid "" "You can choose your preferred play style.\n" "This option can be changed at any time in \f[c:#707070]{}\f[/c] > " "\f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c].\n" "For more information, visit {} and  Discord!" msgstr "" "Sana uygun olan oynanış biçimini seçebilirsin.\n" "Bu ayar \f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/c] > \f[c:#707070]{}\f[/" "c]'da değiştirilebilir.\n" "Daha fazla bilgi için, {}'i veya  Discord sunucumuzu ziyaret edebilirsin!" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:18 msgid "Reforged Gameplay" msgstr "Yenilenmiş Oynanış" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:20 msgid "Reforged HUD" msgstr "Yenilenmiş Arayüz" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:22 msgid "Reforged Main Menu" msgstr "Yenilenmiş Ana Menü" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:24 msgid "Ledge Climbing" msgstr "Çıkıntılara Tırmanma" #. TRANSLATORS: Menu item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:26 msgid "Weapon Wheel" msgstr "Silah Tekerleği" #. TRANSLATORS: Header in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:76 msgid "You can enable enhancements that were added to this remake." msgstr "Bu versiyona eklenmiş geliştirmeleri aktive edebilirsin." #. TRANSLATORS: Option for Weapon Wheel item in Options > Gameplay > Enhancements section #: Sources/Jazz2/UI/Menu/GameplayEnhancementsSection.cpp:127 msgid "Enabled With Ammo Count" msgstr "Mermi ile Etkin" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:42 #: Sources/Jazz2/UI/Menu/LanguageSelectSection.cpp:43 msgid "Language" msgstr "Dil Ayarları" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:45 msgid "Scripting" msgstr "Komutlama" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:48 #| msgid "Continue" msgid "Continuous Jump" msgstr "" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:50 msgid "Switch To New Weapon" msgstr "Yeni Silaha Geç" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:52 msgid "Allow Cheats" msgstr "Hilelere İzin Ver" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:54 msgid "Overwrite Episode Completion" msgstr "Bölümün üzerine yaz" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:58 msgid "Razer Chroma™" msgstr "Razer Chroma™" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:62 msgid "Browse \"Source\" Directory" msgstr "\"Source\" Dizinine Gözat" #. TRANSLATORS: Menu item in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:67 #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:76 msgid "Refresh Cache" msgstr "Önbelleği Yenile" #. TRANSLATORS: Option for Allow Cheats in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "Yes" msgstr "Evet" #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:134 msgid "No" msgstr "Hayır" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:138 msgid "No Cheats Only" msgstr "Sadece hilesiz" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:141 msgid "Higher Score Only" msgstr "Sadece en yüksek skor" #. TRANSLATORS: Option for Overwrite Episode Completion in Options > Gameplay section #: Sources/Jazz2/UI/Menu/GameplayOptionsSection.cpp:143 msgid "Always" msgstr "Hep" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:24 msgid "Rescale Mode" msgstr "Ölçeklendirme Ayarları" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:26 msgid "Resolution" msgstr "Çözünürlük" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:34 msgid "Fullscreen" msgstr "Tam ekran" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:38 msgid "Antialiasing" msgstr "Kenar Yumuşatma" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:40 msgid "Background Dithering" msgstr "Arkaplan Titremesi" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:42 msgid "Water Quality" msgstr "Su Kalitesi" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:44 msgid "Show Player Trails" msgstr "Karakter İzini Göster" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:46 msgid "Preferred Splitscreen" msgstr "Tercih Edilen Bölünmüş Ekran Modu" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:48 msgid "Prefer Zoom Out" msgstr "Uzaklaştırmayı Ayarla" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:50 msgid "Keep Aspect Ratio In Cinematics" msgstr "Sinematiklerde En-Boy Oranını Koru" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:52 msgid "Unaligned Viewport" msgstr "Hizalanmamış Görünüm" #. TRANSLATORS: Menu item in Options > Graphics section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:54 msgid "Performance Metrics" msgstr "Performans Değerleri" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:89 #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:22 msgid "Graphics" msgstr "Grafik Ayarları" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "Low" msgstr "Düşük" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:148 msgid "High" msgstr "Yüksek" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Vertical" msgstr "Dikey" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:150 msgid "Horizontal" msgstr "Yatay" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:153 msgid "Enabled \f[c:#d0705d](Experimental)\f[/c]" msgstr "Etkin \f[c:#d0705d](Deneysel)\f[/c]" #. TRANSLATORS: Reserved for later use #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Short" msgstr "Kısa" #: Sources/Jazz2/UI/Menu/GraphicsOptionsSection.cpp:158 msgid "Detailed" msgstr "Detaylı" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:129 msgid "Highscores for \f[c:#d0705d]Base game\f[/c]" msgstr "\f[c:#d0705d]Ana oyun\f[/c] için yüksek puanlar" #: Sources/Jazz2/UI/Menu/HighscoresSection.cpp:139 #, c++-format msgid "Highscores for \f[c:#d0705d]{}\f[/c]" msgstr "\f[c:#d0705d]{}\f[/c] için yüksek puanlar" #. TRANSLATORS: Header in Import Episodes section #: Sources/Jazz2/UI/Menu/ImportSection.cpp:72 msgid "Select files of your original game to unlock additional episodes" msgstr "Ekstra bölümleri orijinal oyun dosyalarının içinden seçerek aç" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:78 #, c++-format msgid "Processing of {} file..." msgid_plural "Processing of {} files..." msgstr[0] "{} dosyası işleniyor..." msgstr[1] "{} dosyaları işleniyor..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:81 msgid "Waiting for files..." msgstr "Dosyalar bekleniyor..." #: Sources/Jazz2/UI/Menu/ImportSection.cpp:87 msgid "No files were selected!" msgstr "Hiçbir dosya seçilmedi!" #: Sources/Jazz2/UI/Menu/ImportSection.cpp:92 msgid "No new episodes were imported!" msgstr "Hiçbir yeni bölüm içeriye alınmadı!" #: Sources/Jazz2/UI/Menu/InputDiagnosticsSection.cpp:88 msgid "No gamepads are detected!" msgstr "Oyun kolu algılanmadı!" #: Sources/Jazz2/UI/Menu/MultiplayerGameModeSelectSection.cpp:65 msgid "Select Game Mode" msgstr "Oyun Modu Seç" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:25 #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:108 msgid "Sounds" msgstr "Ses Ayarları" #. TRANSLATORS: Menu item in Options section #: Sources/Jazz2/UI/Menu/OptionsSection.cpp:30 #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:168 msgid "User Profile" msgstr "Kullanıcı Profili" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:30 msgid "Resume" msgstr "Devam Et" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:35 msgid "Spectate" msgstr "İzle" #. TRANSLATORS: Menu item in main menu #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 #: Sources/Jazz2/UI/Menu/PauseSection.cpp:47 msgid "Save & Exit" msgstr "Kaydet & Çık" #: Sources/Jazz2/UI/Menu/PauseSection.cpp:44 msgid "Disconnect & Exit" msgstr "Bağlantıyı Kes ve Çık" #. TRANSLATORS: Menu item in Play Multiplayer section #: Sources/Jazz2/UI/Menu/PlayMultiplayerSection.cpp:20 #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:153 msgid "Connect To Server" msgstr "Sunucuya Bağlan" #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:79 msgid "Processing of files in \f[c:#9e7056]\"Source\"\f[/c] directory..." msgstr "[c:#9e7056]\"Source\"\f[/c] dizinindeki dosyalar işleniyor..." #: Sources/Jazz2/UI/Menu/RefreshCacheSection.cpp:82 msgid "Newly added levels and episodes will be available soon." msgstr "Yeni bölümler yakında erişilebilir hale gelecek." #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:19 msgid "Left" msgstr "Sol" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:21 msgid "Right" msgstr "Sağ" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:23 msgid "Up" msgstr "Yukarı" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:25 msgid "Down" msgstr "Aşağı" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:27 msgid "Buttstomp" msgstr "Popo vuruşu" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:29 msgid "Fire" msgstr "Ateş" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:31 msgid "Jump" msgstr "Zıpla" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:33 msgid "Run" msgstr "Koş" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:35 #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:182 msgid "Change Weapon" msgstr "Silah Değiştir" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:37 msgid "Back" msgstr "Geri" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:39 msgid "Toggle Console" msgstr "Konsolu Aç/Kapat" #. TRANSLATORS: Menu item in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:43 #, c++-format msgid "Weapon {}" msgstr "Silah {}" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:176 msgid "Press any key or button to assign" msgstr "Tuş atayın" #. TRANSLATORS: Bottom hint in Options > Controls > Remap Controls section, prefixed with key/button to press #: Sources/Jazz2/UI/Menu/RemapControlsSection.cpp:193 msgid "to remove assignment" msgstr "ile atanmış tuşu silin" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:15 msgid "None / Pixel-perfect" msgstr "Kapalı / Orijinal" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:23 msgid "CRT Scanlines" msgstr "CRT Çizgileri" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:25 msgid "CRT Shadow Mask" msgstr "CRT Gölge Maskesi" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:27 msgid "CRT Aperture Grille" msgstr "CRT Açıklık Izgarası" #. TRANSLATORS: Menu item in Options > Graphics > Rescale Mode section #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:30 msgid "Monochrome" msgstr "Tek Renk" #: Sources/Jazz2/UI/Menu/RescaleModeSection.cpp:55 msgid "Select Rescale Mode" msgstr "Ölçeklendirme Modunu Seçin" #: Sources/Jazz2/UI/Menu/ServerSelectSection.cpp:166 msgid "No servers found, but still searchin'!" msgstr "Sunucu bulunamadı, ama hala aranıyor!" #: Sources/Jazz2/UI/Menu/SimpleMessageSection.cpp:48 #: Sources/Jazz2/UI/Multiplayer/MpInGameLobby.cpp:144 msgid "Press \f[c:#d0705d]Fire\f[/c] to continue" msgstr "Devam etmek için \f[c:#d0705d]Ateş\f[/c] tuşuna bas" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:17 msgid "Master Volume" msgstr "Ana Ses Seviyesi" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:19 msgid "SFX Volume" msgstr "Ses Efektleri Seviyesi" #. TRANSLATORS: Menu item in Options > Sounds section #: Sources/Jazz2/UI/Menu/SoundsOptionsSection.cpp:21 msgid "Music Volume" msgstr "Müzik Seviyesi" #. TRANSLATORS: Menu item to select number of players #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:20 msgid "Number of Local Players" msgstr "Yerel Oyuncu Sayısı" #. TRANSLATORS: Menu item to select difficulty #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:22 msgid "Difficulty" msgstr "Zorluk" #. TRANSLATORS: Menu item to start selected episode/level #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:24 msgid "Start" msgstr "Oyuna Başla" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Easy" msgstr "Kolay" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Medium" msgstr "Orta" #: Sources/Jazz2/UI/Menu/StartGameOptionsSection.cpp:293 msgid "Hard" msgstr "Zor" #. TRANSLATORS: Header in Options > Controls > Touch Controls section #: Sources/Jazz2/UI/Menu/TouchControlsOptionsSection.cpp:51 msgid "You can adjust position of the touch zones by drag and drop." msgstr "" "Dokunma bölgelerini tutup bırakarak bulundukları pozisyonu ayarlayabilirsin." #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:67 msgid "Discord Integration" msgstr "Discord Entegrasyonu" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:70 msgid "Player Name" msgstr "Oyuncu Adı" #. TRANSLATORS: Menu item in Options > User Profile section #: Sources/Jazz2/UI/Menu/UserProfileOptionsSection.cpp:73 msgid "Unique Player ID" msgstr "Benzersiz Oyuncu Kimliği" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:171 #, c++-format msgid "Points: {}" msgstr "Puanlar: {}" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:185 #, c++-format msgid "Game starts in {}" msgstr "Oyun {} içinde başlıyor" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:192 #, c++-format msgid "Waiting for {} more player" msgid_plural "Waiting for {} more players" msgstr[0] "{} oyuncu daha bekleniyor" msgstr[1] "{} oyuncu daha bekleniyor" #: Sources/Jazz2/UI/Multiplayer/MpHUD.cpp:254 msgid "Find exit!" msgstr "Çıkışı bul!" #: Sources/Main.cpp:669 Sources/Main.cpp:730 msgid "" "\f[c:#704a4a]Cannot load specified level!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c0x804a4a]İstenilen bölüm yüklenemedi!\f[/c]\n" "\n" "\n" "Gerekli dosyaların erişilebilir\n" "olduğundan emin olun ve tekrar deneyin." #: Sources/Main.cpp:791 msgid "" "\f[c:#704a4a]Cannot resume saved state!\f[/c]\n" "\n" "\n" "Make sure all necessary files\n" "are accessible and try it again." msgstr "" "\f[c0x804a4a]İstenilen kayıt yüklenemedi!\f[/c]\n" "\n" "\n" "Gerekli dosyaların erişilebilir\n" "olduğundan emin olun ve tekrar deneyin." #: Sources/Main.cpp:955 msgid "Unnamed server" msgstr "İsimsiz sunucu" #: Sources/Main.cpp:1113 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid parameter specified." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Geçersiz parametre belirtildi." #: Sources/Main.cpp:1114 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client version is not compatible with the server." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Oyun sürümünüz sunucuyla uyumlu değil." #: Sources/Main.cpp:1115 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Authentication failed.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor! [/c]\\f[/c]\n" "\n" "\n" "Kimlik doğrulama başarısız oldu.\n" "Daha fazla bilgi için sunucu yöneticileriyle iletişime geçin." #: Sources/Main.cpp:1116 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid password specified." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Geçersiz parola belirtildi." #: Sources/Main.cpp:1117 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Invalid player name specified.\n" "Please check your profile and try it again." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Geçersiz oyuncu adı belirtildi.\n" "Lütfen profilinizi kontrol edin ve tekrar deneyin." #: Sources/Main.cpp:1118 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "This client is not in the server whitelist.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Bu istemci sunucu beyaz listede değil.\n" "Daha fazla bilgi için sunucu yöneticileriyle iletişime geçin." #: Sources/Main.cpp:1119 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server requires 3rd party authentication provider.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Sunucu 3. taraf kimlik doğrulama sağlayıcısı gerektiriyor.\n" "Daha fazla bilgi için sunucu yöneticileriyle iletişime geçin." #: Sources/Main.cpp:1120 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server capacity is full.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Kapasite dolu.\n" "Lütfen tekrar deneyin." #: Sources/Main.cpp:1121 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Server is not in a state where it can process your request.\n" "Please try again in a few seconds." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Sunucu isteğinizi gerçekleştirebilecek durumda değil.\n" "Lütfen daha sonra tekrar deneyin." #: Sources/Main.cpp:1122 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down.\n" "Please try it later." msgstr "" "\f[c:#704a4a]Bağlantı kapandı!\f[/c]\n" "\n" "\n" "Sunucu kapanıyor.\n" "Lütfen daha sonra tekrar deneyin." #: Sources/Main.cpp:1123 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for maintenance.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Bağlantı kapandı!\f[/c]\n" "\n" "\n" "Sunucu kapanıyor.\n" "Lütfen daha sonra tekrar deneyin." #: Sources/Main.cpp:1124 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for reconfiguration.\n" "Please try it again later." msgstr "" "\f[c:#704a4a]Bağlantı kapandı!\f[/c]\n" "\n" "\n" "Sunucu kapanıyor.\n" "Lütfen daha sonra tekrar deneyin." #: Sources/Main.cpp:1125 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Server is shutting down for update.\n" "Please check your client version and try it again in a minute." msgstr "" "\f[c:#704a4a]Bağlantı kapandı!\f[/c]\n" "\n" "\n" "Sunucu kapanıyor.\n" "Lütfen daha sonra tekrar deneyin." #: Sources/Main.cpp:1126 msgid "" "\f[c:#704a4a]Connection has been lost!\f[/c]\n" "\n" "\n" "Please try it again and if the problem persists,\n" "check your network connection." msgstr "" "\f[c:#704a4a]Bağlantı koptu!\f[/c]\n" "\n" "\n" "Lütfen daha sonra tekrar deneyin. Problem devam ediyorsa,\n" "internet bağlantınızı gözden geçirin." #: Sources/Main.cpp:1127 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "The server is not responding for connection request." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "Sunucu bağlantı isteğine cevap vermiyor." #: Sources/Main.cpp:1128 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#907050]kicked\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Bağlantı koptu!\f[/c]\n" "\n" "\n" "Sunucudan \f[c:#907050]atıldınız\f[/c].\n" "Daha fazla bilgi için sunucu yöneticileri ile iletişime geçin." #: Sources/Main.cpp:1129 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "You have been \f[c:#725040]banned\f[/c] off the server.\n" "Contact server administrators for more information." msgstr "" "\f[c:#704a4a]Bağlantı koptu!\f[/c]\n" "\n" "\n" "Sunucudan \f[c:#725040]yasaklandınız\f[/c].\n" "Daha fazla bilgi için sunucu yöneticileri ile iletişime geçin." #: Sources/Main.cpp:1130 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "Cheating detected." msgstr "" "\f[c:#704a4a]Bağlantı kapatıldı!\f[/c]\n" "\n" "\n" "Hile tespit edildi." #: Sources/Main.cpp:1131 msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain required assets.\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "İstemcinizde gerekli dosyalar bulunmuyor.\n" "Lütfen gerekli dosyaları indirin ve tekrar deneyin." #: Sources/Main.cpp:1132 msgid "" "\f[c:#704a4a]Connection has been closed!\f[/c]\n" "\n" "\n" "The server has disconnected you due to inactivity." msgstr "" "\f[c:#704a4a]Bağlantı kapatıldı!\f[/c]\n" "\n" "\n" "Sunucu, etkin olmadığınız için bağlantınızı kesti." #: Sources/Main.cpp:1387 #, c++-format msgid "" "\f[c:#704a4a]Cannot connect to the server!\f[/c]\n" "\n" "\n" "Your client doesn't contain level \"{}\".\n" "Please download the required files and try it again." msgstr "" "\f[c:#704a4a]Sunucuya bağlanılamıyor!\f[/c]\n" "\n" "\n" "İstemciniz \"{}\" bölümünü içermiyor.\n" "Lütfen gerekli dosyaları indirin ve tekrar deneyin." #~ msgid "Unknown" #~ msgstr "Bilinmiyor" #~ msgid "Play Custom Game" #~ msgstr "Özel Oyun Seç" #, c-format #~ msgid "Connecting to {}:{}..." #~ msgstr "{}:{}'ya bağlanıyor..." #~ msgid "Create Custom Server" #~ msgstr "Özel Sunucu Yarat" #~ msgid "Connect to Server" #~ msgstr "Sunucuya Bağlan" #~ msgid "or" #~ msgstr "ya da" #~ msgid "Creating server..." #~ msgstr "Sunucu yaratılıyor..." #~ msgid "Error" #~ msgstr "Hata" deathkiller-jazz2-native-2a7ccef/Docs/000077500000000000000000000000001512772601700200005ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Docs/.gitignore000066400000000000000000000000151512772601700217640ustar00rootroot00000000000000__pycache__/ deathkiller-jazz2-native-2a7ccef/Docs/Building.dox000066400000000000000000000354551512772601700222650ustar00rootroot00000000000000namespace Death { //###==##====#=====--==~--~=~- --- -- - - - - /** @page building Building the project @brief Guide how to build Jazz² Resurrection @tableofcontents @m_class{m-noindent} The project requires following tools and libraries to build it successfully: - **CMake** 3.15 or newer - C++ compiler with C++17 support --- any newer version of **GCC**, **Clang** and **MSVC** should work, **MinGW** and **Clang-CL** toolchains should be also supported - **OpenGL** 3.3, **OpenGL|ES** 3.0 or **WebGL** 2.0 library @m_span{m-text m-dim} (alternatively **ANGLE** or **Mesa** translation library) @m_endspan - **GLEW** library @m_span{m-text m-dim} (required only on **Windows**) @m_endspan - **GLFW** or **SDL2** library @m_span{m-text m-dim} (not required on **Android** and **UWP**, because these platforms use a different backend) @m_endspan - **libcurl** library @m_span{m-text m-dim} (not required on **Emscripten** and **Windows**, because these platforms use a different backend) @m_endspan - **zlib** library @m_class{m-noindent} In addition, these libraries are required for an optimal experience: - **OpenAL** library --- audio support - **libopenmpt** library --- module music playback - **libogg** / **libvorbis** library --- `.ogg` file support @m_span{m-text m-dim} (not needed for the original assets) @m_endspan - **libwebp** library --- `.webp` file support @m_span{m-text m-dim} (currently not supported, not needed for the original assets) @m_endspan - **AngelScript** library --- [AngelScript](https://www.angelcode.com/angelscript/) scripting support - **liblua** library --- [Lua](https://www.lua.org/) scripting support @m_span{m-text m-dim} (currently not supported) @m_endspan It tries to download or compile all libraries automatically, but in case of build errors a manual download is necessary. Also, system-wide libraries have priority over the bundled ones, so in case of any incompatibility just install the system libraries. @section building-getting-started How to get started Clone the repository using any Git client, IDE, or command line: @code{.sh} git clone https://github.com/deathkiller/jazz2-native.git @endcode @m_class{m-noindent} To configure CMake, following commands can be used: @code{.sh} mkdir build cmake -B build -D CMAKE_BUILD_TYPE=Debug -D NCINE_CREATE_CONTENT_SYMLINK=ON @endcode Alternatively, change `CMAKE_BUILD_TYPE` to `Release` to enable all performance optimizations and disable debugging. You can also specify build configuration parameters, which are described @ref building-config-params "below". In addition, you can use the `NCINE_CREATE_CONTENT_SYMLINK` parameter to automatically create a symbolic link to the @cpp "Content" @ce directory in the target directory, otherwise you would have to copy it to @cpp "build" @ce directory manually. To start actual build of the project, use following commands: @code{.sh} make -j $(nproc) -C build @endcode @m_class{m-noindent} Everything should be built into the @cpp "build" @ce directory and ready to go. @subsection building-android Building on Android Android build requires **Android SDK** and **NDK** installed, see [Get started with the NDK](https://developer.android.com/ndk/guides). Assembling APK files also requires **Gradle**. Path to **Gradle** can be supplied by `GRADLE_HOME` environment variable if not detected automatically. The build can be configured using a similar command as above: @code{.sh} mkdir build cmake -B build -D CMAKE_BUILD_TYPE=Debug \ -D NCINE_BUILD_ANDROID=ON \ -D NCINE_UNIVERSAL_APK=ON \ -D NCINE_NDK_ARCHITECTURES="arm64-v8a;armeabi-v7a" @endcode @m_class{m-noindent} See @ref building-config-params-android for more details. Then following commands can be used to build the project and assemble the APK file: @code{.sh} make -j $(nproc) -C build cd build gradle assembleDebug @endcode @m_class{m-noindent} Alternatively, replace `assembleDebug` with `assembleRelease` to create release APK file. @subsection building-windows Building on Windows On Windows, Visual Studio can be used. Using already included `.sln` is not recommended, because it requires manual configuration and doesn't support all features and parameters. Instead the project can be opened as CMake directory directly in Visual Studio, or it can be configured using a similar command as above: @code{.bat} mkdir build cmake -B build -D CMAKE_BUILD_TYPE=Debug -A x64 ^ -D CMAKE_SYSTEM_PROCESSOR=x64 ^ -D NCINE_CREATE_CONTENT_SYMLINK=ON @endcode @m_class{m-noindent} See @ref building-config-params-windows for more details. To build it for 32-bit operating system, use `Win32` instead of `x64`. To change version of VS toolset, use `-D CMAKE_GENERATOR_TOOLSET=…` parameter. By default CMake creates a new Visual Studio `.sln` solution and project files in @cpp "build" @ce directory that can be opened easily afterwards. Building with **Clang-CL** compiler is also possible specifying `-T ClangCL` parameter. @subsection building-uwp Building for Xbox (Universal Windows Platform) The same commands can be used as for @ref building-windows "Windows", but two additional parameters must be specified to change the target: @code{.bat} cmake -B build -D CMAKE_BUILD_TYPE=Debug -A x64 ^ -D CMAKE_SYSTEM_PROCESSOR=x64 ^ -D CMAKE_SYSTEM_NAME=WindowsStore ^ -D CMAKE_SYSTEM_VERSION="10.0" @endcode @m_class{m-noindent} Additionaly, a code-signing certificate is required to create an installable `.msixbundle` package. See @ref building-config-params-uwp for more details. - - - @subsection building-config-params Build configuration parameters By default it tries to find the first available backend for the currently installed libraries. **GLFW** is usually preferred over **SDL2**, because it's more lightweight. On the other hand, **SDL2** usually has better gamepad support and better support in general. The following parameters can be used to customize the build: - `CMAKE_BUILD_TYPE` --- Build configuration - Possible values: `Debug`, `Release` - `CMAKE_INSTALL_PREFIX` (default @cpp "/usr/local" @ce) --- Install prefix on **Unix** systems - `NCINE_CONTENT_DIR` (default @cpp "./Content" @ce) --- Path to the @cpp "Content" @ce game data directory - Some build targets include @cpp "Content" @ce directory directly inside the executable package (e.g., Android) - `NCINE_CREATE_CONTENT_SYMLINK` (default @cpp OFF @ce) --- Create symbolic link to the @cpp "Content" @ce game data directory in target directory - Ignored on **Android**, **Emscripten**, **Nintendo Switch** and **UWP** platforms - `NCINE_DOWNLOAD_DEPENDENCIES` (default @cpp ON @ce) --- Download all missing dependencies automatically - `NCINE_EMBED_SHADERS` (default @cpp ON @ce) --- Embed shader files in executable - `NCINE_PREFERRED_BACKEND` (default @cpp "GLFW" @ce) --- Preferred backend on desktop - Possible values: `GLFW`, `SDL2` - Ignored on **Android** and **UWP** platforms - `NCINE_VERSION_FROM_GIT` (default @cpp ON @ce) --- Set current game version from Git repository automatically - `NCINE_WITH_THREADS` (default @cpp ON @ce except on Emscripten) --- Allow to use multiple threads for better performance - `NCINE_WITH_OPENGLES` (default @cpp OFF @ce) --- Use **OpenGL|ES** library instead of **OpenGL** - `NCINE_WITH_ANGLE` (default @cpp OFF @ce except on UWP) --- Enable Google **ANGLE** library support - `NCINE_WITH_GLEW` (default @cpp ON @ce) --- Use **GLEW** library - `NCINE_WITH_BACKWARD` (default @cpp ON @ce except on Android, Emscripten and UWP) --- Enable better exception handling - `NCINE_WITH_WEBP` @m_class{m-label m-danger m-flat} **deprecated** --- Enable `.webp` image file support, requires **libwebp** library - `NCINE_WITH_AUDIO` (default @cpp ON @ce) --- Enable audio support, requires **OpenAL** library - `NCINE_WITH_VORBIS` (default @cpp ON @ce) --- Enable `.ogg` audio file support, requires **libvorbis** library - `NCINE_WITH_OPENMPT` (default @cpp ON @ce) --- Enable module music audio file support, requires **libopenmpt** library - `NCINE_COMPILE_OPENMPT` (default @cpp OFF @ce) --- Download and compile **libopenmpt** library from source automatically - `NCINE_WITH_ANGELSCRIPT` (default @cpp OFF @ce) --- Enable [AngelScript](https://www.angelcode.com/angelscript/) scripting support - `ANGELSCRIPT_VERSION_TAG` allows to specify the version to be downloaded if @cpp NCINE_DOWNLOAD_DEPENDENCIES @ce is enabled - `NCINE_WITH_LUA` @m_class{m-label m-danger m-flat} **deprecated** --- Enable [Lua](https://www.lua.org/) scripting support - `NCINE_WITH_IMGUI` (default @cpp OFF @ce) --- Enable integration with [Dear ImGui](https://github.com/ocornut/imgui) library - `IMGUI_VERSION_TAG` allows to specify the version to be downloaded @subsubsection building-config-params-android Platform-specific parameters for Android - `NCINE_BUILD_ANDROID` (default @cpp OFF @ce) --- Enable building for **Android** platform - `NCINE_ASSEMBLE_APK` (default @cpp ON @ce) --- Assemble Android APK file, requires with **Gradle** - `NCINE_NDK_ARCHITECTURES` (default @cpp "arm64-v8a" @ce) --- Semicolon-separated list of target CPU architectures - Possible values: `arm64-v8a` (for 64-bit ARM), `armeabi-v7a` (for 32-bit ARM), `x86`, `x86_64` - `NCINE_UNIVERSAL_APK` (default @cpp OFF @ce) --- Create universal APK containing all specified CPU architectures - `NDK_DIR` --- Android NDK directory, usually detected automatically @subsubsection building-config-params-linux Platform-specific parameters for Linux - `NCINE_ASSEMBLE_DEB` (default @cpp OFF @ce) --- Assemble DEB package of the game - `NCINE_ASSEMBLE_RPM` (default @cpp OFF @ce) --- Assemble RPM package of the game - `NCINE_BUILD_FLATPAK` (default @cpp OFF @ce) --- Build Flatpak version of the game - `NCINE_LINUX_PACKAGE` --- Override Linux package name, otherwise @cpp "Jazz² Resurrection" @ce will be used - `NCINE_OVERRIDE_CONTENT_PATH` --- Override @cpp "Content" @ce directory path - If not specified, following path will be used: @cpp CMAKE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Content/" @ce - `NCINE_PACKAGED_CONTENT_PATH` (default @cpp OFF @ce) --- Use alternative path search strategy - If enabled, @cpp "Content" @ce will be always relative to current directory - Has higher priority than `NCINE_OVERRIDE_CONTENT_PATH` @subsubsection building-config-params-windows Platform-specific parameters for Windows - `DEATH_WITH_VC_LTL` (default @cpp ON @ce) --- Build with **VC-LTL** for lighter binaries, requires [VC-LTL](https://github.com/Chuyu-Team/VC-LTL5) - `NCINE_COPY_DEPENDENCIES` (default @cpp ON @ce) --- Copy all required libraries to build target directory automatically @subsubsection building-config-params-uwp Platform-specific parameters for Universal Windows Platform - `NCINE_UWP_CERTIFICATE_THUMBPRINT` --- Code-signing certificate thumbprint - Use either `NCINE_UWP_CERTIFICATE_THUMBPRINT` or `NCINE_UWP_CERTIFICATE_PATH` - `NCINE_UWP_CERTIFICATE_PATH` (default @cpp "UwpCertificate.pfx" @ce) --- Code-signing certificate path - `NCINE_UWP_CERTIFICATE_PASSWORD` (optional) --- Code-signing certificate password for `NCINE_UWP_CERTIFICATE_PATH` @subsubsection building-config-params-adv Advanced parameters - `DEATH_CPU_USE_RUNTIME_DISPATCH` --- Build with runtime dispatch for CPU-dependent functionality - Uses code paths optimized for multiple architectures with the best-matching variant selected at runtime based on detected CPU features, see @ref Death::Cpu namespace - Enabled by default if `DEATH_CPU_USE_IFUNC` is supported - `DEATH_CPU_USE_IFUNC` (default @cpp ON @ce if supported) --- Allow using [GNU IFUNC](https://sourceware.org/glibc/wiki/GNU_IFUNC) for runtime CPU dispatch - `DEATH_DEBUG` --- Enable verbose logging and additional assertions for debugging - Enabled by default for `Debug` build configuration - `DEATH_DEBUG_SYMBOLS` --- Create debug symbols for executable - Separate `.pdb` file will be created on Windows and Linux, on other platforms the symbols will probably be embedded in the executable - Enabled by default on Windows platforms - `DEATH_TRACE` (default @cpp ON @ce) --- Enable runtime event tracing, see @ref Asserts.h file - `DEATH_TRACE_ASYNC` (default @cpp ON @ce if `NCINE_WITH_THREADS`) --- Enable asynchronous processing of event tracing for better performance - `DEATH_USE_RUNTIME_CAST` (default @cpp ON @ce) --- Enable @ref Death::runtime_cast() and type information optimization - `NCINE_ADDRESS_SANITIZER` (default @cpp OFF @ce) --- Enable `AddressSanitizer` memory error detector - `NCINE_ARCH_EXTENSIONS` --- Target CPU architecture extensions (instruction sets) - Depends on target CPU and compiler support - See documentation of `/arch` in MSVC ([docs](https://learn.microsoft.com/en-us/cpp/build/reference/arch-minimum-cpu-architecture?view=msvc-170)) and `-m` in GCC ([docs](https://gcc.gnu.org/onlinedocs/gcc-8.4.0/gcc/Submodel-Options.html)) for more details - `NCINE_CODE_COVERAGE` (default @cpp OFF @ce) --- Enable `gcov` instrumentation for testing code coverage - `NCINE_GCC_HARDENING` (default @cpp OFF @ce) --- Enable memory corruption mitigation methods of GCC - `NCINE_LINKTIME_OPTIMIZATION` (default @cpp ON @ce) --- Compile with link-time optimization - `NCINE_THREAD_SANITIZER` (default @cpp OFF @ce) --- Enable `ThreadSanitizer` detector - `NCINE_UNDEFINED_SANITIZER` (default @cpp OFF @ce) --- Enable `UndefinedBehaviorSanitizer` detector @subsubsection building-config-params-debug Debugging parameters - `NCINE_AUTOVECTORIZATION_REPORT` (default @cpp OFF @ce) --- Enable report generation from compiler auto-vectorization - `NCINE_INPUT_DEBUGGING` (default @cpp OFF @ce) --- Enable extensive (gamepad) input debugging and logging - `NCINE_PROFILING` (default @cpp OFF @ce) --- Enable profiling - `NCINE_STRIP_BINARIES` (default @cpp OFF @ce) --- Strip debug symbols from binaries for smaller size - `NCINE_WITH_FIXED_BATCH_SIZE` (default @cpp OFF @ce) --- Enable fixed batch size for rendering - `NCINE_WITH_RENDERDOC` @m_class{m-label m-danger m-flat} **deprecated** --- Enable integration with [RenderDoc](https://renderdoc.org/) - `NCINE_WITH_TRACY` (default @cpp OFF @ce) --- Enable integration with [Tracy](https://github.com/wolfpld/tracy) frame profiler - `TRACY_VERSION_TAG` allows to specify the version to be downloaded @subsubsection building-config-params-game Game-specific parameters - `DISABLE_RESCALE_SHADERS` (default @cpp OFF @ce) --- Disable rescale shaders and use only nearest neighbor - `SHAREWARE_DEMO_ONLY` (default @cpp OFF @ce) --- Shareware Demo only, usually used on **Emscripten** platform - `WITH_MULTIPLAYER` (default @cpp OFF @ce) --- Enable experimental online multiplayer support - `DEDICATED_SERVER` (default @cpp OFF @ce) --- Build the application as dedicated server only, `WITH_MULTIPLAYER` must be enabled */ } deathkiller-jazz2-native-2a7ccef/Docs/Config.py000066400000000000000000000036371512772601700215700ustar00rootroot00000000000000import re DOXYFILE = 'Doxyfile' MAIN_PROJECT_URL = 'https://deat.tk/jazz2/' SHOW_UNDOCUMENTED = True VERSION_LABELS = True SEARCH_DOWNLOAD_BINARY = True SEARCH_BASE_URL = "https://deat.tk/jazz2/docs/" SEARCH_EXTERNAL_URL = "https://google.com/search?q=site:deat.tk+jazz2+docs+{query}" STYLESHEETS = [ 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600&subset=latin-ext', '../css/m-dark+documentation.compiled.css' ] LINKS_NAVBAR2 = [ (None, 'annotated', []), (None, 'files', []) ] # Code wrapped in DOXYGEN_ELLIPSIS() will get replaced by an (Unicode) ellipsis # in the output; code wrapped in DOXYGEN_IGNORE() will get replaced by nothing. # In order to make the same code compilable, add # # #define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ # #define DOXYGEN_IGNORE(...) __VA_ARGS__ # # to the snippet code. def _doxygen_ignore(code: str): for macro, replace in [('DOXYGEN_ELLIPSIS(', '…'), ('DOXYGEN_IGNORE(', '')]: while macro in code: i = code.index(macro) depth = 1 for j in range(i + len(macro), len(code)): if code[j] == '(': depth += 1 elif code[j] == ')': depth -= 1 if depth == 0: break assert depth == 0, "unmatched %s) parentheses in %s" % (macro, code) code = code[:i] + replace + code[j+1:] return code # Highlighting of 0x…_rgb color literals and related variants _doxygen_colors_src = re.compile(r"""0x(?P[0-9a-f]{6})(?P[0-9a-f]{2})?()?(?P_s?rgba?(f|h)?)""") _doxygen_colors_dst = r"""0x\g\g\g""" M_CODE_FILTERS_PRE = { 'C++': _doxygen_ignore } M_CODE_FILTERS_POST = { 'C++': lambda str: _doxygen_colors_src.sub(_doxygen_colors_dst, str) }deathkiller-jazz2-native-2a7ccef/Docs/Death.dox000066400000000000000000000657661512772601700215650ustar00rootroot00000000000000/** @file @brief Proxy definitions for documentation purposes Definitions from this package are usually supplied via a compiler flag by CMake or by including @ref CommonBase.h. */ namespace Death { //###==##====#=====--==~--~=~- --- -- - - - - /** @brief Whether the current build is for debugging purposes No need to @cpp #include @ce anything for this macro to be defined, it's supplied via a compiler flag by CMake. Usually present when the build configuration is set to `Debug`. */ #define DEATH_DEBUG #undef DEATH_DEBUG /** @brief Whether runtime event tracing is enabled No need to @cpp #include @ce anything for this macro to be defined, it's supplied via a compiler flag by CMake. */ #define DEATH_TRACE #undef DEATH_TRACE /** @brief Whether exception usage is suppressed Exception usage is automatically enabled according to the compiler support by checking the @cpp _CPPUNWIND @ce and @cpp __EXCEPTIONS @ce defines. If @cpp DEATH_SUPPRESS_EXCEPTIONS @ce is defined, exception support is explicitly disabled. In this case, @cpp throw @ce is usually replaced with asserts. No need to @cpp #include @ce anything for this macro to be defined, it's supplied via a compiler flag by CMake. */ #define DEATH_SUPPRESS_EXCEPTIONS #undef DEATH_SUPPRESS_EXCEPTIONS /** @brief Whether @ref Death::runtime_cast() and type information optimization is suppressed Type information optimization is usually automatically enabled if the compiler supports it. If @cpp DEATH_SUPPRESS_RUNTIME_CAST @ce is defined, this optimization is explicitly disabled. In this case, the standard @cpp dynamic_cast() @ce is used instead. No need to @cpp #include @ce anything for this macro to be defined, it's supplied via a compiler flag by CMake. */ #define DEATH_SUPPRESS_RUNTIME_CAST #undef DEATH_SUPPRESS_RUNTIME_CAST /** @brief Build with runtime CPU dispatch Defined if the library is built with performance-critical code paths optimized for multiple architectures (such as SSE or AVX on x86), with the best matching variant selected at runtime based on detected CPU features. If not defined, the library is built with just a single variant that's picked at compile time depending on target architecture flags being passed to the compiler. The actual feature detection and dispatch both in the runtime and compile-time scenario is performed by the @relativeref{Death,Cpu} library. See @ref Cpu-usage-automatic-cached-dispatch for details and information about performance tradeoffs. No need to @cpp #include @ce anything for this macro to be defined, it's supplied via a compiler flag by CMake. */ #define DEATH_CPU_USE_RUNTIME_DISPATCH #undef DEATH_CPU_USE_RUNTIME_DISPATCH /** @brief GNU IFUNC is allowed to be used for runtime dispatch Defined if the @relativeref{Death,Cpu} library can perform runtime dispatch using [GNU IFUNC](https://sourceware.org/glibc/wiki/GNU_IFUNC), exposing the @ref DEATH_CPU_DISPATCHED_IFUNC() macro. Supported only on Linux with glibc and on Android with API 30+. See @ref Cpu-usage-automatic-cached-dispatch for details and information about performance tradeoffs. No need to @cpp #include @ce anything for this macro to be defined, it's supplied via a compiler flag by CMake. */ #define DEATH_CPU_USE_IFUNC #undef DEATH_CPU_USE_IFUNC /** @brief MSVC 2015 compatibility Defined if compatibility mode for Microsoft Visual C++ 2015 is enabled. */ #define DEATH_MSVC2015_COMPATIBILITY #undef DEATH_MSVC2015_COMPATIBILITY /** @brief MSVC 2017 compatibility Defined if compatibility mode for Microsoft Visual C++ 2017 is enabled. */ #define DEATH_MSVC2017_COMPATIBILITY #undef DEATH_MSVC2017_COMPATIBILITY /** @brief MSVC 2019 compatibility Defined if compatibility mode for Microsoft Visual C++ 2019 (or later) *without* the `/permissive-` flag set is enabled. */ #define DEATH_MSVC2019_COMPATIBILITY #undef DEATH_MSVC2019_COMPATIBILITY /** @brief Unix target Defined if the library is built for some Unix flavor (Linux, BSD, macOS, iOS, Android, ...). Note that while the behavior of Emscripten is closely emulating Unix systems, @ref DEATH_TARGET_UNIX is not defined there, only @ref DEATH_TARGET_EMSCRIPTEN. */ #define DEATH_TARGET_UNIX #undef DEATH_TARGET_UNIX /** @brief Apple target Defined if the library is built for Apple platforms (macOS, iOS). */ #define DEATH_TARGET_APPLE #undef DEATH_TARGET_APPLE /** @brief iOS target Defined if the library is built for iOS (device or simulator). */ #define DEATH_TARGET_IOS #undef DEATH_TARGET_IOS /** @brief iOS Simulator target Defined if the library is built for iOS Simulator. */ #define DEATH_TARGET_IOS_SIMULATOR #undef DEATH_TARGET_IOS_SIMULATOR /** @brief Windows target Defined if the library is built for Windows (desktop, Windows Store, Phone or Xbox). */ #define DEATH_TARGET_WINDOWS #undef DEATH_TARGET_WINDOWS /** @brief Windows RT target Defined if the library is built for Universal Windows Platform (Windows Store, Phone or Xbox). In this case, @ref DEATH_TARGET_WINDOWS is also defined. */ #define DEATH_TARGET_WINDOWS_RT #undef DEATH_TARGET_WINDOWS_RT /** @brief Emscripten target Defined if the library is built for [Emscripten](http://kripken.github.io/emscripten-site/). Note that while the behavior of Emscripten is closely emulating Unix systems, @ref DEATH_TARGET_UNIX is not defined there, only @ref DEATH_TARGET_EMSCRIPTEN. */ #define DEATH_TARGET_EMSCRIPTEN #undef DEATH_TARGET_EMSCRIPTEN /** @brief Android target Defined if the library is built for Android. */ #define DEATH_TARGET_ANDROID #undef DEATH_TARGET_ANDROID /** @brief Nintendo Switch target Defined if the library is built for Nintendo Switch. */ #define DEATH_TARGET_SWITCH #undef DEATH_TARGET_SWITCH /** @brief x86 target Defined if the library is built for x86 platforms (32 or 64-bit). Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV, @ref DEATH_TARGET_WASM, @ref DEATH_TARGET_32BIT, @ref DEATH_TARGET_BIG_ENDIAN and derived instruction set variables are not exposed in CMake because the meaning is unclear on platforms with multi-architecture binaries. If neither @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV nor @ref DEATH_TARGET_WASM is defined, the platform might be either a very old pre-WebAssembly @ref DEATH_TARGET_EMSCRIPTEN or any other that the library doesn't know about yet. */ #define DEATH_TARGET_X86 #undef DEATH_TARGET_X86 /** @brief ARM target Defined if the library is built for ARM platforms (32 or 64-bit). Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_X86, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV, @ref DEATH_TARGET_WASM, @ref DEATH_TARGET_32BIT, @ref DEATH_TARGET_BIG_ENDIAN and derived instruction set variables are not exposed in CMake because the meaning is unclear on platforms with multi-architecture binaries. If neither @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV nor @ref DEATH_TARGET_WASM is defined, the platform might be either a very old pre-WebAssembly @ref DEATH_TARGET_EMSCRIPTEN or any other that the library doesn't know about yet. */ #define DEATH_TARGET_ARM #undef DEATH_TARGET_ARM /** @brief PowerPC target Defined if the library is built for PowerPC platforms (32 or 64-bit). Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_RISCV, @ref DEATH_TARGET_WASM, @ref DEATH_TARGET_32BIT, @ref DEATH_TARGET_BIG_ENDIAN and derived instruction set variables are not exposed in CMake because the meaning is unclear on platforms with multi-architecture binaries. If neither @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV nor @ref DEATH_TARGET_WASM is defined, the platform might be either a very old pre-WebAssembly @ref DEATH_TARGET_EMSCRIPTEN or any other that the library doesn't know about yet. */ #define DEATH_TARGET_POWERPC #undef DEATH_TARGET_POWERPC /** @brief RISC-V target Defined if the library is built for RISC-V platforms (32 or 64-bit). Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_WASM, @ref DEATH_TARGET_32BIT, @ref DEATH_TARGET_BIG_ENDIAN and derived instruction set variables are not exposed in CMake because the meaning is unclear on platforms with multi-architecture binaries. If neither @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV nor @ref DEATH_TARGET_WASM is defined, the platform might be either a very old pre-WebAssembly @ref DEATH_TARGET_EMSCRIPTEN or any other that the library doesn't know about yet. */ #define DEATH_TARGET_RISCV #undef DEATH_TARGET_RISCV /** @brief WebAssembly target Defined if the library is built for WebAssembly (32 or 64-bit). Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV, @ref DEATH_TARGET_32BIT, @ref DEATH_TARGET_BIG_ENDIAN and derived instruction set variables are not exposed in CMake because the meaning is unclear on platforms with multi-architecture binaries. If neither @ref DEATH_TARGET_X86, @ref DEATH_TARGET_ARM, @ref DEATH_TARGET_POWERPC, @ref DEATH_TARGET_RISCV nor @ref DEATH_TARGET_WASM is defined, the platform might be either a very old pre-WebAssembly @ref DEATH_TARGET_EMSCRIPTEN or any other that the library doesn't know about yet. */ #define DEATH_TARGET_WASM #undef DEATH_TARGET_WASM /** @brief Whether the library is built for a 32-bit target Defined if the library is built for a 32-bit target. Not defined on 64-bit platforms. */ #define DEATH_TARGET_32BIT #undef DEATH_TARGET_32BIT /** @brief Whether the platform defaults to big-endian Defined when the platform defaults to big-endian (such as HP/PA RISC, Motorola 68k, Big-Endian MIPS, PowerPC and SPARC). Not defined on little-endian platforms (such as x86 and ARM). This macro only reflects the usual architecture default. */ #define DEATH_TARGET_BIG_ENDIAN #undef DEATH_TARGET_BIG_ENDIAN /** @brief GCC compiler Defined if the code is being compiled by GCC or GCC-compatible Clang (which is @ref DEATH_TARGET_APPLE_CLANG but not @ref DEATH_TARGET_CLANG_CL, for example). While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with GCC and then used via Clang. Major GCC version number can be accessed using the `__GNUC__` macro. Clang always reports itself as GCC 4.4, its version is reported in `__clang_major__` instead. */ #define DEATH_TARGET_GCC #undef DEATH_TARGET_GCC /** @brief Clang compiler Defined if the code is being compiled by Clang or any of its variants (@ref DEATH_TARGET_APPLE_CLANG, @ref DEATH_TARGET_CLANG_CL). If this variable is defined, usually @ref DEATH_TARGET_GCC is also defined, except for Clang-CL. While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with Clang and then used via GCC. Major Clang version number can be accessed using the `__clang_major__` macro, however note that Apple Clang (in Xcode) [uses its own numbering](https://en.wikipedia.org/wiki/Xcode#Toolchain_versions) --- for example Clang 10 is actually Apple Clang 12. */ #define DEATH_TARGET_CLANG #undef DEATH_TARGET_CLANG /** @brief Apple's Clang compiler Defined if the code is being compiled by Apple's Clang. If this variable is defined, @ref DEATH_TARGET_GCC and @ref DEATH_TARGET_CLANG are also defined. This is primarily useful when checking for Clang version, as Apple uses a different versioning scheme. While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with Clang and then used via GCC. Major Apple Clang version number can be accessed using the `__clang_major__` macro, which is the same macro as vanilla Clang. There is no macro that exposes the matching vanilla Clang version, the only option is to use [an external mapping table](https://en.wikipedia.org/wiki/Xcode#Toolchain_versions) --- for example Apple Clang 12 is actually Clang 10. */ #define DEATH_TARGET_APPLE_CLANG #undef DEATH_TARGET_APPLE_CLANG /** @brief Clang-CL compiler Defined if the code is being compiled by Clang with a MSVC frontend. If this variable is defined, @ref DEATH_TARGET_CLANG and @ref DEATH_TARGET_MSVC is also defined (but @ref DEATH_TARGET_GCC not). While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with Clang-CL and then used via MSVC. Clang-CL uses the same versioning scheme as vanilla Clang, accessible using the `__clang_major__` macro. It also exposes the `_MSC_VER` macro, reporting the MSVC version it's compatible with. */ #define DEATH_TARGET_CLANG_CL #undef DEATH_TARGET_CLANG_CL /** @brief Intel LLVM compiler Defined if the code is being compiled by Intel LLVM (oneAPI) compiler. While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with Intel LLVM and then used via Clang. */ #define DEATH_TARGET_INTEL_LLVM #undef DEATH_TARGET_INTEL_LLVM /** @brief MSVC compiler Defined if the code is being compiled by MSVC or Clang with a MSVC frontend. If this variable is defined, @ref DEATH_TARGET_CLANG might also be defined. While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with MSVC and then used via Clang-CL. MSVC version can be accessed using the `_MSC_VER` macro. The macro uses an [internal version numbering](https://en.wikipedia.org/wiki/Microsoft_Visual_C++#Internal_version_numbering), so for example MSVC 2019 16.7 (14.27) is reported as 1927. */ #define DEATH_TARGET_MSVC #undef DEATH_TARGET_MSVC /** @brief Cygwin environment Defined if the code is being compiled by Cygwin toolchain. If this variable is defined, @ref DEATH_TARGET_GCC and possibly also @ref DEATH_TARGET_CLANG are defined. While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with MSVC and then used via Clang-CL. */ #define DEATH_TARGET_CYGWIN #undef DEATH_TARGET_CYGWIN /** @brief MinGW compiler Defined if the code is being compiled by GCC / Clang running under MinGW. If this variable is defined, @ref DEATH_TARGET_GCC and possibly also @ref DEATH_TARGET_CLANG are defined. While this variable is exposed in CMake as well, it's not guaranteed that the reported compiler is consistent between CMake and C++ --- for example, a library can be built with MSVC and then used via Clang-CL. */ #define DEATH_TARGET_MINGW #undef DEATH_TARGET_MINGW /** @brief STL libc++ target Defined if the library is built against Clang [libc++](https://libcxx.llvm.org/) STL implementation. This is most common on Apple acOS and iOS and on newer Android NDKs, it's also sometimes used on Linux. Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_LIBSTDCXX and @ref DEATH_TARGET_DINKUMWARE are not exposed in CMake because the detection is non-trivial. Major libc++ version number can be accessed using the `_LIBCPP_VERSION` version macro. While libc++ can only be used with Clang (and not GCC), its version might or might not be the same as `__clang_major__`. */ #define DEATH_TARGET_LIBCXX #undef DEATH_TARGET_LIBCXX /** @brief STL libstdc++ target Defined if the library is built against GCC [libstdc++](https://gcc.gnu.org/onlinedocs/libstdc++/) STL implementation. This is most common on Linux and under MinGW, note that Clang is able to use libstdc++ as well. Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_LIBCXX and @ref DEATH_TARGET_DINKUMWARE are not exposed in CMake because the detection is non-trivial. Major libstdc++ version number can be accessed using the `_GLIBCXX_RELEASE` macro, however it's available only since libstdc++ 7. Older versions define just `__GLIBCXX__`, which contains a release date (and the dates, of course, [overlap for patch releases](https://stackoverflow.com/a/37119478) and can be just anything for custom GCC forks). If libstdc++ is used together with GCC, it can be assumed both have the same version; if libstdc++ is used together with Clang, the versions can be arbitrary. */ #define DEATH_TARGET_LIBSTDCXX #undef DEATH_TARGET_LIBSTDCXX /** @brief STL Dinkumware target Defined if the library is built against Dinkumware STL implementation (used by MSVC). Note that Clang is able to use this implementation as well. Note that unlike other `DEATH_TARGET_*` variables, this variable, @ref DEATH_TARGET_LIBSTDCXX and @ref DEATH_TARGET_DINKUMWARE are not exposed in CMake because the detection is non-trivial. The MSVC standard library is closely tied to a compiler version, thus `_MSC_VER` can be used to determine its version on both MSVC and Clang-CL. */ #define DEATH_TARGET_DINKUMWARE #undef DEATH_TARGET_DINKUMWARE /** @brief SSE2 target Defined on @ref DEATH_TARGET_X86 "x86" if [Streaming SIMD Extensions 2](https://en.wikipedia.org/wiki/SSE2) are enabled at compile time (`-msse2` or higher on GCC/Clang, `/arch:SSE2` or higher on MSVC). All x86-64 targets support SSE2. Implied by @ref DEATH_TARGET_SSE3. */ #define DEATH_TARGET_SSE2 #undef DEATH_TARGET_SSE2 /** @brief SSE3 target Defined on @ref DEATH_TARGET_X86 "x86" if [Streaming SIMD Extensions 3](https://en.wikipedia.org/wiki/SSE3) are enabled at compile time (on GCC/Clang it's `-msse3` and higher, MSVC doesn't have a direct option and it's only implied by `/arch:AVX`). Superset of @ref DEATH_TARGET_SSE2, implied by @ref DEATH_TARGET_SSSE3. */ #define DEATH_TARGET_SSE3 #undef DEATH_TARGET_SSE3 /** @brief SSSE3 target Defined on @ref DEATH_TARGET_X86 "x86" if [Supplemental Streaming SIMD Extensions 3](https://en.wikipedia.org/wiki/SSSE3) are enabled at compile time (on GCC/Clang it's `-mssse3` and higher, MSVC doesn't have a direct option and it's only implied by `/arch:AVX`). Superset of @ref DEATH_TARGET_SSE3, implied by @ref DEATH_TARGET_SSE41. Note that certain older AMD processors have [SSE4a](https://en.wikipedia.org/wiki/SSE4#SSE4a) but neither SSSE3 nor SSE4.1. Both can be however treated as a subset of SSE4.1 to a large extent, and it's recommended to use @ref DEATH_TARGET_SSE41 to detect those. */ #define DEATH_TARGET_SSSE3 #undef DEATH_TARGET_SSSE3 /** @brief SSE4.1 target Defined on @ref DEATH_TARGET_X86 "x86" if [Streaming SIMD Extensions 4.1](https://en.wikipedia.org/wiki/SSE4#SSE4.1) are enabled at compile time (on GCC/Clang it's `-msse4.1` and higher, MSVC doesn't have a direct option and it's only implied by `/arch:AVX`). Superset of @ref DEATH_TARGET_SSSE3, implied by @ref DEATH_TARGET_SSE42. Note that certain older AMD processors have [SSE4a](https://en.wikipedia.org/wiki/SSE4#SSE4a) but neither SSSE3 nor SSE4.1. Both can be however treated as a subset of SSE4.1 to a large extent, and it's recommended to use @ref DEATH_TARGET_SSE41 to detect those. */ #define DEATH_TARGET_SSE41 #undef DEATH_TARGET_SSE41 /** @brief SSE4.2 target Defined on @ref DEATH_TARGET_X86 "x86" if [Streaming SIMD Extensions 4.2](https://en.wikipedia.org/wiki/SSE4#SSE4.2) are enabled at compile time (on GCC/Clang it's `-msse4.2` and higher, MSVC doesn't have a direct option and it's only implied by `/arch:AVX`). Superset of @ref DEATH_TARGET_SSE41, implied by @ref DEATH_TARGET_AVX. */ #define DEATH_TARGET_SSE42 #undef DEATH_TARGET_SSE42 /** @brief Target with POPCNT instructions Defined on @ref DEATH_TARGET_X86 "x86" if [POPCNT](https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#ABM_(Advanced_Bit_Manipulation)) is enabled at compile time. On GCC/Clang it's `-mpopcnt` and is also implied by `-msse4.2` and higher, MSVC doesn't have a direct option but it's assumed to be implied by `/arch:AVX`. To avoid failures at runtime, prefer to detect its presence with @relativeref{Death,Cpu::runtimeFeatures()}. */ #define DEATH_TARGET_POPCNT #undef DEATH_TARGET_POPCNT /** @brief Target with LZCNT instructions Defined on @ref DEATH_TARGET_X86 "x86" if [LZCNT](https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#ABM_(Advanced_Bit_Manipulation)) is enabled at compile time (on GCC/Clang it's `-mlznct`, MSVC doesn't have a direct option but it's assumed to be implied by `/arch:AVX2`). However note that this instruction has encoding compatible with an earlier `BSR` instruction which has a slightly different behavior. To avoid wrong results if it isn't available, prefer to detect its presence with @relativeref{Death,Cpu::runtimeFeatures()} instead. */ #define DEATH_TARGET_LZCNT #undef DEATH_TARGET_LZCNT /** @brief Target with BMI1 instructions Defined on @ref DEATH_TARGET_X86 "x86" if [BMI1](https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#BMI1_(Bit_Manipulation_Instruction_Set_1)) including the `TZCNT` instruction is enabled at compile time (on GCC/Clang it's `-mbmi`, MSVC doesn't have a direct option but it's assumed to be implied by `/arch:AVX2`). However note that the `TZCNT` instruction has encoding compatible with an earlier `BSF` instruction which has a slightly different behavior. To avoid wrong results if it isn't available, prefer to detect its presence with @relativeref{Death,Cpu::runtimeFeatures()} instead. Presence of this instruction set is *not* implied by @ref DEATH_TARGET_BMI2. */ #define DEATH_TARGET_BMI1 #undef DEATH_TARGET_BMI1 /** @brief Target with BMI2 instructions Defined on @ref DEATH_TARGET_X86 "x86" if [BMI2](https://en.wikipedia.org/wiki/X86_Bit_manipulation_instruction_set#BMI2_(Bit_Manipulation_Instruction_Set_2)) is enabled at compile time. On GCC/Clang it's `-mbmi2`, MSVC doesn't have a direct option but it's assumed to be implied by `/arch:AVX2`. To avoid failures at runtime, prefer to detect its presence with @relativeref{Death,Cpu::runtimeFeatures()}. Presence of this instruction set does *not* imply @ref DEATH_TARGET_BMI1. */ #define DEATH_TARGET_BMI2 #undef DEATH_TARGET_BMI2 /** @brief Target with CLFLUSHOPT instruction Defined on @ref DEATH_TARGET_X86 "x86" if CLFLUSHOPT (Flush Cache Line Optimized) instruction is enabled at compile time. On GCC/Clang it's `-mclflushopt`, MSVC doesn't have a direct option. */ #define DEATH_TARGET_CLFLUSHOPT #undef DEATH_TARGET_CLFLUSHOPT /** @brief AVX target Defined on @ref DEATH_TARGET_X86 "x86" if [Advanced Vector Extensions](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions) are enabled at compile time (`-mavx` and higher on GCC/Clang, `/arch:AVX` on MSVC). Superset of @ref DEATH_TARGET_SSE42, implied by @ref DEATH_TARGET_AVX2. */ #define DEATH_TARGET_AVX #undef DEATH_TARGET_AVX /** @brief AVX target with F16C Defined on @ref DEATH_TARGET_X86 "x86" if the [F16C instruction set](https://en.wikipedia.org/wiki/F16C) is enabled at compile time. On GCC/Clang it's `-mf16c`, MSVC doesn't have a direct option but it's assumed to be implied by `/arch:AVX2`. To avoid failures at runtime, prefer to detect its presence with @relativeref{Death,Cpu::runtimeFeatures()}. */ #define DEATH_TARGET_AVX_F16C #undef DEATH_TARGET_AVX_F16C /** @brief AVX target with FMA Defined on @ref DEATH_TARGET_X86 "x86" if the [FMA3 instruction set](https://en.wikipedia.org/wiki/FMA_instruction_set) is enabled at compile time. On GCC/Clang it's `-mfma`, MSVC doesn't have a direct option but it's assumes to be implied by `/arch:AVX2`. To avoid failures at runtime, prefer to detect its presence with @relativeref{Death,Cpu::runtimeFeatures()}. The FMA4 instruction set, which used to be supported only in certain range of AMD processors and isn't anymore, is not detected, and AMD switched to FMA3 since. */ #define DEATH_TARGET_AVX_FMA #undef DEATH_TARGET_AVX_FMA /** @brief AVX2 target Defined on @ref DEATH_TARGET_X86 "x86" if [Advanced Vector Extensions 2](https://en.wikipedia.org/wiki/Advanced_Vector_Extensions#Advanced_Vector_Extensions_2) are enabled at compile time (`-mavx2` and higher on GCC/Clang, `/arch:AVX2` on MSVC). Superset of @ref DEATH_TARGET_AVX, implied by @ref DEATH_TARGET_AVX512F. */ #define DEATH_TARGET_AVX2 #undef DEATH_TARGET_AVX2 /** @brief AVX-512 Foundation target Defined on @ref DEATH_TARGET_X86 "x86" if [AVX-512](https://en.wikipedia.org/wiki/AVX-512) Foundation instructions are enabled at compile time (`-mavx512f` and higher on GCC/Clang, `/arch:AVX512` on MSVC). Superset of @ref DEATH_TARGET_AVX2. */ #define DEATH_TARGET_AVX512F #undef DEATH_TARGET_AVX512F /** @brief NEON target Defined on @ref DEATH_TARGET_ARM "ARM" if [ARM NEON](https://en.wikipedia.org/wiki/ARM_architecture#Advanced_SIMD_(Neon)) instructions are enabled at compile time (`-mfpu=neon` on GCC/Clang, implicitly supported on ARM64). Implied by @ref DEATH_TARGET_NEON_FMA. Apart from NEON, there's several other mutually incompatible ARM instruction sets. Detection for these will be added when the platforms become more widespread: - Helium, which is a low-power alternative to NEON - SVE, which is a HPC-focused alternative to NEON, in 2021 found mostly just on [AWS Graviton](https://aws.amazon.com/ec2/graviton/) - SVE2, which is a next-generation vector instruction set designed to be a successor to both NEON and SVE, scheduled to appear in production in around 2022 - AMX, which is Apple's proprietary and patented instruction set available only on their own hardware */ #define DEATH_TARGET_NEON #undef DEATH_TARGET_NEON /** @brief NEON target with FMA Defined on @ref DEATH_TARGET_ARM "ARM" if NEON FMA instructions are enabled at compile time (`-mfpu=neon-vfpv4` on GCC/Clang on 32-bit ARM, implicitly supported on ARM64). Not defined if FMA is only available for scalar code and not for NEON. Superset of @ref DEATH_TARGET_NEON, implied by @ref DEATH_TARGET_NEON_FP16. */ #define DEATH_TARGET_NEON_FMA #undef DEATH_TARGET_NEON_FMA /** @brief NEON target with FP16 vector arithmetic Defined on @ref DEATH_TARGET_ARM "ARM" if ARMv8.2-a NEON FP16 vector arithmetic support is enabled at compile time (`-march=armv8.2-a+fp16` on GCC/Clang). Superset of @ref DEATH_TARGET_NEON_FMA. */ #define DEATH_TARGET_NEON_FP16 #undef DEATH_TARGET_NEON_FP16 /** @brief SIMD128 target Defined on @ref DEATH_TARGET_WASM "WebAssembly" if [128-bit SIMD](https://github.com/webassembly/simd) instructions are enabled at compile time (`-msimd128` passed to Clang), and the compiler supports the finalized version of the intrinsics, which is since Clang 13 and Emscripten 2.0.18. Emscripten SDK 2.0.13 to 2.0.17 ship with a Clang that reports as 13 but isn't actually the final version. */ #define DEATH_TARGET_SIMD128 #undef DEATH_TARGET_SIMD128 } deathkiller-jazz2-native-2a7ccef/Docs/Doxyfile000066400000000000000000003342651512772601700215230ustar00rootroot00000000000000# Doxyfile 1.8.17 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. # # All text after a double hash (##) is considered a comment and is placed in # front of the TAG it is preceding. # # All text after a single hash (#) is considered a comment and will be ignored. # The format is: # TAG = value [value, ...] # For lists, items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (\" \"). #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the configuration # file that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # https://www.gnu.org/software/libiconv/ for the list of possible encodings. # The default value is: UTF-8. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded by # double-quotes, unless you are using Doxywizard) that should identify the # project for which the documentation is generated. This name is used in the # title of most generated pages and in a few other places. # The default value is: My Project. PROJECT_NAME = Jazz² Resurrection # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version # control system is used. PROJECT_NUMBER = # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. PROJECT_BRIEF = API Docs # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. PROJECT_LOGO = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. OUTPUT_DIRECTORY = ../build/docs/ # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and # will distribute the generated files over these directories. Enabling this # option can be useful when feeding doxygen a huge amount of source files, where # putting all generated files in the same directory would otherwise causes # performance problems for the file system. # The default value is: NO. CREATE_SUBDIRS = NO # If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII # characters to appear in the names of generated files. If set to NO, non-ASCII # characters will be escaped, for example _xE3_x81_x84 will be used for Unicode # U+3044. # The default value is: NO. ALLOW_UNICODE_NAMES = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, # Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), # Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, # Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, # Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, # Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, # Ukrainian and Vietnamese. # The default value is: English. OUTPUT_LANGUAGE = English # The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all generated output in the proper direction. # Possible values are: None, LTR, RTL and Context. # The default value is: None. OUTPUT_TEXT_DIRECTION = None # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. # The default value is: YES. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief # description of a member or function before the detailed description # # Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. # The default value is: YES. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator that is # used to form the text in various listings. Each string in this list, if found # as the leading text of the brief description, will be stripped from the text # and the result, after processing the whole list, is used as the annotated # text. Otherwise, the brief description is used as-is. If left blank, the # following values are used ($name is automatically replaced with the name of # the entity):The $name class, The $name widget, The $name file, is, provides, # specifies, contains, represents, a, an and the. ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # doxygen will generate a detailed section even if there is only a brief # description. # The default value is: NO. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. # The default value is: NO. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path # before files name in the file list and in the header files. If set to NO the # shortest path that makes the file name unique will be used # The default value is: YES. FULL_PATH_NAMES = YES # The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. # Stripping is only done if one of the specified strings matches the left-hand # part of the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the path to # strip. # # Note that you can specify absolute paths here, but also relative paths, which # will be relative from the directory where doxygen is started. # This tag requires that the tag FULL_PATH_NAMES is set to YES. STRIP_FROM_PATH = . \ ../Sources/ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the # path mentioned in the documentation of a class, which tells the reader which # header file to include in order to use a class. If left blank only the name of # the header file containing the class definition is used. Otherwise one should # specify the list of include paths that are normally passed to the compiler # using the -I flag. STRIP_FROM_INC_PATH = ../Sources/Shared/ \ ../Sources/ # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but # less readable) file names. This can be useful is your file systems doesn't # support long names like on DOS, Mac, or CD-ROM. # The default value is: NO. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the # first line (until the first dot) of a Javadoc-style comment as the brief # description. If set to NO, the Javadoc-style will behave just like regular Qt- # style comments (thus requiring an explicit @brief command for a brief # description.) # The default value is: NO. JAVADOC_AUTOBRIEF = NO # If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line # such as # /*************** # as being the beginning of a Javadoc-style comment "banner". If set to NO, the # Javadoc-style will behave just like regular comments and it will not be # interpreted by doxygen. # The default value is: NO. JAVADOC_BANNER = NO # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus # requiring an explicit \brief command for a brief description.) # The default value is: NO. QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a # multi-line C++ special comment block (i.e. a block of //! or /// comments) as # a brief description. This used to be the default behavior. The new default is # to treat a multi-line C++ comment block as a detailed description. Set this # tag to YES if you prefer the old behavior instead. # # Note that setting this tag to YES also means that rational rose comments are # not recognized any more. # The default value is: NO. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new # page for each member. If set to NO, the documentation of a member will be part # of the file/class/namespace that contains it. # The default value is: NO. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen # uses this value to replace tabs by spaces in code fragments. # Minimum value: 1, maximum value: 16, default value: 4. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that act as commands in # the documentation. An alias has the form: # name=value # For example adding # "sideeffect=@par Side Effects:\n" # will allow you to put the command \sideeffect (or @sideeffect) in the # documentation, which will result in a user-defined paragraph with heading # "Side Effects:". You can put \n's in the value part of an alias to insert # newlines (in the resulting output). You can put ^^ in the value part of an # alias to insert a newline as if a physical newline was in the original file. # When you need a literal { or } or , in the value part of an alias you have to # escape them by means of a backslash (\), this can lead to conflicts with the # commands \{ and \} for these it is advised to use the version @{ and @} or use # a double escape (\\{ and \\}) ALIASES = \ "partialsupport=@xrefitem death-partialsupport \"Partially supported\" \"List of partially supported features\"" \ "thirdparty=@xrefitem credits-third-party \"Third-party components\" \"Third-party components\"" \ "todoc=@xrefitem todoc \"Documentation TODO\" \"Documentation-related TODO list\"" \ "experimental=@attention This functionality is still experimental and may change in the future without keeping full backwards compatibility." \ "cb{1}=@code{\1}" \ "cpp=@code{.cpp}" \ "cmake=@code{.cmake}" \ "ce=@endcode" \ "m_div{1}=@xmlonly@endxmlonly" \ "m_enddiv=@xmlonly@endxmlonly" \ "m_span{1}=@xmlonly@endxmlonly" \ "m_endspan=@xmlonly@endxmlonly" \ "m_class{1}=@xmlonly@endxmlonly" \ "m_footernavigation=@xmlonly@endxmlonly" \ "m_examplenavigation{2}=@xmlonly@endxmlonly" \ "m_keywords{1}=@xmlonly@endxmlonly" \ "m_keyword{3}=@xmlonly@endxmlonly" \ "m_enum_values_as_keywords=@xmlonly@endxmlonly" \ "m_since{2}=@since @m_class{m-label m-success m-flat} @ref death-changelog-\1-\2 \"new in \1.\2\"" \ "m_since_latest=@since @m_class{m-label m-success m-flat} @ref death-changelog-latest \"new in Git master\"" \ "m_deprecated_since{2}=@since deprecated in \1.\2 @deprecated" \ "m_deprecated_since_latest=@since deprecated in Git master @deprecated" \ "m_highlight_lines{1}=@xmlonly@endxmlonly" \ "relativeref{2}=@ref \1::\2 \"\2\"" # This tag can be used to specify a number of word-keyword mappings (TCL only). # A mapping has the form "name=value". For example adding "class=itcl::class" # will allow you to use the command class in the itcl::class meaning. TCL_SUBST = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all # members will be omitted, etc. # The default value is: NO. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or # Python sources only. Doxygen will then generate output that is more tailored # for that language. For instance, namespaces will be presented as packages, # qualified scopes will look different, etc. # The default value is: NO. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources. Doxygen will then generate output that is tailored for Fortran. # The default value is: NO. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for VHDL. # The default value is: NO. OPTIMIZE_OUTPUT_VHDL = NO # Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice # sources only. Doxygen will then generate output that is more tailored for that # language. For instance, namespaces will be presented as modules, types will be # separated into more groups, etc. # The default value is: NO. OPTIMIZE_OUTPUT_SLICE = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and # language is one of the parsers supported by doxygen: IDL, Java, JavaScript, # Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the # default for Fortran type files), VHDL, tcl. For instance to make doxygen treat # .inc files as Fortran files (default is PHP), and .f files as C (default is # Fortran), use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. EXTENSION_MAPPING = # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable # documentation. See https://daringfireball.net/projects/markdown/ for details. # The output of markdown processing is further processed by doxygen, so you can # mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in # case of backward compatibilities issues. # The default value is: YES. MARKDOWN_SUPPORT = YES # When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. # Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 5 # When enabled doxygen tries to link words that correspond to documented # classes, or namespaces to their corresponding documentation. Such a link can # be prevented in individual cases by putting a % sign in front of the word or # globally by setting AUTOLINK_SUPPORT to NO. # The default value is: YES. AUTOLINK_SUPPORT = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should set this # tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); # versus func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. # The default value is: NO. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. # The default value is: NO. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip (see: # https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen # will parse them like normal C++ but will assume all classes use public instead # of private inheritance when no explicit protection keyword is present. # The default value is: NO. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate # getter and setter methods for a property. Setting this option to YES will make # doxygen to replace the get and set methods by a property in the documentation. # This will only work if the methods are indeed getting or setting a simple # type. If this is not the case, or you want to show the methods anyway, you # should set this option to NO. # The default value is: YES. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. # The default value is: NO. DISTRIBUTE_GROUP_DOC = NO # If one adds a struct or class to a group and this option is enabled, then also # any nested class or struct is added to the same group. By default this option # is disabled and one has to add nested compounds explicitly via \ingroup. # The default value is: NO. GROUP_NESTED_COMPOUNDS = NO # Set the SUBGROUPING tag to YES to allow class member groups of the same type # (for instance a group of public functions) to be put as a subgroup of that # type (e.g. under the Public Functions section). Set it to NO to prevent # subgrouping. Alternatively, this can be done per class using the # \nosubgrouping command. # The default value is: YES. SUBGROUPING = YES # When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions # are shown inside the group in which they are included (e.g. using \ingroup) # instead of on a separate page (for HTML and Man pages) or section (for LaTeX # and RTF). # # Note that this feature does not work in combination with # SEPARATE_MEMBER_PAGES. # The default value is: NO. INLINE_GROUPED_CLASSES = NO # When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions # with only public data fields or simple typedef fields will be shown inline in # the documentation of the scope in which they are defined (i.e. file, # namespace, or group documentation), provided this scope is documented. If set # to NO, structs, classes, and unions are shown on a separate page (for HTML and # Man pages) or section (for LaTeX and RTF). # The default value is: NO. INLINE_SIMPLE_STRUCTS = NO # When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or # enum is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically be # useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. # The default value is: NO. TYPEDEF_HIDES_STRUCT = NO # The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This # cache is used to resolve symbols given their name and scope. Since this can be # an expensive process and often the same symbol appears multiple times in the # code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small # doxygen will become slower. If the cache is too large, memory is wasted. The # cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range # is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 # symbols. At the end of a run doxygen will report the cache usage and suggest # the optimal cache size from a speed point of view. # Minimum value: 0, maximum value: 9, default value: 0. LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in # documentation are documented, even if no documentation was available. Private # class members and static file members will be hidden unless the # EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. # Note: This will also disable the warnings about undocumented members that are # normally produced when WARNINGS is set to YES. # The default value is: NO. EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will # be included in the documentation. # The default value is: NO. EXTRACT_PRIVATE = NO # If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual # methods of a class will be included in the documentation. # The default value is: NO. EXTRACT_PRIV_VIRTUAL = YES # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. EXTRACT_PACKAGE = NO # If the EXTRACT_STATIC tag is set to YES, all static members of a file will be # included in the documentation. # The default value is: NO. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined # locally in source files will be included in the documentation. If set to NO, # only classes defined in header files are included. Does not have any effect # for Java sources. # The default value is: YES. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. If set to YES, local methods, # which are defined in the implementation section but not in the interface are # included in the documentation. If set to NO, only methods in the interface are # included. # The default value is: NO. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base name of # the file that contains the anonymous namespace. By default anonymous namespace # are hidden. # The default value is: NO. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation # section is generated. This option has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. If set # to NO, these classes will be included in the various overviews. This option # has no effect if EXTRACT_ALL is enabled. # The default value is: NO. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend # declarations. If set to NO, these declarations will be included in the # documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = YES # If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any # documentation blocks found inside the body of a function. If set to NO, these # blocks will be appended to the function's detailed documentation block. # The default value is: NO. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation that is typed after a # \internal command is included. If the tag is set to NO then the documentation # will be excluded. Set it to YES to include the internal documentation. # The default value is: NO. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file # names in lower-case letters. If set to YES, upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # (including Cygwin) ands Mac users are advised to set this option to NO. # The default value is: system dependent. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with # their full class and namespace scopes in the documentation. If set to YES, the # scope will be hidden. # The default value is: NO. HIDE_SCOPE_NAMES = NO # If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will # append additional text to a page's title, such as Class Reference. If set to # YES the compound reference will be hidden. # The default value is: NO. HIDE_COMPOUND_REFERENCE= NO # If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of # the files that are included by a file in the documentation of that file. # The default value is: YES. SHOW_INCLUDE_FILES = YES # If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each # grouped member an include statement to the documentation, telling the reader # which file to include in order to use the member. # The default value is: NO. SHOW_GROUPED_MEMB_INC = NO # If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include # files with double quotes in the documentation rather than with sharp brackets. # The default value is: NO. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the # documentation for inline members. # The default value is: YES. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the # (detailed) documentation of file and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. # The default value is: YES. SORT_MEMBER_DOCS = NO # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief # descriptions of file, namespace and class members alphabetically by member # name. If set to NO, the members will appear in declaration order. Note that # this will also influence the order of the classes in the class list. # The default value is: NO. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the # (brief and detailed) documentation of class members so that constructors and # destructors are listed first. If set to NO the constructors will appear in the # respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. # Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief # member documentation. # Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting # detailed member documentation. # The default value is: NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy # of group names into alphabetical order. If set to NO the group names will # appear in their defined order. # The default value is: NO. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by # fully-qualified names, including namespaces. If set to NO, the class list will # be sorted only by class name, not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the alphabetical # list. # The default value is: NO. SORT_BY_SCOPE_NAME = YES # If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper # type resolution of all parameters of a function it will reject a match between # the prototype and the implementation of a member function even if there is # only one candidate or it is obvious which candidate to choose by doing a # simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still # accept a match between prototype and implementation in such cases. # The default value is: NO. STRICT_PROTO_MATCHING = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo # list. This list is created by putting \todo commands in the documentation. # The default value is: YES. GENERATE_TODOLIST = NO # The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test # list. This list is created by putting \test commands in the documentation. # The default value is: YES. GENERATE_TESTLIST = NO # The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug # list. This list is created by putting \bug commands in the documentation. # The default value is: YES. GENERATE_BUGLIST = NO # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) # the deprecated list. This list is created by putting \deprecated commands in # the documentation. # The default value is: YES. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional documentation # sections, marked by \if ... \endif and \cond # ... \endcond blocks. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the # initial value of a variable or macro / define can have for it to appear in the # documentation. If the initializer consists of more lines than specified here # it will be hidden. Use a value of 0 to hide initializers completely. The # appearance of the value of individual variables and macros / defines can be # controlled using \showinitializer or \hideinitializer command in the # documentation regardless of this setting. # Minimum value: 0, maximum value: 10000, default value: 30. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated at # the bottom of the documentation of classes and structs. If set to YES, the # list will mention the files that were used to generate the documentation. # The default value is: YES. SHOW_USED_FILES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. This # will remove the Files entry from the Quick Index and from the Folder Tree View # (if specified). # The default value is: YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces # page. This will remove the Namespaces entry from the Quick Index and from the # Folder Tree View (if specified). # The default value is: YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command command input-file, where command is the value of the # FILE_VERSION_FILTER tag, and input-file is the name of an input file provided # by doxygen. Whatever the program writes to standard output is used as the file # version. For an example see the documentation. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. To create the layout file # that represents doxygen's defaults, run doxygen with the -l option. You can # optionally specify a file name after the option, if omitted DoxygenLayout.xml # will be used as the name of the layout file. # # Note that if you run doxygen from a directory containing a file called # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. LAYOUT_FILE = # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib # extension is automatically appended if omitted. This requires the bibtex tool # to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. # For LaTeX the style of the bibliography can be controlled using # LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the # search path. See also \cite for info how to create references. CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated to # standard output by doxygen. If QUIET is set to YES this implies that the # messages are off. # The default value is: NO. QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES # this implies that the warnings are on. # # Tip: Turn warnings on while writing the documentation. # The default value is: YES. WARNINGS = YES # If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate # warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag # will automatically be disabled. # The default value is: YES. WARN_IF_UNDOCUMENTED = YES # If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some parameters # in a documented function, or documenting parameters that don't exist or using # markup commands wrongly. # The default value is: YES. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that # are documented, but have no documentation for their parameters or return # value. If set to NO, doxygen will only warn about wrong or incomplete # parameter documentation, but not about the absence of documentation. If # EXTRACT_ALL is set to YES then this flag will automatically be disabled. # The default value is: NO. WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when # a warning is encountered. # The default value is: NO. WARN_AS_ERROR = NO # The WARN_FORMAT tag determines the format of the warning messages that doxygen # can produce. The string should contain the $file, $line, and $text tags, which # will be replaced by the file and line number from which the warning originated # and the warning text. Optionally the format may contain $version, which will # be replaced by the version of the file (if it could be obtained via # FILE_VERSION_FILTER) # The default value is: $file:$line: $text. WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning and error # messages should be written. If left blank the output is written to standard # error (stderr). WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag is used to specify the files and/or directories that contain # documented source files. You may enter file names like myfile.cpp or # directories like /usr/src/myproject. Separate the files or directories with # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. INPUT = . \ ../Sources # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv # documentation (see: https://www.gnu.org/software/libiconv/) for the list of # possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and # *.h) to filter out the source-files in the directories. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, # *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), # *.doc (to be provided as doxygen C comment), *.txt (to be provided as doxygen # C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f, *.for, *.tcl, *.vhd, # *.vhdl, *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.h \ *.hpp \ *.dox # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. # The default value is: NO. RECURSIVE = YES # The EXCLUDE tag can be used to specify files and/or directories that should be # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. # # Note that relative paths are relative to the directory from which doxygen is # run. EXCLUDE = CodingStyle/ \ Snippets/ \ ../Sources/jsoncpp/ \ ../Sources/nCine/Backends/Android/Bridge/ \ ../Sources/nCine/Base/ParallelHashMap/ \ ../Sources/nCine/Base/pdqsort/ \ ../Sources/Jazz2/Multiplayer/Backends/ # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded # from the input. # The default value is: NO. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* EXCLUDE_PATTERNS = */Implementation/* \ */Test/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test # # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories use the pattern */test/* EXCLUDE_SYMBOLS = Death::*Implementation \ Death::*Test \ nCine::*Implementation # The EXAMPLE_PATH tag can be used to specify one or more files or directories # that contain example code fragments that are included (see the \include # command). EXAMPLE_PATH = CodingStyle/ \ Snippets/ \ ../Sources/Examples/ # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and # *.h) to filter out the source-files in the directories. If left blank all # files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude commands # irrespective of the value of the RECURSIVE tag. # The default value is: NO. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or directories # that contain images that are to be included in the documentation (see the # \image command). IMAGE_PATH = . \ Snippets/ # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command: # # # # where is the value of the INPUT_FILTER tag, and is the # name of an input file. Doxygen will then use the output that the filter # program writes to standard output. If FILTER_PATTERNS is specified, this tag # will be ignored. # # Note that the filter must not add or remove lines; it is applied before the # code is scanned, but not when the output code is generated. If lines are added # or removed, the anchors will not be placed correctly. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: pattern=filter # (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how # filters are used. If the FILTER_PATTERNS tag is empty or if none of the # patterns match the file name, INPUT_FILTER is applied. # # Note that for custom extensions or not directly supported extensions you also # need to set EXTENSION_MAPPING for the extension otherwise the files are not # properly processed by doxygen. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will also be used to filter the input files that are used for # producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). # The default value is: NO. FILTER_SOURCE_FILES = NO # The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file # pattern. A pattern will override the setting for FILTER_PATTERN (if any) and # it is also possible to disable source filtering for a specific pattern using # *.ext= (so without naming a filter). # This tag requires that the tag FILTER_SOURCE_FILES is set to YES. FILTER_SOURCE_PATTERNS = # If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that # is part of the input, its contents will be placed on the main page # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will be # generated. Documented entities will be cross-referenced with these sources. # # Note: To get rid of all source code in the generated output, make sure that # also VERBATIM_HEADERS is set to NO. # The default value is: NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. # The default value is: NO. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any # special comment blocks from generated source code fragments. Normal C, C++ and # Fortran comments will always remain visible. # The default value is: YES. STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. # The default value is: NO. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES then for each documented function # all documented entities called/used by that function will be listed. # The default value is: NO. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set # to YES then the hyperlinks from functions in REFERENCES_RELATION and # REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will # link to the documentation. # The default value is: YES. REFERENCES_LINK_SOURCE = YES # If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the # source code will show a tooltip with additional information such as prototype, # brief description and links to the definition and documentation. Since this # will make the HTML file larger and loading of large files a bit slower, you # can opt to disable this feature. # The default value is: YES. # This tag requires that the tag SOURCE_BROWSER is set to YES. SOURCE_TOOLTIPS = YES # If the USE_HTAGS tag is set to YES then the references to source code will # point to the HTML generated by the htags(1) tool instead of doxygen built-in # source browser. The htags tool is part of GNU's global source tagging system # (see https://www.gnu.org/software/global/global.html). You will need version # 4.8.6 or higher. # # To use it do the following: # - Install the latest version of global # - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file # - Make sure the INPUT points to the root of the source tree # - Run doxygen as normal # # Doxygen will invoke htags (and that will in turn invoke gtags), so these # tools must be available from the command line (i.e. in the search path). # # The result: instead of the source browser generated by doxygen, the links to # source code will now point to the output of htags. # The default value is: NO. # This tag requires that the tag SOURCE_BROWSER is set to YES. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a # verbatim copy of the header file for each class for which an include is # specified. Set to NO to disable this. # See also: Section \class. # The default value is: YES. VERBATIM_HEADERS = NO #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all # compounds will be generated. Enable this if the project contains a lot of # classes, structs, unions or interfaces. # The default value is: YES. ALPHABETICAL_INDEX = NO # The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in # which the alphabetical index list will be split. # Minimum value: 1, maximum value: 20, default value: 5. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. COLS_IN_ALPHA_INDEX = 3 # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored # while generating the index headers. # This tag requires that the tag ALPHABETICAL_INDEX is set to YES. IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output # The default value is: YES. GENERATE_HTML = NO # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a # relative path is entered the value of OUTPUT_DIRECTORY will be put in front of # it. # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). # The default value is: .html. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a user-defined HTML header file for # each generated HTML page. If the tag is left blank doxygen will generate a # standard header. # # To get valid HTML the header file that includes any scripts and style sheets # that doxygen needs, which is dependent on the configuration options used (e.g. # the setting GENERATE_TREEVIEW). It is highly recommended to start with a # default header using # doxygen -w html new_header.html new_footer.html new_stylesheet.css # YourConfigFile # and then modify the file new_header.html. See also section "Doxygen usage" # for information on how to generate the default header that doxygen normally # uses. # Note: The header is subject to change so you typically have to regenerate the # default header when upgrading to a newer version of doxygen. For a description # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard # footer. See HTML_HEADER for more information on how to generate a default # footer and what special commands can be used inside the footer. See also # section "Doxygen usage" for information on how to generate the default footer # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of # the HTML output. If left blank doxygen will generate a default style sheet. # See also section "Doxygen usage" for information on how to generate the style # sheet that doxygen normally uses. # Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as # it is more robust and this tag (HTML_STYLESHEET) will in the future become # obsolete. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_STYLESHEET = # The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined # cascading style sheets that are included after the standard style sheets # created by doxygen. Using this option one can overrule certain style aspects. # This is preferred over using HTML_STYLESHEET since it does not replace the # standard style sheet and is therefore more robust against future updates. # Doxygen will copy the style sheet files to the output directory. # Note: The order of the extra style sheet files is of importance (e.g. the last # style sheet in the list overrules the setting of the previous ones in the # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_STYLESHEET = # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note # that these files will be copied to the base HTML output directory. Use the # $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these # files. In the HTML_STYLESHEET file, use the file name only. Also note that the # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_EXTRA_FILES = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to # this color. Hue is specified as an angle on a colorwheel, see # https://en.wikipedia.org/wiki/Hue for more information. For instance the value # 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 # purple, and 360 is red again. # Minimum value: 0, maximum value: 359, default value: 220. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors # in the HTML output. For a value of 0 the output will use grayscales only. A # value of 255 will produce the most vivid colors. # Minimum value: 0, maximum value: 255, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the # luminance component of the colors in the HTML output. Values below 100 # gradually make the output lighter, whereas values above 100 make the output # darker. The value divided by 100 is the actual gamma applied, so 80 represents # a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not # change the gamma. # Minimum value: 40, maximum value: 240, default value: 80. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting this # to YES can help to show when doxygen was last run and thus if the # documentation is up to date. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_TIMESTAMP = YES # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML # page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_MENUS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_DYNAMIC_SECTIONS = NO # With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries # shown in the various tree structured indices initially; the user can expand # and collapse entries dynamically later on. Doxygen will expand the tree to # such a level that at most the specified number of entries are visible (unless # a fully collapsed tree already exceeds this amount). So setting the number of # entries 1 will produce a full collapsed tree by default. 0 is a special value # representing an infinite number of entries and will result in a full expanded # tree by default. # Minimum value: 0, maximum value: 9999, default value: 100. # This tag requires that the tag GENERATE_HTML is set to YES. HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development # environment (see: https://developer.apple.com/xcode/), introduced with OSX # 10.5 (Leopard). To create a documentation set, doxygen will generate a # Makefile in the HTML output directory. Running make will produce the docset in # that directory and running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_DOCSET = NO # This tag determines the name of the docset feed. A documentation feed provides # an umbrella under which multiple documentation sets from a single provider # (such as a company or product suite) can be grouped. # The default value is: Doxygen generated docs. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_FEEDNAME = "Doxygen generated docs" # This tag specifies a string that should uniquely identify the documentation # set bundle. This should be a reverse domain-name style string, e.g. # com.mycompany.MyDocSet. Doxygen will append .docset to the name. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_BUNDLE_ID = org.doxygen.Project # The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. # The default value is: org.doxygen.Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. # The default value is: Publisher. # This tag requires that the tag GENERATE_DOCSET is set to YES. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop # (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on # Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML # files are now used as the Windows 98 help format, and will replace the old # Windows help format (.hlp) on all Windows platforms in the future. Compressed # HTML files also contain an index, a table of contents, and you can search for # words in the documentation. The HTML workshop also contains a viewer for # compressed HTML files. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_HTMLHELP = NO # The CHM_FILE tag can be used to specify the file name of the resulting .chm # file. You can add a path in front of the file if the result should not be # written to the html output directory. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_FILE = # The HHC_LOCATION tag can be used to specify the location (absolute path # including file name) of the HTML help compiler (hhc.exe). If non-empty, # doxygen will try to run the HTML help compiler on the generated index.hhp. # The file has to be specified with full path. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated # (YES) or that it should be included in the master .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. GENERATE_CHI = NO # The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) # and project file content. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. CHM_INDEX_ENCODING = # The BINARY_TOC flag controls whether a binary table of contents is generated # (YES) or a normal table of contents (NO) in the .chm file. Furthermore it # enables the Previous and Next buttons. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members to # the table of contents of the HTML help documentation and to the tree view. # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that # can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help # (.qch) of the generated HTML documentation. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify # the file name of the resulting .qch file. The path specified is relative to # the HTML output folder. # This tag requires that the tag GENERATE_QHP is set to YES. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace # (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual # Folders (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- # folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom # Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom # Filters (see: https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- # filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: # https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = # The QHG_LOCATION tag can be used to specify the location of Qt's # qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the # generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be # generated, together with the HTML files, they form an Eclipse help plugin. To # install this plugin and make it available under the help contents menu in # Eclipse, the contents of the directory containing the HTML and XML files needs # to be copied into the plugins directory of eclipse. The name of the directory # within the plugins directory should be the same as the ECLIPSE_DOC_ID value. # After copying Eclipse needs to be restarted before the help appears. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_ECLIPSEHELP = NO # A unique identifier for the Eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have this # name. Each documentation set should have its own identifier. # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. ECLIPSE_DOC_ID = org.doxygen.Project # If you want full control over the layout of the generated HTML pages it might # be necessary to disable the index and replace it with your own. The # DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top # of each HTML page. A value of NO enables the index and the value YES disables # it. Since the tabs in the index contain the same information as the navigation # tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. DISABLE_INDEX = NO # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. If the tag # value is set to YES, a side panel will be generated containing a tree-like # index structure (just like the one that is generated for HTML Help). For this # to work a browser that supports JavaScript, DHTML, CSS and frames is required # (i.e. any modern browser). Windows users are probably better off using the # HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can # further fine-tune the look of the index. As an example, the default style # sheet generated by doxygen has an example that shows how to put an image at # the root of the tree instead of the PROJECT_NAME. Since the tree basically has # the same information as the tab index, you could consider setting # DISABLE_INDEX to YES when enabling this option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. GENERATE_TREEVIEW = NO # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. # # Note that a value of 0 will completely suppress the enum values from appearing # in the overview section. # Minimum value: 0, maximum value: 20, default value: 4. # This tag requires that the tag GENERATE_HTML is set to YES. ENUM_VALUES_PER_LINE = 4 # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used # to set the initial width (in pixels) of the frame in which the tree is shown. # Minimum value: 0, maximum value: 1500, default value: 250. # This tag requires that the tag GENERATE_HTML is set to YES. TREEVIEW_WIDTH = 250 # If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to # external symbols imported via tag files in a separate window. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML # output directory to force them to be regenerated. # Minimum value: 8, maximum value: 50, default value: 10. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_FONTSIZE = 12 # Use the FORMULA_TRANSPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are not # supported properly for IE 6.0, but are supported on all modern browsers. # # Note that when changing this option you need to delete any form_*.png files in # the HTML output directory before the changes have effect. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. FORMULA_TRANSPARENT = YES # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. FORMULA_MACROFILE = # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see # https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path # to it using the MATHJAX_RELPATH option. # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. USE_MATHJAX = NO # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: # http://docs.mathjax.org/en/latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_FORMAT = HTML-CSS # When MathJax is enabled you need to specify the location relative to the HTML # output directory using the MATHJAX_RELPATH option. The destination directory # should contain the MathJax.js script. For instance, if the mathjax directory # is located at the same level as the HTML output directory, then # MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. # The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = http://www.mathjax.org/mathjax # The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax # extension names that should be enabled during MathJax rendering. For example # MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site # (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_CODEFILE = # When the SEARCHENGINE tag is enabled doxygen will generate a search box for # the HTML output. The underlying search engine uses javascript and DHTML and # should work on any modern browser. Note that when using HTML help # (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) # there is already a search function so this one should typically be disabled. # For large projects the javascript based search engine can be slow, then # enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to # search using the keyboard; to jump to the search box use + S # (what the is depends on the OS and browser, but it is typically # , /
Open-source Jazz Jackrabbit 2 reimplementation
Brought to you by @deathkiller

## Introduction Jazz² Resurrection is reimplementation of the game **Jazz Jackrabbit 2** released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension and MLLE. This repository contains fully rewritten game in C++ with better performance and many improvements. Further information can be found [here](https://deat.tk/jazz2/). [![Build Status](https://img.shields.io/github/actions/workflow/status/deathkiller/jazz2-native/linux.yml?branch=master&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZmZmZiIgZD0iTTI0IDIuNXYxOUwxOCAyNCAwIDE4LjV2LS41NjFsMTggMS41NDVWMHpNMSAxMy4xMTFMNC4zODUgMTAgMSA2Ljg4OWwxLjQxOC0uODI3TDUuODUzIDguNjUgMTIgM2wzIDEuNDU2djExLjA4OEwxMiAxN2wtNi4xNDctNS42NS0zLjQzNCAyLjU4OXpNNy42NDQgMTBMMTIgMTMuMjgzVjYuNzE3eiI+PC9wYXRoPjwvc3ZnPg==)](https://github.com/deathkiller/jazz2-native/actions) [![Latest Release](https://img.shields.io/github/v/tag/deathkiller/jazz2?label=release)](https://github.com/deathkiller/jazz2/releases/latest) [![All Downloads](https://img.shields.io/github/downloads/deathkiller/jazz2/total.svg?color=blueviolet)](https://github.com/deathkiller/jazz2/releases) [![Code Quality](https://img.shields.io/codacy/grade/64eb3ca12bd04c64bf3f3515744b591a.svg?logo=codacy&logoColor=ffffff)](https://www.codacy.com/app/deathkiller/jazz2-native) [![License](https://img.shields.io/github/license/deathkiller/jazz2-native.svg)](https://github.com/deathkiller/jazz2-native/blob/master/LICENSE) [![Discord](https://img.shields.io/discord/355651795390955520.svg?color=839ef7&label=chat&logo=discord&logoColor=ffffff&labelColor=586eb5)](https://discord.gg/Y7SBvkD) ## Preview
Preview
## Running the application ### Windows * Install [Microsoft Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) * Download the game * Copy contents of original *Jazz Jackrabbit 2* directory to `‹Game›\Source\` * Run `‹Game›\Jazz2.exe`, `‹Game›\Jazz2_avx2.exe` or `‹Game›\Jazz2_sdl2.exe` application `‹Game›` *denotes path to Jazz² Resurrection. The game requires **Windows 7** (or newer) and GPU with **OpenGL 3.3** support. Game files should **not** be copied to* `Program Files`*. Cache is recreated during the intro cinematics on the first startup, so it can't be skipped. Also, the sound effects in the intro cinematics require the cache, so they will be missing the first time the game is started up.* ### Linux * Download the game * Install dependencies: `sudo apt install libcurl4 libglew2.2 libglfw3 libsdl2-2.0-0 libopenal1 libvorbisfile3 libopenmpt0` * Alternatively, install provided `.deb` or `.rpm` package and dependencies should be installed automatically * Copy contents of original *Jazz Jackrabbit 2* directory to `‹Game›/Source/` * If packages are used, the files must be copied to `~/.local/share/Jazz² Resurrection/Source/` or `/usr/local/share/Jazz² Resurrection/Source/` instead, please follow instructions of specific package * Run `‹Game›/jazz2` or `‹Game›/jazz2_sdl2` application * If packages are used, the game should be visible in application list `‹Game›` *denotes path to Jazz² Resurrection.* `~` *denotes user's home directory. The game requires GPU with **OpenGL 3.3** or **OpenGL ES 3.0** (ARM) support. Cache is recreated during the intro cinematics on the first startup, so it can't be skipped. Also, the sound effects in the intro cinematics require the cache, so they will be missing the first time the game is started up.* Alternatively, you can use package repository for your Linux distribution:
[![ArchLinux](https://img.shields.io/badge/Arch%20Linux-grey?logo=archlinux&logoColor=ffffff)](https://aur.archlinux.org/packages/jazz2-bin) [![Flathub](https://img.shields.io/flathub/v/tk.deat.Jazz2Resurrection?label=Flathub&logo=flathub&logoColor=ffffff)](https://flathub.org/apps/tk.deat.Jazz2Resurrection) [![Gentoo](https://img.shields.io/badge/Gentoo-grey?logo=gentoo&logoColor=ffffff)](https://packages.gentoo.org/packages/games-arcade/jazz2) [![NixOS](https://img.shields.io/badge/NixOS-grey?logo=nixos&logoColor=ffffff)](https://search.nixos.org/packages?channel=unstable&show=jazz2&from=0&size=50&sort=relevance&type=packages&query=jazz2) [![OpenSUSE](https://img.shields.io/obs/games/jazz2/openSUSE_Tumbleweed/x86_64?label=OpenSUSE&logo=opensuse&logoColor=ffffff)](https://build.opensuse.org/package/show/games/jazz2) [![Ubuntu](https://img.shields.io/badge/Ubuntu-grey?logo=ubuntu&logoColor=ffffff)](https://xtradeb.net/play/jazz2/) ### macOS * Download the game and install provided `.dmg` application bundle * Copy contents of original *Jazz Jackrabbit 2* directory to `~/Library/Application Support/Jazz² Resurrection/Source/` * Run the newly installed application `~` *denotes user's home directory. Cache is recreated during the intro cinematics on the first startup, so it can't be skipped. Also, the sound effects in the intro cinematics require the cache, so they will be missing the first time the game is started up.* Alternatively, you can install it using [![Homebrew](https://img.shields.io/homebrew/cask/v/jazz2-resurrection?logo=homebrew&logoColor=ffffff&label=Homebrew&color=b56b2b)](https://formulae.brew.sh/cask/jazz2-resurrection) `brew install --cask jazz2-resurrection` ### Android * Download the game * Install `Jazz2.apk` or `Jazz2_x64.apk` on the device * Copy contents of original *Jazz Jackrabbit 2* directory to `‹Storage›/Android/data/jazz2.resurrection/files/Source/` * On **Android 11** or newer, you can *Allow access to external storage* in main menu, then you can use these additional paths: * `‹Storage›/Games/Jazz² Resurrection/Source/` * `‹Storage›/Download/Jazz² Resurrection/Source/` * Run the newly installed application `‹Storage›` *usually denotes internal storage on your device.* `Content` *directory is included directly in APK file, no action is needed. The game requires **Android 5.0** (or newer) and GPU with **OpenGL ES 3.0** support. Cache is recreated during the intro cinematics on the first startup. Also, the sound effects in the intro cinematics require the cache, so they will be missing the first time the game is started up.* ### Nintendo Switch * Download the game * Install `Jazz2.nro` package (custom firmware is needed) * Copy contents of original *Jazz Jackrabbit 2* directory to `/Games/Jazz2/Source/` on SD card * Run the newly installed application with enabled full RAM access *Cache is recreated during the intro cinematics on the first startup, so it can't be skipped. It may take more time, so white screen could be shown longer than expected. Also, the sound effects in the intro cinematics require the cache, so they will be missing the first time the game is started up.* ### Web (Emscripten) * Go to https://deat.tk/jazz2/wasm/ * Import episodes from original *Jazz Jackrabbit 2* directory in main menu to unlock additional content *The game requires browser with **WebAssembly** and **WebGL 2.0** support – usually any modern web browser.* ### Xbox (Universal Windows Platform) * Download the game * Install `Jazz2.cer` certificate if needed (the application is self-signed) * Install `Jazz2.msixbundle` package * Run the newly installed application * Copy contents of original *Jazz Jackrabbit 2* directory to destination shown in the main menu * Alternatively, copy the files to `\Games\Jazz² Resurrection\Source\` on an external drive to preserve settings across installations, the application must be set to `Game` type, `exFAT` is recommended or correct read/write permissions must be assigned * Run the application again *Cache is recreated during the intro cinematics on the first startup, so it can't be skipped. It may take more time, so white screen could be shown longer than expected. Also, the sound effects in the intro cinematics require the cache, so they will be missing the first time the game is started up.* ## Building the application This section contains only a brief explanation of the build process. For a more detailed explanation, including build configuration parameters, please refer to [the developer documentation](https://deat.tk/jazz2/docs/). ### Windows * Build dependencies will be downloaded automatically by *CMake* * Can be disabled with `NCINE_DOWNLOAD_DEPENDENCIES` option, then download [build dependencies](https://github.com/deathkiller/jazz2-libraries) manually to `.\Libs\` * Build the project with *CMake* * Alternatively, download [build dependencies](https://github.com/deathkiller/jazz2-libraries) to `.\Libs\`, open the solution in [Microsoft Visual Studio 2019](https://www.visualstudio.com/) (or newer) and build it ### Linux * Build dependencies will be downloaded automatically by *CMake* * Can be disabled with `NCINE_DOWNLOAD_DEPENDENCIES` option, then download [build dependencies](https://github.com/deathkiller/jazz2-libraries) manually to `./Libs/` * System libraries always have higher priority, there is no need to download them separately if your system already contains all dependencies * In case of build errors, install following packages (or equivalent for your distribution):
`libgl1-mesa-dev libglew-dev libglfw3-dev libsdl2-dev libopenal-dev libopenmpt-dev libcurl4-openssl-dev zlib1g-dev` * Build the project with *CMake* ### macOS * Build dependencies will be downloaded automatically by *CMake* * Can be disabled with `NCINE_DOWNLOAD_DEPENDENCIES` option, then download [build dependencies](https://github.com/deathkiller/jazz2-libraries/tree/macos) manually to `./Libs/` * Build the project with *CMake* ### Android * Install Android SDK (preferably to `../android-sdk/`) * Install Android NDK (preferably to `../android-ndk/`) * Install Gradle (preferably to `../gradle/`) * Build dependencies will be downloaded automatically by *CMake* * Can be disabled with `NCINE_DOWNLOAD_DEPENDENCIES` option, then download [build dependencies](https://github.com/deathkiller/jazz2-libraries/tree/android) manually to `./Libs/` * Build the project with *CMake* and `NCINE_BUILD_ANDROID` option ### Nintendo Switch * Install [devkitPro toolchain](https://devkitpro.org/wiki/devkitPro_pacman) * Build the project with *CMake* and devkitPro toolchain ```bash cmake -D CMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/Switch.cmake -D NCINE_PREFERRED_BACKEND=SDL2 ``` ### Web (Emscripten) * Install [Emscripten SDK](https://emscripten.org/docs/getting_started/downloads.html) (preferably to `../emsdk/`) ```bash cd .. git clone https://github.com/emscripten-core/emsdk.git cd emsdk ./emsdk install latest ./emsdk activate latest ``` * Build dependencies will be downloaded automatically by *CMake* * Can be disabled with `NCINE_DOWNLOAD_DEPENDENCIES` option * Copy required game files to `./Content/` directory – the files must be provided in advance * Build the project with *CMake* and Emscripten toolchain ### Xbox (Universal Windows Platform) * Build dependencies will be downloaded automatically by *CMake* * Can be disabled with `NCINE_DOWNLOAD_DEPENDENCIES` option, then download [build dependencies](https://github.com/deathkiller/jazz2-libraries) manually to `.\Libs\` * Run *CMake* to create [Microsoft Visual Studio 2019](https://www.visualstudio.com/) (or newer) solution ```bash cmake -D CMAKE_SYSTEM_NAME=WindowsStore -D CMAKE_SYSTEM_VERSION="10.0" ``` ## License This project is licensed under the terms of the [GNU General Public License v3.0](./LICENSE) and uses extensively modified [nCine](https://github.com/nCine/nCine) game engine.deathkiller-jazz2-native-2a7ccef/Sources/000077500000000000000000000000001512772601700205335ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/App.manifest000066400000000000000000000037551512772601700230150ustar00rootroot00000000000000 UTF-8 true PerMonitorV2 SegmentHeap true true deathkiller-jazz2-native-2a7ccef/Sources/AppRun000066400000000000000000000001341512772601700216610ustar00rootroot00000000000000#!/bin/sh cd "${APPDIR}/usr/bin/" "${APPDIR}/usr/bin/jazz2" /appimage-path:"${APPIMAGE}" $@ deathkiller-jazz2-native-2a7ccef/Sources/Icons/000077500000000000000000000000001512772601700216065ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Icons/1024px.png000066400000000000000000012155601512772601700232640ustar00rootroot00000000000000PNG  IHDR+7IDATxٻDa 3{<APukϩ# i;I, lmx /~=LVx{?v gizG}8<>n~g0AAtIk+pGCp>> E=e* sZ^N?Q7ں'VrD_ 7jh 0_[!"]=  5u׭3"0pjPւڶ.?0S3l>+c)N9y \;kx|y~o{LȏKvMGAy~|rpP'1#j=MOQ1kw#x?9lWζ~Y>^hO"cȿ;]oSd?nYfܦv匈؞ϾЮ^oݾBG^o-/G{#ݎ[߂ID_9 >"@\V].~L :e Ƒ r,}HВc9l]ϳG @Tvc%i$I|>h=3GmSɽ {$I$I@$ׄ?(Q,xun m$V(7 L$IWb$I48^S߿׏qw_aESO9F;QQNC4.pvݑO܃P?T?گ 4$I$I I_w2 }= VQSm@zSy6ZYp\Аskz35zP>1?ļF]MG>'@I$I3#I$[ qͦxO+mRPB Bmnr N觿M_w_b, `8d*>` ;"ŷ9 :_GAkPxC$>3}O2$;)  $I$II$<šy"f扎,sA`zX!SА!&cSqLacncc좿Basv(y؟FB$EF;`<7^rFc|QHE| EU 7 5e.qd16}˱Xf{I@d,hAHܮTzm=?zf$I$I@$ɟ=~ϧ&: si]]϶WR 3Um@f߶:㿑tVZȞwߟpBM^7zB~ߖbCb&NHCugւ$ yOž˂o=}^kAEN)!P`0j_z _m!@$,l @I$I$IgO{2No![{Ğ`emaݮ̺(h< Kq-3i,l#b\f7W&= ~g L{\-=Dn.)LbM7em}OPk`lߺgT C& [P= *מPEGܙq_s ]s&@$I|=H$ϻﳿ'6s\ӏ!;t ~X[iqgL!Jm6J5v=$w'ȡCXe)L*b| u @1Ox+[\u;cӀBjo=X?x4pǞc M uxEli!]Q x>i$I$# $I~\?_)8Ϸ C30 */5N[_7d{WikM˷pld pվ^[Lw>4`^lD//~?tKK6Le.@UzĄi 0."ܻpQjSm{Uدwkd?"<:!Xtb oFi$I$& $I7%SY[fg3{&{e O/޾K\ 8~6󼕵œlvWg oCh /qwhmUb.%"̇5pL""ҫWAo!^M?x(&r8XQ'zVԫ91mK!TSQsGH~.H$ICI$pA0}ˍ/kZW#AUBK(;[Rò[DpHߔXō3j~_:Kb{$ֵ# $Hp-iG]qF7?^>^<)aLV ff?.(sUiOQ鲄J=Eb%}p5C+TVe(BтWQbc@" 8"m?H$IMI$P'HGhX6|-}dջHE6,0,}IbIWW?gZ zypԎBUl!Gڼork=y3vƿ ~ϊGz;@3SNȨ(y7QkPIKoKi=z뽟D8P Yv[w.U?Ҕ) hHAx{X7ёTQS~[1koI-@Jtx< ` K В\Z_B; }Ti|$e1S0*+@c#0Uq%"j`4t>6s΄cݮTO`J%#գ}{`<&O} a8/һX?Ǫ]7-wC3 qALp,a{8eK7޻T4M\#ƫ 92} 2l?V~c"V4P_H4ZWqj>-iDo ׁCb$ِ %ܬ& LPD8h ]FrGFZqW^Jts 28ϼ8nV6p9 m<I? ;yfJj"a0Dž0e}e\F2ϑ88έ Ps< (c0w4i =ǧ_22 (%YE}) bAߏ%i1f3-f v@1<l17>9b=WS tL1:THfz y?jΖC9B~Ɂ#N7eLt8 25ĿCе hrAĈ u$:KǨ;6.H^T٭VOĿY⟮Ys;:8)G[ :-pٓA*Ye0HL>G $w-\`M!6 MϨ0+i"p4qRj B&2!wG1HIQ|ǜY/[666666]l`ccc'V|OԘȜ!Nj$_fa|rϛGb F)L1u5H\A2pC$*#'KOY! (;C5S*:ҧ`$+8?Oȃ8ý(U$u`_&~.?1\DN)1)<̍?W& `=:4GlA4ȯ~h:02Yc2AlnёR "T2XW4?2_'wfZ>" _`>ۃ#EQrDXs% GrIi*AbR9?yXl"%O]F(GN>DZD|?!< "H 1Ԓ?@3T9 i>H>Y.ӈ!Y'To$y Wt Rx)*J+ )?Vwg :mllllc"WOWwcsǾ)G0(# n<A$f-  8r N +6kDYanULRuR @&@As A-sm ~u< wCŲBx,4`e @bճZI*.kIT ť*ΏUW]AWB@*8x{TY +[olllll _V &~DUKRq"HG3ьMtJE-t5I}OGJ9WA3 \sъ &J"HBPCr~R)QBh#6jF!+ :M2Tƀ*CK*BY-t &3՞"@҄Hbs Mׯ>pLQX:4A`u"%֡l}A5 ::u牯x:U7l[6666޵0ymD̥n)ɉ%; V`aaaי3&<{Q̘X)i #7C9h{PHi%ctjb's?|E8.e38q(AR!*m! n`CAP t` ${o> J +p5yiZX_l%LEf mɉrl ZM]}v6+e1hq cؓD$-O  vܝ$V|-R¬[*s- 8j!*I: k)P3P1РSNX hgy]0(? ] ʐNk]lƺ 51bw#ռDu$ Qm%(c`'?K? Aw F?m{#/*2,,,,,,X_l )dzҚfFQhuE9,45=rK\[Ώtl0hPcR X mέ삟^W!ou}Af,)A(;\G/$Qf?ȌSE.9 .zh'*I1 Al37:/=EIN1{ӲMnpymk5֚9ؚf+B`t+2ښ0-,,,,,-X?3RX lxo4]y`GvY}JAJSMR9#&y 7eA  lSa8) | @Ss&!Ԑ2c^̸t~B~M*BD(:a=A"Zo:hJ3Ul2h ]A0t$QB!QW:p#*!xN :@A0-8uح3zsCSGn I.G|twvxw >FMv1~R~s  {;N\dx3mn{ nU憀`]skŴp͉;}woȕBUahIABeCY@2pLrJצת4ςT_>UCy<@u! *Bd]ȋ2u$8lUA:ȳ?vB:-D0̞cΡmhd4H]C};C(p}UVB,/?0O[' >Ձ롂 V?]YXCssvֆyo/qoۼ7_=5(@fݹ2tC&g@~ nC1IچlMS)Ncmn1_nnjRMy1{t~$B3CDv>I姄82?63&^d&@d͖Ĝ5/O )$0h 9 3/,lҠEM3\P5xqU=ܚQdckjAIul?@By]&9N'P& ? =V~paaaaߊUXXXX i8s#mk-}r=f=Kᲇ=$ o w?Yn_巯~ .40NG* :G] F#*Iz:b| Ad}K y3I] AK RѤS'4K DγrT5( ?\.:0(6 s]W9TWXpYqMTSfir7lk#g}wDT nC;cͿyu8-o*,,,}b3y[0=CfLo{X{8~]mo7kvw=Ww"@vи wQ/N=OF~m!C6RD{`rR9PO2bQ1xVtmۧFOE!BsCL~`Pe/&J tѫ h7 S&f2Cmj6 EwAփtH9$s$ sG-@G_ eE* g9Gg.>. V`aawSd%fx,h)&UmO=ZbaE]ނ Z71MMݫ@E=_~/?s.iAj|ӌi lJz6nClA4QHY;\g(ky'D *rAeOjW@{B`x 2.²Gz_6{d0տ|ֳ@v6ɶ7%ܿW;5@:Aj~"D@9 3W=yqJB @^;94"L 0'KT TEqz@cCf-1$ ͭ{0_}nYaoܛ{:ކ˴g! Wť[ϕk=40^ٟ&,=_詓9rDj*T S8׊}B:>2i/*{Jf>-~{k8&-TPУPtN` mJ޲?Flcl\|L3~3u?3˔WHQ/?C '@ p߯g3X ,,,,,ӱ + 6sb}k`ۂ֡il1gK۶`l_h|y4ި5 ) }s&讠o{_@ƚIdp&D RqGUf+&% D2wKz 6A3W@ǻ^M9eK8wdA?yTJ@MOÄ=O Ug.A=GB3Q`MFhM֌ hTj T]pwH%Mnf9&+'23AEAE8Ts]BMF>a 4~Ӈ'Cc XfnYhdiY?g~O7@ F(bPE8vwn]< {r曣 FE(ِD/C4@9kW_u.p2?ǡZO^eA@ wQP89GBXS=aBTp{y^(ϙ`316 t0?G ̷ icky@:Bҷ1xݏ\E 7%NHs~<5Q)fIŒE3uIF*CpӴwwJaAh6 &k^Pfd5ClQ$nsywwh[g_ѨA ϊ bM'B_:XXXXX u;Ufi3SL269˗eZ>=/.4;i;n20 w6֥&Bh]MpEtE#yo2dO3s* d0GSz]Uc8> WZL|^?k:z3'YIu}4a>9I.2{^nfWM(TAJq.ܸq)^9Io$l*7&ۣ,lG'rsàCP ac; =A*ɇwFۗ%4`P[ÈSkbG a L&ջZ^yC$$B`R\Αeh˼}TĠf,2Dʗ_ \lՄ߻_#Os iE!2e|͌_ 뉼f%ؔ\>{]Ͼhp#Ƀ>, $a/e/>8{|+8S_#׋:~&|>OJ $Ap_ D3݄Η]! JDPq@lf\H(Fi=}3g52?mkQBG}6`SM?q_\rmS+6[2BWJ|D:QC' @#%EPщhPjc?cmkhH%Ztp8g*h$%Yd\'N!uAndOZqL:\U[)SiXԚ/{GR €lͯJA3U+)7nw.ܸq? qD}?Ƃ˓ģg[-7KwO>Ti!K6bh.ߧPf *YC4o"e>b gD~eJ*"kTJO6 J:>gվ,- vQ Vmd 0Vin͚50t8V$W ]@r z]k?SzG%wS洞 R2h֪/ < dPw@j{gƌ a3kѡ.Ez1>: ts?qƍwƍܮ׻"ͫi˜VFcK65_0[jRjU(*DӬGSPo@8/J7a EjX`Su:#P)0ɥXG(M5n:.ɩQRK?~.G/ZD1əR\i2TJ Q 4z uqRfAxX$9Z BZ/0y/Ys71mTs\ ėk2J1HR=0J!K]=o0s=(3f3 6IOf Ձԑ]֣48<$T_x@QK{ܸqƍpƍ&_~ןDd~klɊݲ-ON҆־Ζj7KO0D)=˟x :V [jնeP-("s%Yarh8e)\| >BK< ,ey9?l]ߘ9+>r-IrI_B@ `] [&ZbZOoBxHz}h#q7!Fv^aTº#i9Ԓ WšB0xT"ʅXy]nsGR]{D rM- -6HAuvZIU.sZ!yHba?̕d "՗:Jo_Y`qƍwƍ4a a-k}?͌kzHiF@HMS iCDS2B!ڡiXMi]c%Y)dZ90?^}B+~c^X׀#̿_/P"e<χ]}gnWט *E /T1zGjyd:hDuKD`xL-"?zO8@Dyj'!ȯ4 dL]1Qix7G@s]l rûz~M[m6J ׷pUhoa"K{{nf*p$X#T\w>'>oƍ7nqnܸOgr#&8}5_Eys79lD!U$FbAOަOPlm'<ޅZЁ$eUX&]25o&X: &/ĵ4Ō9ɒӚ*,Tɂx5h s;9kix+N,%%}/5gLA+>2N$+H(B&!QOG Od|x:&؈XgH :ȮΘzH.]P;Fo[=V%`ứmTjDCX2$H&$``v4 %iP=g?w-:`!7ӿѤƍ7n7nc?32=4)x~KQH}xF֬'aL6coބxJ,/ .'K>hmJgG{Uq X4IHP꿜X'@26?KrJ'2W?)[g2~74@<4t59~}f/ (k% 01B_OQB`n8 ( =`4]`W!9^N%R3E%bw$ShTD3b3ݍֽ[(a]|ߍb>M`0@8 #¤ VA 38hA0$>J"{4ݺAuIpJV,ŧ~B'*~Moܸq?wƍTkhךY{lxx3= FF)+|,^f0Ch}ֵ3dƢ6 91bA.ʎ1:5JTl/L?P3'L3i.cja˹&4jsPqgWn iC 5|$_uk}(\?Jr) KY %)AR ΑS] k}^a$@d BDb` ")se (:fR U 03#a&YZ]JrkJB= ],މxdon}Ђ]sF=#K"= 0n5 d ,ԓjv::iaapors@`MAI6 !zU<;$B/SJ q HPXRE7n]qu̿$1"kۂrvfmk6_ǣe26'6Rː!5!)4(6CCЈdb1j؟hc1 ~c(J2]WNԗ?&9-cRUT)Je|{)FE?,=z)'._Ǐ2ϵ_(,>*p{ͱq*AIAA}<4sCS=$3c7͈i%pI9K$#"aT1/D!!Pƿ(a%dO.D,tY߄]%i;%a1suB<ZAC` uw!`aN&]F{0 Y؍0Eu,TUS)0t#u(̯wƍ(9n#;Iyf?MJ=}Uw͝@(dS&%ݸq.ܸqwǯE D@-ڬXJVcֿg{g4J}M-k#YP!qW$U]G 4dW@u?'d.2 2v]߯KS '@܆^Obh9跡4 cXw^]'-D/&J=5$ h)uE#<ؤ`~aS.c ޘJ;L|6|_8L͞&53>]O) 8y>ʙN$W6BS'}}8S2}]k)G] MTQ`c7]죙 қ[6L,vZa;\7ص_0Ֆ&9goKr25X2^iͦ9iƂF*pYI\.D2#sg_pi\4y-dIucr:ARbk^=.ݏMĂqmU4jaIfFbf AA4 `ݜHl~v=?S=bM7stɡq(Bخ&:f%uv_7nܸ]qAmb?nx+IciYN m6k$/ f%TZuS'Nah!"Arrߚ W9$1'l̆: E 9Bς>'5y -9r )k@js*JMEytIrj$N29X%2R!LJ]g<HuD|{rӉahVZMt#hΒ y~\BK_jPpJr&Nou"V:]RAvqI^:PE4!hLŠyP9݄ 816 LϺղu{5#Toޏ )?19ƍ7pnܸ;_+;1\uV1_>w׿m[>Jb֟> FiCZn&8L܎4:x>߿ǡ8fVf\a$ 5x'#\3$GJ%R׳We1fVȱV,\g-I ahNO`jQ_@>_8I;$5}Rk8H>ŋP /9hSl|u_L0ѹbB=]"p.`W@uB|r5V`y=sty~)8gBw ӀDuUߛJFY$q{4???"w)T-BAt#-T mDREFQh4+J-piw ɳКc?|~6҈axFL\ fpƍ|7nN ìXն?g4| ?H[hЇMB< ɮyg,Vq*Y +S/]2>H#/ .}b' 2?gu> ,4[ƧAԤ]֬zB\A K2yJИDbbP$|O`O^ҹN_F pt@]B#Ou:eaC\0M:M@gz6KG9% tץf>ˈBGco.r*d$L]`ƘW .B8Bi)Txs`7 7EJZ v- N||D&81 0LI?*)`PB~Rv݉OK6 Dr" ㄎd~xnܸqwƍ BrEomto;7IA<Դ ^!/2BQ&`^PX9?m +SgguFoFdw]3,:i| ξ7.rT 0}qfqAm RѫNA_z@O*jɗ;.19 ^\tAT ez*yc} }//N"U'i'7nsqnܸ;Kuaf3j-ۖ[}R݌f.y\[N̜=gPT:Y"@Mz'X  -9ib4Fs3&y8ݵwdO<2+CAMaOZJۄd}~ ^zR{< g`hPYo OwMU8%=cթ{M/"㴺Y\ieq=D@:K:J 8΅k#EKErhN:3~} g#Əa'C|@Q`pњ$ D誩p38I]adC>Ӛ`ٍߛCt!wwuKNpaU@7nܸq?wƍ'?+ddTfuV{) 9s,,ʵ Fz٦2g dUs$Idヂ0!Wʥ_>=s v⠟IGw):-U|~"5=/2OD|cLrN~K a pӽ_5zyCEB,1Gr']皅%ǹK9.WO P=kg 5,y^oJuZ ec!C{#PU MVRL P&a2ELX@Q ,$$oڧ~0"Do ɕ|@ D WP pƍL7nϷ-}oL=Ŋ|X);A z.ǛGuz"%%Ii?Ջ6ܾ(0V0xd1bŹxٴHAczSTC~bHbN6<F[Sk0Ԧ2$ӕCR_gOjg$쿾\ sFI~]?w7E_=tTK`v1ǚw2\Гi!R.ԹFvfbZ ' I W%![Li꩚cӂ̽x뇜~̌>ʒl  "Zj\.GS4&57QdPj`Y#uI46b@? Y\V u;ֽKr,D]o7Wr)pz@__7kvqϝ1%)ItPuƍ7l52I7Pn9(Gvm]A &֘k#]4}_w?.!=x@_B&xܱ?:6~&7npnܸg/g _A0%5́Fad!rEli]܇Z*z_j2vnj rKmx`Fd4v1o:S K\k'XV_J$ב~kZ*M_xy+qeVG_ZmQJԭ5[|>gjJe.9z1RQrփaw&ϝ|X9$2?PRהq(g*@1u6%)}J `(iQ0]wACI%G-12B$%ۻ>H *`[POM`C( ؚ5^ӡ]ϥ}ƋIOB "8;Ap7n.ܸqB*0, u\P 2AxFw}5(~4A3b ԆMI>&yDY$0F=6e<(ʦ13d y@-?.?xĖf'.;ЩtXytGM28H>~V Obm.NeX C`yP:-2prޚ0Q5'bry;5mz@cyT'΋!,5!Bn1-v h\8ۙ1Vq^?v tF6zꪗ@LDzڜiPM "i.Zy=$ `S 1ACh@g`vʭ5w\F#w>=\(ic!Gg@F CFgܸq?wƍ6$ilI~p}[\f !ilTBۮ4`([>}[Wju6tލ2Q Tpz|-bGϕOßBZ|<ک$&ku :ym-p5\߻P$S 3|edGK`J-Vm>*R"FVo*"."^Բz\A^?rHJZN]/?0#zK/nPA?Phvw psA4ψw'5댡 :79$#<({fgO"q36{J3twz۷5#rOt|90aʟ2 pƍl7nY/s!> jfƁ`N]tҜBHGl6WÉn|1{\uҒ-ȌʩRƀ ݚԆ]uIkS@S=n 7}Ax & 9}%;տE*~&7n]q_^bPϚev"s )d:+=>)un rrLȰudh̘%sW}c /]ͿuT}0¯%Iu-@ԗZӟwɘSG.ǖq1ܵ{u?e#ጿ;o/wP׵Vlޞ.<5RwK~[#|1M 4Y=n6 f"eA/@[7(k07cy}wU0$Am=ƍ7 7n;E ! Z5kF |Glr#nwbn|B%B;W00P28fs4h2Ԍ5Iרׯg j"-yj*yq9 2Xf^>Cɟ:]`lZǫJUef tnvBdh\ aDbꡁ 4 =^f[ׇ4LN6Qq-]ot) {/[My,ڙGv% 6J2 =hmӦ]`*MY#aax ''Bk)lKr!)Χv=Dtnܸqpƍk5q߬OǶ`l/͹AۼI}oA#i|xӗi m( 91RCRz2|!e'qͷC*_C[G~* f+jIe\`}1!]8?ˈIԜT-5mрQs0^4M(O7L2_~V!Eʘ VϗHXWhkEOeh/׋,W$1\=Nfκ 4OO F=2hkph4IoO#|"6@SCM /{ٗ I7;wܡ)u.;xnܸqpƍKK'0&Zck}=k"dlh-r!Bnro7ľ9: iCcЦO06K 3 fv;ng^WRN^NS-TCb ̧^~+HJQE~x>Q9ㄖBGBuG/x' ৈd4 kH5 8GII@D:m-LI va|̺`]^>7"u MSP.$ȩ/E8U)a|YpUBeKN3"3*a*308HIz]{ UjvͶFE6> ? Az m$ U'sq Gv)YZz qƍ7j@hͶm@p۾9 7m$a> T'4o:s?:;7lSb PI\}'a莉OCޟ NHJ ~6 *rR *F.͸V9|[菩#w[/rFl,.i&T_2o"j@F֏$-F&žycW܃2@-IH']C!VC(; 6o>-K)݃V@ u>67nܸ]qƟ kE) Џq8GnAnA$Mu|&Ge`#h9ȇhA'U"r-e9bD,v1Ag"5J-Ȫ]OgX&k7)PÏܟ3QGlwB^m7fbmߖH@\m4f必uBB@@p9|UWhu*;K8i25ɱuY~k oM_ncȾ <,)^`2<38;!&iwvè ロqlfA4YY$ֵi=6#0(|wiw>wNYd]9=ƍ?7nUr&Ƅ> ج;`zH !KZ'ظo:Ѧ7 !#:Zj3t$ $E@KD ~PKC=%v_{V>/2CcI44$B2BOxrk3 `z"x?'/A PKŨV3|R$q2upil 81/y7 uͬ|LU : `SII5a쳝*B=x=!ۜqTq"JU&A{ 4_\ke붌BUxzfv:3 3Յ'%:-cҺLeRk Peڙ꙳~K`2((y [i^Ͻ|I] DJ6<B IL ă"Z ހ&4H͸{Njf"@ pƍ-7 şڗZ';ѹ1jS iMS']rS¾i~Cσ]PzIB&g .$!Z PpjigZ7Ɉg8߇^}:HO1QwA'1jL٥\ XwoTz,ϚaϋKHGs4ox27"D3 %AZRC4f5|-q..)4;Jق귚gwl!.!}S$ 1zj[SwUcVeځ[eLG@ok['˔H4:;L]&(Ԣ9t~Pzu$C(+`BGDTp%;uZcy8-)>J6%+5-y#gdU,bJj "HiNRsPzzXv[~KjqԘ>,pO&I?]쓞TWJxo=#Pj$/+(qlWUUk܎eqCR>*[4MC!LCtNUt0(Me9Ίn湹iDQk^ U=ɮh U*k{BgU>Lwj2)cwASF./o쉑5b=kOYz_?츙cU}IV&}RU!H4DF:+ \WU<3y6%8Z_#8_Ir ~ lMѯ" PU &Lcc(`v!(WJɜsHY!=ד1uT<| 1҄ė>7666v`cc ,JztvAu)p8WeI/LA4iŻʜH\OoFp!2f9UaV?Xj0Ieb ߗ0[4Z\h~k9 }4섕d{tM Jc "o͉!-)%JF 'yԦkGg];z= JeS;SΗ@&/|-44iǕ #NR1n.3XeA&0`4{]pT-L0{,ثllllYƟXUMɹ 6%.P5r,[/C44?Z[0d/ At^q27ɀq(;9t/&8WW7S%P)"QI?z>WC"dd8/[BHbS7|G)fX -e7666 v`ccA(ݤF9T#{qeĆZT$xuMNz&+# '\Xgʞ[xҀך*0Vӟ: ? PETp6w(o{>1[HĒ@L~vJviWzV\i48{N!+.xS9%s`Bi=f*eM2Vn$^ P5$= iC9TG{`JjƿqQKoVUW >1P)X0CMQCU{VI8V(M $SQ\1edU㼀/QRnvpk&z;$T6|eᕡ.ROWA)UJʦb`Sb0bd)r '鉜Frޝ!~\B. 2r.sOc4+Kׅ7pLb}ġg<Ҹw"tN*f QZHIL_$N4sܥl249=fSMUfUYS ́au&Pn:y{:9-8C{M S‰>%J(]̊>$ +LOd{,A4'JxSU%7B?T5vU /EI zxZp/@olll ?&U"9FRT6c*4u^)^( "MQ)bǘYgAn/ϖ2/'EAzLyi|>ѵz&Ci$q/53ƲqͣO +$9Q|4|3-SLˤ"0yǵ!j& dDdg!X˪tb#{ys`yNItʿJbHDBLjs 1H WTU0$(4UlUϜ&#%@q"8C7QZSKpG_vKnlll ? .Mʮͼ`4 C %FK8obi5UZerXz^9uRĉWO Xz 1J D4!QzL1 *.ܐU Z*o&{r^QlIS ynK/cCQ=!zF5axWK:i_!T~7VHzΗ3ޘ`RNGNN7 #성Ьmp ׏)U5[7666 v`ccwYs7X0/ _Yʫ Rq6?澪$ pH\لv]FEe% <ћ"2[)_OQ/&֊jN{-uכ ~=!({9&L/Lj 8*'NjJ726t&Y^NL>)͏=gYS fIשUڹ<(gJPIydkj17yWڤZ?~!Prߛ ,-R+>)$+wLryVhE]pP80QH3y]HPɪvNJUsttC]0RBYD`b~x pUoT^`cccNlll[jPUzE _ sg9pىE>;Zp.2옫*RT̳83b-g:yW}?ɡХK3bЭ(@.IlS>_՜!p ErgS3onwq2-)Q+rI@`HkY t*W5С^S4$.N RS?S;ZXD4'4Q2-B^w#osR㼌2HsrUZ*RSKQM*[JBTW/q:Ma4)L~c?htȤ"(f&5~UU%$?a_4nU F+3ا44 VxH34͒4-~2ID`URD 0L_)„?۞f]?U O DzR+mu37"xJi"ac'ݓ A_+Hi}DYnFb Yx\˨ZqriA0uoN'rkзif웿6\{B'Jt.Iʼ>ŎE ϕI.-rcOfk;N7n#NT(8ߒ|hto"fOiDQQU΃(*"]C/Nfy$wR&b@[2$HA\:wh= Haet2@!h?҆+hN9j$I*]3$JL_4H\yRWNer1~ϣaJ6򹞾hn|.C~ T 6I x]XGgtyV^_C"8]5;3xN4i",M& +ڼ>8L)6Тg/)cD]W)1SgbP#+O@?oτβߺ=H31瑯VȮ%sA¤aK$J?HW=AB @G_ f--~8j݊4$ 0>=MNlll`S;ZZlFAӜwdTLR&VLF d;BTp Ե_cӭ}$׮NOGSnIo񿩒B(5ϿA<$,؜H5w)]NGaN"wZ`uU/\,?'"&0ʜ5'}f"gYmybI_Orz~X.wߖk ^uebɡٵoA;/߈2=RcrnbQi&+@#Z,7_{˾[,|h`c?JQ'MNlllJ<SSo'A3(*jo _J4W> jDNrhspB%r1/gH|pm:+ ` F! \$_~x >ʑc|w $*G;0 D'2=L@"/GHS`y}bfz{Z{/iQ}qI7sYU /(Kg|>V{ Q_WS!6qPmq_X)c&n34A>`=R0J:pdqI~x Aa8Z]~Y#$ 7\c-kQDxeH<Ϗ> g{*9jnɇ|NSRyJC4[U!.3%nCt#+14 ]N.sFu3>26uA 0(Y',,ĻB'A^;9P2 䜋!獫Dz~[vbhp;$TrA3FŻ͊L݁HڙŻH2/.'߼ˢ@hM CsbQdpt@ޢޠj<{ch$*R>IQ9ypױ/?8}AUYUz(0eJ79wVdtX%3e;l (1k{;)}f?ar>Q>? P); 2!̊RBΦ3W(g="f8 žn@@t*O0Ms4K1H 5aXƺta܇rjL(3_#!gĴ5a䈕hŸAK?^'|Ob.*,2x<ƺ9ͼn¯LC-s[^4{Ӥo(:ȗC/ы̇/D0QT@pVE oIy̙m!O 0.|foi,}6*+vU8\6w*'" |b?ȫ8`.x8h*qTFEVt\F:>!؈ ӬU=N0@ Oyȃi57i6f)vr6dz0UwB {Wv=44%lMs4jg3l>?=Z0G_M-1g'i/nTYϾ'*C1+vlDV&LE@|邙a Ht^D(o/\}2`MEQ~W@P丟+^)}$5b]}^H)"JkBEKa.y+WtkʆR̽@(^uJm i8Xrij\|ph4b$M]vW;,4V} `WiE VN!c Qb~UWLPb#%ƳؙPt K+~O'O4oq:ic'uŹz$F4M˵&1e8kz.z #D T ;6}@%'XzF9 >y|^?u5ί5a.srq=jQ$mк,]"H%u5&bM1m& x>C}"M%NuzI TyI5Z&qctp?<5ҧ\(zX=-ƯNlllJpS,0f4ƶ&.QRUXUcj3lD•Dt:xnvWB(WLM.'<j*RƜ_ Ҙ=taY| u`g ?%ۗ{I$5yT- %CşVEsN nw$lF ;9}G,<Gͼvp,+# =$ \<2/UNtnNd}ObM jp =4NR%C~5x.]πiT#jlm+#shk Oc,eƯxu_ x.FflثTVfVI ] } H^dItQ=:h6?3E3;w:7aWp⾒[+I!/_'yTaINl$qRu$-?Lk "u/6<$ureTd:9i=^Lf_J܉<~b9'Yr͖|ӻ=Og_3=r.Ԩut[=Y{ -~FjAByMpnFh멱 H+R^ƯIW ݸ% 2u(Vʫ.%-hcjAå0|&H$8GN5rӐu٨`4 Le<}1SN45Ư_=MևkTkNSJ? Oo⳱d*UI*/$C]?YByWoX%0D&A4Eādۿ "'UY7Mgn]>Ke|=eLNm:%|7?n@SjvB Nz^R&ϫ&Nb]fccc {_ UWa%$ x%?t|[)o{xE_UJNjt$DIުȭ萉2@8"$ KxL/*}v/Pp.pj U6ݧGCeA]u*wZU4j. i4_X-Ґ A[kh&:f} K^ }clԈyo1S(&ܱb+-ES:eZ`&ie6HTF0הu>c1L¦iaty7b˃(g"o1841={yXK9C[wiA8 q2E;S*^J?(k5xtqx] ^@/)LPT e2m &6V58DUAj:O,NXY9r0)Q>bj5Z|Υ(jL=˴7•i%>8LtZWMC: +9})Ne9_į/"'OT˗QOUX^c6ו ]Iȭ5[s~(_4vɯ}e?aALU.$r_g0.jz)tnq!`[4qJRj_Qd޵hK8fnXM=I&>l]= h=NUxm7c{EЩpO3PӋЩ$(}$ YM _C? Wks. # 뼦7S3v1}_;ŁC(2> ~ZPA"Aby')>},ÿa%7_4eUJpmN1̿. :)7$)8okVA t NA*+lڮέcaN'L2qzgW3(|9 qj8P} K,LC0R!]}{N#|N?#;}gk@ Q?TB>%ȩ0:%CaG<xrz|0h_&\ 9y8~]4ib/t!Zn?ύiyƝݻPxlŮD%sbC52.DvǪe#aX|2 PSW G@ήe] yA8Ȍs44M ޶em4$ ٦aaaJ,,,Ng xO! \O6lvN- kspAwC씟KAeM"k>*H$!a6)yҼB4Ɵs쪜=н:YӠ  9i = O0f0-I44v}qs|&ʾ^]yoA|t1:O J<?7Q݇ş~2I*!f5EBsLpgtRj) ŦLd 0QdF»Z(xM#J Wӥ@uh <$[}^bd#-iUό? `aaaa%~/|t87bB-X#Iͷ\ExFQj!E0[HWwJX/Ǖ#Aܦ.MOUAe #Aׂ wdMC΋XcZV`aa]߷&Dʧu.bԅ>n:@8z Owq ) JVKr)}gC]EAuvf,I<*Nmg:X}_1걒ġO*tlms摎$8uED=/5'뎃R}O"XZ4 a n.rs7Fo]t8)bd.Gu^Iqcd*q`  4_{}N#??ՎIL}!vtzmNiPFʝn0N`|aN[˔Gd~28ln!o:O?VN˽RfⓁ;?9NN>JR̥h.76y2 "Mw;G:#O/T;;3t0^@O,/|ճJ5Bhz19 q4la?]Km"z?fU]_z}nyAz΅8v!82ylDŊ[ diT&6@;dr۬H77($).E%y7j f]̡bOW XXX: {nJPݵ'?ڬٛ/ͥ7Hk"]f0unh49(6#D+D@H.YWdՅй2:P+r0;Ky|zkmAJk'8XvO,#JuUAp) kS 4Jc `r{)j*SFr?sJи>ouO(G!)Dwɮ*[wpbNx3&kϬQtuGgS[lQ.4d"1n*XPvX7=oU)) \XXXX Œ[YC5ms۶'h|G׆E4׋hHn :>ZÎ2$EPk qhOIN9%l+)Z*UXʵB1ncN!xIÁtHL#c%&ĩ:g]N^̨<~G z*U|<^Nތ;W'qLOzcUfc_ף:P^A[LLA8E FI9ё“R`9rQ'U`BWLDo& b68oZeOv΄`gv:`fmWonפFtZ]v Iq;c,ÿ ?4@BwKW.m9 emK2%YmcPV ~g,Qbhu*8g/'0]xEyj#҄f_4=Mw 5_v>g_CMt\Ayk P*#}W04B.hg.A>=LH̕b ~N\%7.vnJ .Z~.ȇ~xHlg>#~/]FB8H/2:N%&9fmSd6F3Fjx׾zo$mv<s0̿ Oj5 ߃XXXo~ cpd% r$vܝ.7'?^Oah`iꄑî宝H~ZQRS\o_< GEO<oƚ)TP# ɸ @;+4A7/A +M4l;롴K?(q^"$㨐xmB\ޠqUS8)X~=Nqjɏr2xDCSc)Aoy| -us;*;A΃Ц}}Ts.e6tNLl*7l߻;!4PM wo?G@F[@]*Z,,,,u(cy:wlt6bmzվ^K}on0'yb2 ;:K[^ t/C[78 s>x:nl(tC/gGfcfJ;iO3;B*LJeP SwBB?T \gQ~2CAbݎCcG,Njh1beݿ s<u.51NOd$ɯ󂃡S+VMp.W ƶqo1N&BG|MMI xtv+U[H#5 ;t'D9!ZMt3xAx}QpM_lwH "Z,,,,u-TZ;lb`lk?߶o( ,R4H`smjď~)io 7PC @T'2TiꢌѕCO^<灩 jJ'N/iwWauy^G\&9Ax>E!I&gc]Jv G aIOⳕ 7`iWE?IxZǩcדEJ2xWgpAoSK8n/F%aZq@nZʴl<ܙLpsg@@:1D }d)ysomkidlci~D#X-V`aaWjB+bzgLwn{?cEM(rM OE0OV GLzW>kǽX1ejrp woVſQ_[Ie:yIsbv==#n=%4|JX0@;sm{׿:IFg['Cݛk3*ڙP  umqD̕=N4̿ÝziFt5| 23 %ٚ&paaaa%|oe~(9|L,ϳ‰W4XsRQV^^F2_q4e1R:.)U<;ll]k|˪γ֛#յwM#L/PٟL%.wn*(".S[B"eUX=K-ZfO?gk0"Ȁ)bh|e9~}>zqwI* xg˗_|R:3_5jTNR6F$#hv er@=w!dۗ%)(r@tʜ8f*qc ?aٕ "4ءCe 2 +u1Nbd⩼T<~qlPhbVu"hr0PK'/SI$Bu5{V "PP10fú47Aa~Y-)}7}vǔ {u[.=V&K{~& r⴮pZ쒼ujQQ~OpN_[0XoS >/gr>9[Bo@t<4;qR9)qq'>\yzN> ߉iH?ȭ릸:uՑ˝23& R:j#@wt~J㨗˻a`  ? A@#tLZ'v2Pzo7䤦t>8EB#6u؀՚Vf)J w`ָ {l\.5DPQڬ:E.JWQLRKڪLvE:fz'5sz_XJeDzQ]<b!pzg!qa>NcPEE%z nT'xGΆ5Gw|JHc̥Jdn] uLC.])d ܹQn(5AhiAI$](Z `aaaa%~|j-_w볓r &ȕo&^}MSFB٧XK2uP &<t3BًU&aM#Bs@AQ|A4?aڶ̳ŁUG jנ +wCԸyb8YѦa;  <}{BivReqҔ`v͠׮CY9Пjs@G<"$ep?&8 ;rOEF#]~N%iOP?DT]OPuƵ~R'SWO-S4 0Iɹ~oU 3&.(2TۯvK2eTP?}R?ja'aD)683D!S__Xm{?,=#+?A-ޑƒI_ok;y*(=ω(CAGo|sg3 , J~g#E0Oƶ!Ù 5) %7OLƲvAOE+_߶_M.3UJ +9+~":Z$A^$IWiʈ7^^_`ne/#?7 h%$G@p3cXXXXX $lj41JњN(99}@<y,sˮ;&>@rn^F.3הa֬%:Y9I j K7Vz@Iy%<9/~;bo`I+K㪤R,w;ʇWxp:CǁNɄ::I5>/yvz&M^_M5I.Y'"?%l4ZO TdEWQFR@pp>MK8v @  ]il\59SWA2 ER߱Z,,,, ~-֥u5c("؛S&8ww; F2k*n$˓.Ck?f<4.Sis jZr[@KRu d(t`ฦPNZWBOٛ [swa h8X]V`aaoq$p_hƝa;Qn49 e:W%A:L?A;-Lw2!b&*}Zyُkj} V5]IB,*~`n^?X g$XZK0C]}'^*s=i5JΟ&TiI>}ĜF4F95Xf (G~Bϖ'NxA>ZoֿZOC 5? c~1ְ ]qWK žqn9A$ 9SjemQPIAǨh@?|nRC3Y tռg7#%W2CYk\]V`aa~6Fl3䰫" @)i'+wJszHTl2!m`-j2u4s  ̓>Cq$rh.s zt@8S-,zSހ$U=2ySPf}68T螫eӉjy64o nل%A?8uzS[I$ :(`BvW(^s l _H|'f'&~ffh$:,ÿ g!2d+lט[x=q3`MA'Gf<C)2#ޕNKNwO&y*vMF|b9MnK/ JE D>IuwvXrG!CXnP2SPq{l9S#S~6s: #$@UdR4Mi*NN;넀NJ9=5uNN:5OFky䐢j\i1'm~4<  RGy PFMO3i# 8bm\q[.u 0G{#H_E[nTM?vcbe6eU%Wl \.s@IfI;ہ|AªQ^ uŸ_O"yiF Mξ<dzҷ>i|xj:eN:쓗ke5=8SכOB+ jY93/:nS/>!ػx{sATvwʈ.4 &A4SCyυ32}Re$ #5Ӯ#pTv92ԗG?^TWӳI'*XZ_6 !Fdh5*3 0707?/_c5ϯX?c@S$#dpzTBe wIXR׎tQ[oPs:  ~kpl L)S;):h ?HA=1S_iڒUpaaaa%;.HvYc7x|Gk{ZZWd5y>5a0ZЃ2:53S aIɸh^|hzLӈ{/+I 0;σNSFHBwT=*N1K>Ke_ '@aN07ڙ1ݗ"22nj8\;g!Dx=Z~PΆ9$d<7z lCDbxBO8$)(UIbԕ[Ɲ%ޙޏyv :D\Ze|1$ǾöS^!ކj`^G$J Rh8nʡu %rtV4PG=OkÉ44o8a̿HOq\XXXX <8E(Y͢h}ӗQN?# c[53Ԯ6jʥux(OsY(cPԆ#&Uku l4׸(FJ7]pu=Ck|#ZS|PFTDN=r#1$=2*Π47/V'+W4YJӏǮg*h4v/Z39+L cz6C%m+.5 [ lPܿJ>gi]iShޛe*0z#>NfL}_tPdٔPMNOfP,d#.\U@2.(SAg/ڹ_ I`N6> SbX=V`aa?4& FHQFkfaxvxO c׺(f,uծCX7 %Zi*9v-~:[i!'u=ĎQPMCE̦e:4vP슃 흢PFyhWz 5 )4\9?sܴ4Ä+χD`bj:+};C$kw8etr$*Aaɚw6W?pPIMl7NmC>>Il3ͦ@zI84KFԔ,~4 X/SZޠ::vP!Xu !k?% P Ao7W?n_5V`aa?ߒ`h /En__h_[ff'N)LK< 2Xq;ۥ>_m*2 f)PN ) k='FEȯ2>W >@~ԯ:/:<B>>o =N30[7?4 މ g=֮C4B?v蜠rLI+]Zqk C-l7zsPpLYꯁ%bihp4N» -> YD|.Fzu*Ĥl>k"V`aaw @,%x_g7['>6=A^;sL˺vCT'oGL `viLe> y6T1>,C4Jj짪iPyB<&2N*k'2As*k.Qq]9065st9 @K%ձ(O&\ v.V|50iv+Nug 8>H3`{ XK% zQ,*^}8e=zs0T^ELWwq uP$0 ~, Ȥ;B%:?߿k `aaaa%~/? D\maj@}O'__p" I4 bD*2$ai-Mlޘ|Re|cp6:Z ݗ"eT^04߫z2NJG}"dFJ/*7y%mduz:iN;m(t(0Ϊ @ϙ7NfnK;C)@> 6O(Ca=!|IޕOP{n@)jLL|g,ER-I+6tOr}N14/ZY* @~Fת1O=yK T` d_Z9ن<ܩ ]|3:aP/kh b8#=3e >$ ܱJCjvN: s#r]Y-`-f- 4*u0 P=oޙfWi {'P =4~9Gd[*+ v=?c]۸mگNuߘ;LOȃͿ{3>LMpe x)&T/MxI\ u CF z@4!?6H1b\%߼s<̿$t& +𧂘O v6n[on['uaA|J8IxdA@ Zi #.13[o7N5w~@ Wo'4<*byu\Pe}m0ǂvrb~2Ar"ԕcxiܦb`QiWόd`x8A^iT?z]XSm+q.hjyJAzmbTaNuPۨoRv2x3ʓC O1?S $Uמ ckA0ucQ 7w| d ~vͪ? +Aa2M%NrGZofRD@^^T?zG$AITU> Jh F. ڜnBMnsͽSځ"cWaaa/XXX]5k0kc7Ң=;[f`⛠OŸEo9NuîѱE$zyC'Ce0Ziq2ΫU"OcA͙%Ui,K% - ;y)˄pol;a=̿ ɛ)aEY0;q85ruE#埕d0fY=SIRrzu]*`'%ͿlND#_Փ" Ȧh r]%xr>h3SӀx2Eܗj(C`\x[(+_ISjK/V}I D }빫G-sDm-u$ :tӶ#>y2ovv宀td ߧ5̆,R}~+I) *c4-G?O_$}vܴEgנSe;MmWӿ,Ԯؼ?] $ip(C|$PUݾf9FUOyn':0 -`>Y[b4ؽ[}UjO˹ס'k Ef槂 i2+dk(E,PU5CAp{,vVB#yc' W^C N?louc'$C2/ʻ '֫f2NO@)L4 =2-}ʯ3 6i-v[ .:MWc (s~3#LJ,,,~p^/uӺF#Akֺ̖}_F4Ob<*SzBx@j]MK(Kjh&(ҪRHcNؽ9ث̘2OĪQu(INPƟ3%0!F>C &*L?S=/Q׉b7fjxMA$ӡ]MP\sqg?f Pn{yH;T*Y GJ"MDGyN86IgWs7s5n<ݐ7F/I;a: Su$=cccc'666U4ҰNoVZcdzlYSN)<xRz8(àmNy7%̏hc$,Mh!Qn$jn4 mk#Vy~-"4Ty*?By]4ߕgU*c& 4ie3U>0jp-/Is oqL$1 24rY]U:wU (vKD7/OjO;42EcVGTɛ:9XOL=1X1abjXWOCװBD5i9=%p4Mae:8 tͫNlll|=' S5F<߳7=?Gt?? ?H= <(ANv%$HaO=p.C^j wF 1B%.54,2q݀Qy2).6ӸW4RQ-itcoμņ5u-Ԫ)B!/)}-?n=CP9 {}3-*rXs} s[Z^w}ƼأaH>ϥ:cFd|QddiCr7e24Of>V?g|$^bUGTgUd>mw?-̆K50p Vz#wRPē T{Sd7 @Nlll?'b@ۣg~iw<%D Zՙ7A''?СI:}/$C+!WJX!S=`Ҽ6QxMerP@yZP+\N/El=F|\wpK b0ح nD=={kI) )oSA M ==$F%Љҡ?SR ZR&N)O_Z qS=ߛ+3^wT2P~B,SdJA%͙kB "q2.o /wH4]xaÞij0͎;L̿e_@%ZalTx,S {*4 .-7HZmZ=9Lh8 2>42߮8%WQ:yE+kq%=~knВQHDA?c `[4 ɃS\25ױ(菍pBc25rv>pq0f8!zz>f̩,0srB̰<ܧw@h0d*K"Yh!*TcWĹ;|MaC4x<{?`%$XZ|NϞ?z[%X$;^% T^LruU΋JvH4p>2tψQMR=wW/+ϊEZ&X'24~56y\5=ڐcL&E\K?X,ѓ$wFu3Q6|꿱U .df#j2{J 9p{ګci ׻GU}ˌ S =^k0f,*ߤ _;Tg"E46@;5 p.?;U]3NJKy8.oAk$$8 }@_&2qM(/bbytv㿡ҨV/1P`Mw{%j~=zoKXv/N_L_OVeMM'ϐqVKnuBHhxᷩj%AaE RcwfvX@15vYK%rosN;xK"Q=&A$0HéyO CId"u\_F=^~\jz'GX70ֳjAK>WXgj2q*K ޸B!kK]Z4e B V0ĥP NJk&TFtiMǬʋOd@渚)B/b U.<С4碅sw$xh7^Y: `ZxMTTru۫YMHw1P}R(h55-n~k:ktc?$;4eS6Z0E`=v`ccw_NI<56*XaC胙x0\4:؍(3 Sv2z(^N^cRƅkdVi^$k8,*)<%3eNc"%\ WZMOj?OeBqBѤPi a5aGgBQ=5O-kרzHES^6-9#`<_ru~ŒȀ ^g#*˚JM4@xRV?|4Y*2E&*dI +oGh!j %C PPsԡN )8E#KET}dƿ;,'n4dq0:0dPw56RMx4VO02i`EI  0o0XhX{=r P+S1 ҥ\zZ?MwyTEֵר* eu*BNF6wե*ՓJe(3&Ayyn3*-pl0cݧ#q/obW7k-T/$ne^Tpe$Q.c턆2_\A.d9- GLqr0{eԔs\|/b`eQ̰Z4 ̇|QPg+ `,~31 5AU??cw 7?к=NF AW4|vѴϓF1!ihq' ws*S|(YnﭽnZ(((pm: Jaܵ97&Pє(+$85!'tZ˨V:4KqKյ=+AL# B5Wj>+2J `%N H批lCٲL,P􏦖/>$kPiĻPXk |zﰞe, m`hASpU1Xc8+_8Tu4R+)z% RP2L~&@l{e%X7 oNlll?OWE$oh G.>muyxOO'M0z/Pq>6vE2mX3,"Y+Q'veI$Ч&ك[c-󀋈A'w9W 2МE>v;)IﴁtIM`0@y4IM(jq8*W_'՛-h5Dg&Fx+gO ale4If>54aےėvP (֧Θ2w7-X l~ZC:sKf#@My0 % @y 8o8\c :i{[S/Xo+(']I ZrYA_֝v`cc㟘,p䉌 j{% .QbcO!tΦ0q Y]ZI٥BI;}>YcJ~BF.]p:)_'EysDPHGmtߍ1L2[TlL_1o*2AaAƸR: ܘ{Gܻ|LjT~Rz \w[r6 g&}m5QLI;&mOp=޹{.(3Rcmwk7n 7n(HdNRO.Tqihz OFAXS30jVF|*ERiQXޓT҄C+ -j@SSF,c $bS8Wƫf\qh&))sAR$4j1 ®se!n'6feҍɯob(n*t~[,;ԓ"0Jn_t4PB1I5cz24ɝi>L;$2${pLNST1gKN8#9:u[s@3:X<"Ft@ԋ HȂRdK *wHrq7VС:u }~߸qƝqƯ?8;ͳ]Ų꿑F4WBSBz柝|ԡB¿oJ0?=}n`l[WIr tp\Cuꮈb^rcL=@a\Yq-`"3:Gֵ] tr6YuEX29cc,Zn9 B^ t5).X㕋0NU2xKn툫?P0zk FK[Mi **UVuckGnl5v-ՏM-r ϘFȖL5Pi&9UѷF3ޯXީ.К4+IͿ\hz8˘{c :Cy7؈AxlOHjtEa/fR-zmoܸqNܸq/ZK+&aMe`m|ZlÃqRS=P4I_$_ Zc&xlKCOl _ED2 ~jTB ;(G/cJs-B}16^kB^:TD[܇ x 3Į&6dm_}g@S#q{ bcΥ\wJ>WHOw.jzccϖsn 5.&ΛuiOTef㍖,Rg|0 iLL$]Cԧ9N$~ɔշ1gdC.EZcOtp?}oTYٸġs£;?U;Il\pRuX}[, P|Pax8c~qw5Kknqƍ;pƍ_1暁,Ol:0|<ٓS8'U?/jy#yX-4  hY)!̭P056I`~n$j^I;t4 8fI!֚NC{4A5RF@Y. 2Mr]'Ͽy+AS4öa;K`S c൘uq(̿:.~Vqh(l5{ LR~.Cb>!*x0R5ū/3[Yz,IV#UmpWN( =:>%ZpQe1Qby:i0JR( X*Vc86{y;mj<d)ꑚXT7mI, `pubrB脚f}U2tb9D:{tzWڦj3Sj*o#PXPU>͓lP^(b4ZTlUB&k _pVis+)+:>\Ӌbه9'"At cqz7 G;x Y 6Bh5޾k3i댯\'dax%dycd\Al!L*&/&,JD1`W)}:o!_/U{̥/D&?z6iE$J\OpiJ*^bؕIo4 2*F,C49Srt4.?#~xS6w#t%_ƍ77k]/Zjy%o䶝z|LpG N<K/r$m" K l}?7<;[Ms;U,2D3!s㡽Veꪒ/zuM?zH٢_-6vMr>.4jzԹ5CN8yV à-Q 񏄃׻ >*(mƵ9X!Iwװ>h Lt:qTLL4j~ bp5{z5~ T2VkyPP̿r=k=xjI .ƿ1JDDo-MB2!'P6Eoo#NG .w9~x#Gл.1? w7nܸ7nm/b f{6=G[fH$ͤ׮}vA·W.pñ TFkh@ydդ E(;4@p"rAuyGo/u2\ύMC@]EroWF&ŵ0L'1AVZ aq zk8\ߕ2"uce@WjL症'Pr=GV'|l~?|yq|ao4vTa4Uks>@n k07_\P3&G KdſzS ]$ǦbQC1NA󃲳 M{w__7nŝq/u?HƿIgx#oNy#_!S? oB 8 EnL - dCMkġ՝YҕbմrtŎTv6Ca&j$15p СPphBb7&5cB>n&';e翢\ZW [s{&,#!'M=٨FHQg brd6TV&y{̈́lTeeoJ d 򬾍jpO•q0#b(ZɨPC-X=HE9,30`TS8,,_ck./&C~+JfVZgbݘP[obguO}Q7Qo%HKaJrQw`JL3߹φXS6!}`ipߧʋ6]$?Dƿ:Ч}1~׹j_pUss= 43"Wvg%LdtvqQHa#0B}t߸qƝq߁J~Y@9鮃ݹ{F3MF=Rc#gڲſ:[!n,R`~̱BgO|jvjhJIeCX:ީ}ҏSdASGq,;,;YNkK6E&D4.i7ZNȧ7C:A@Ba:B|]?c?0TW<GWX4_<\Q' ]\ikYߙP_K`~h_,6t^֩i;gqImXW2% WS(PR`W#2WB2[Z*JU؃8HGg#ԡ{_OcN/u"5wBƍ77H b?}Ѹ}h4l^ߤ\OP=F!sフFRk54Ub=Kŗ kwFՐ]7Oc?bgoHi`A:h˟kt"$PeݜtfT4@F#^cIx__rcuyjݸ0hȘ+S 7ި2Z.hz^cQz;fgWzܽPK.PCӣ-{iPe L+I/Vp7|nĔlp$d(ǾI_kHRZ_;.]' _ "rg&D K- yd~ EWa/ukn[tywqd2%kҜ}Bo :Ou%߳_V^#;v;+bFh]l84 hKL z o:$]u\/@7?y!ܛ:^#q0'( Q .XRWϙ'lck4Ƴs"((X"D0uV]ځFJDž/irF1 ZbΒ&O ˠKZ/-C!xU32y-_:E2:d=\2#<"׫* Lc,^naO @L*AMĽ{rHt`i8J+zq!p6J~11[F5Hw_ 7 @+|Tx?|H@@C#qs ?u?qYv(4"Y-]4&3i鄉#G,$H:>&~=u` g0!`uF[Kxn G7ǯG4;vر8vcH/5{# QY7 :XqB7%p干\).DO3?Y,/ q io0OwcXFAP}ղ`M R):~!q|v㡤0 禀o7F@CR59Y͒ TTJmPv1 %2R>P@GNM%R|ԙ21P}|3{SOr%wgǎ;vǎQ;h+H4RB= J9MswlQ"p=M"7AR_ ͪ1v+MEp&)-x|>FJBMzk%1kCzgzh: gw,b0*E wU%חl7E<wĿPGRu8I'бGB,hcf*v)(VSe !^cC7K;kN%И{@W%0'RX`JmD8BP2ͽ5 .nHSUNXֵ,DqH\C%ʗq^)6TXU9)XY^7`tH k_YhǏBh\2 CqB~6#k,m ;Y >2TF`OiWG32@ax|?,Gљ`JoC7w9&i E6<;v ;vl}7^^%<)v8Zƛ1J$P1Ƈ4)YQ xw3 FAq>Rᄂj>cШsp~-L1a@#E@z Ji250oF8$[ɟ2Vf6D,c!c#qäƕk*1.‰ną8(\W@,\n~RWo2%鳢5=rLde/ {:@!2'2R5FW(+;* \[1ilHU=MI8&;HJ`ٍ7Gr&Ѝl;n|y}\ $4~: ` Hs_m,#'C҇::].jJTa;vcǾ2vy5Cf]0#@+Y/ c|+91T0Swq.40PV.5EGZrm6Nɭ.`C.VǃfKM! YKIg:e@^z9;ýܐ)b JTnb6bY!#KbLU[ *wO_Jm;sg~|:Wt\5ܸjA&A0C nC811SM_( 陝;SǬC205'FDj@ UJ5y!!HJoџ_rs*9r^kMtFy4!*QOԐwzH1?7?rRp %VәرcNرc_L Xf|̬"/BTNeOK:*iNJi FP0Q6ģ7;NKmx7Jd5t&5ۿ'6jsA.S; vcg=<4!i'S5 z`0AUy!%T-,".@`L@70Q=AA5g0Ҙi ~_Fq.() AkhM0UY:SxD9PݬbG hobl8Yn\xCQ?he.´X\PLKY@ӫWU^F~%O)P%V". c*ſ|.:_s/Sz@g|';Cu_AןG@Hj yرcNرcT!y# fIouWNn3 &v^ 4X71+/No|gdU NKK<+Gn i4 mw/o98)`䜰ՑRK˼4'0: vFKQ0"@'ys7r !lPgdž6TaPV)9cɟ'*?#\8UBsj_7Xa|cS0IJi9nD|z4w(0`a)q€ mDـ`!%d'ީ wm~ݦ]9hm?Gv3?"Ps. : M3cǎc9v~3uxY [ e:i"5=Pu {@ rl&E!xMg /k \93o?9{FY9W͹*VBt^%M:_JqI0/BFR&f[v;WQi~@*Hq.'U;(1U/kEB"IU)$v@?VL;Nj/K萪`ic^@1sCU`mbULGBU; V;Ar^rX ‣&7vsHm跾~e;n)o꼧ߢ=|&@0W6H(~ PjKOcǎ;cǎ _wg(>v)]a0c1j+/7u'Ecr.+?>\RG[AvTw>eC?FWgִ!:;4Vt~sf @@Zk/S DF 4æ|.!R邈w̛BXf8WW.0\t, (ekk}̇Mlr~\V"ex_f*:ґ+xtL$\NM+G6⛞9r/V&Uo2/UAXf6NyƒU@ִ|R )ș2H69 @=/M R'ء/?c4_93|97ݗpB  =scHKQZRرc'pرXμDʓge GtS/ N1w$SjvYqUP&*@c9@ASF.sqj|9;{[O*p3ܾ9Ap 78F@CV@~C9-7 ֙ 0azS;x85['KXnz Y:@9D׎%VU.gh Զd1x:?ˉރJT''Wrm5jհs臄,v|1 5jcf,u.iV G7Wx循 IJל"2zHE I7cЬRi[cE4$GsNd7E>EkeW{zd2r[4u<y A2j$2A3cǎcix.Abʍ$0E77_4wL!G^bQJ{jKVuHCnpWlΏ8#t </7ି%>3Pu畭lAx' qeC8.#4d!k`0Mr,oApiFAumSnS0`n.>HrvL1dS7+1c2JL-<+@yyZOiidVt)*OTSF=k75{?LGsb9 &8B6u$ eQG7rLGp8fL,^_&=GQ|/%C߽$Κ?>/> ~:HgNp +;؄GPR >؟J,cǎ;cǎۦ*!c`5պٽCtل hFs3.P70xUvSy_r7LP\$ DцD署Usf,1ցuvGcWޭBㆪo7,-EgB ِ 7nIZ$a3ON 9VOK~.etܴq]!?>l0^fWb=x+KՈ܄%!K3N@ 7%YWexn\T|A`T]E}aJRK+{Gk 6-\&oIouF@@G9TyQ,/R*Va @hWW^_'GJAJ$W6hR*s\: l9y4;vر8v?gUvI_Lb怓jtCf :B0};T<45PTtjaC.Q%=jw<];{gyMlܦ位\0黈Ց;АY" `.0܁)73OI`-R* %ݾ@U4Fgnb^ ,7(716LJ()*H v8b}t/]ru#z N`P}zLS1_>O XYc%^σbJ7.8M!%ljI&! Q2_hqQ:/VH^)u\P%Brܟw"\ʠ0:EB]bjBuB JWwr8 XL{4鯊xim۶m׶`۶m`zFrw\n<`;9p d?0ob 7C(5tBC~M.)o*Zס{I^GJ5w Sjgp9ߵv2NŖphˆN4 ut'eV:.:miH?H6X:&q^ya,yMPb7lQ7yt\,R$<`ATQc+q7iUϭ Rn_PwXx^c/~>/ /T=RzpG:|wJīęx&Jz޵#!I"T0FD{10=84[Sx/O/ZK,Q x6Try&O_O{Oc8Qiȋ=8;At't dC@ބ8)X BK~C+~ {;`۶m۶`۶m5P7P<:duqthvK+jHDs ~tS?.zƌI$XJF).O ?̈? [YL9{BA$QA-PNenGh8\FDXs˜ qy7;L,cC c :T?R_zdG8ܛC/ 0!*5^w91Ճ_5 {j%[.;we|q c*>D&BC&uor})nt(.,i8SJ~VCDzt|=K#%WZ! .Yw⒝llY*6qA?XۿR{n$pKIS)c# ]LR&l: ~ց>)Y<|a/e<߶m۶gmۿnSƟm*SjNnBkGH]yAbvFa8Q¿u-žeW6P ŷGЁ);c]`c.;`DW&udt"5?O yO }:cW|)z')Мќ^'iiI0u+NDbid!?^UY+C*h_) S <3c5͜1bkgb-<ˀP~9>9*Us,Hy<֎<}cN˷ܭr&d>cZ‘(OanGkf90*II70?(şcs]gtscNo5nm۶mm۶+V j'5Ŀ vRg!4\-#؝:SL%׹ݼֹ:H- "D!aaynr##mh:b]Dp&\NQ#^y+`ΎOWN10xgPt@t|ef!VL0*rM jK@ZJVx;^pɘ-E{g7{6=Ti%~J\81;k`6. tD%򊨯98hEUc|`c.1wj~$ePX' 5Mc'9@V:OsuC3ӋQc7MO$(LJZz e~?;۶mm۶-ywgv6|x;v5dƺ_/iNjpyT͡N=4Ya89sbg!+9=%tPyY?:Qs/ׂg"فfT3 'SH'+3|d(LNEx/8tPwZ\,ϻ 1Nc8--4X|r<>ә&d%qE$@QR/τCSjߍ sP ԭx``SPv;tI6-ɤ1=`uPMC]G:CZf`|D)9@RK} Q0Ror<%d~ufY_a}jk+ sP?^ T̿د" I1HNg;rDg? }R7  )\%N_F߶m۶ضm3u1.~oS,_ܽ[s?/ylȑ>p75cFngrW @Ss )=D)*87]>FSM T ggco$ `'GݭҼY#0^[ S9;̪gBgw.C};, b"L$@~rRL5[՝`pCL =eV/x/YO\>ſDpsk_Y|F%2UL_;"RU+QxciXV/y|:.>;CP 8ۂVٕӀ=6![Yw@ALK̳XLXTnyH{ jQp ſC7oޡo{t|-F _e) =߶m۶s۶sL|."rr/4D&q "tܝ[hurWc s1knj HHgbGeHw*9k6y3y%؁J۬i0APp#Q܇8P_Rh|.^֪zZ$c)7 3tFLX-+MHi7EDii~9 NJRApUwπk9V9:Iɉ?hon9Y쀁T~wdD>.M`~ "p-'O 9Jku{DC%$AxINߞ}dr^Ι\OP+  zp>z xp?'O8;)ș+p^Puṿſyu־w|K7?uJ M~swT۶ml;mwF}u J5yhhDG>&$5oݩ ?lA`)`Vc@)sj2d~eڿ; #6?w4iJTR): 6p1A?c8,:C 2&ycEi's*Q -NbJHAd~3+۔]18CZ1uU+ *-KW$_Y-S3:^jA 6|(2wG^Υ.!<︔GDXt6cر H5f5)3 /{fhzN'qMz7(.f9)%xE?> f^0N_CN[?;y|k?Zf\/uvm۶^m۫71޼E@#aI-Bppr:#3 4 3?f ~ ZK\A|4 wJ#n2߉:Ch7F0`0Cfli؏ O*1 8"[:${5؜QԔPS(u Rh$@<:JK xf)9& ;|B_Ў:G8 =??Xa 6)URgXRf #g ؕ.lf- 2Ǭn*c - ƻhj1jw g\HTN,]yD{9)9]ˍ-6jlљhj6;MM]G俣 Gd-K4:@mBzcw߶m۶km۶[4q{f43:&槓j/r΋J5\v `'qpD9Nň9(cId/c93TcێS32猺Dygd;f0~4f. f3ksfp|PKv=I՞Ы 6)gEvu1R}WFl\}ܤ,A1Rl_ 5[L"P۷kYu)~\x^}:粒˪oәW(I%^S/mZk,`tʵ %".d1<Qmԅ0>3iambTEH`u;7 Rr$JQImN@ʋ/u@=j[s8^hfgGءGQBolN!k |.߽菚!}v>t?^߂۶m9m] ?OкNxC$W[y2}:y0uytKX"5'ƬxmOXr53_K9gnjJΨ:38 ˤ#79p̻-/h@VI~aZƲ+O 3H(-^& b$Aq쯯ORs!`b)\.. Sh[(G%tEf~"LZSȕ/S__fԲ/eo'Co?USz@z6!x8\Kn%\N/bti+`t 1ĭL EWHL3O:2-b G|RP܀JwYnD)1rg:\<^F4:Vff 6_:m?IFQtO8o8ϊ7_Ma/i۶m9m}dw8!w)wLF`2}Ɍ4^&l>ftL<=Ģ2rw2|z D}SHɈͥ䑥w'`<p >Fy!h*y%_f )x.lyLwe!32nv3Ƴ#w!)ήo[D;Z8/Oq`:(p m۶ퟵضmۯ9 ʔ`B 0DCdE4gk?sNgSYGhPp?.fuURxwߦGx'Se`ZOqR lN1!FBu&+@}6uLG|FC fFs4'4é៺X>ƷE'W@ZnIRJ.TD$a|L@{02Vȩ@˭;|J't]%G ek+(:N%|#BZ-毠5\wCPQ/=< > U'p|j*=HV'oBuLAWFշ& !DtP ,7K@|N$z_=.Rpߩ s;t-8:8y@1&ቆL!Cpojvq#(: fu5z2IΏe)0MIg77[x=c=5wD߱YݘrCUG[Fz-0?R `0qv<(Xԛ̠J) `|:'B2lA"qdNA`>e}Jz5kR-[7YE?B` llDŽ=$pAHͱd)VqvCay V!Xୢ%4xY/#oni_W}prmXg^Dbk7@CE3堣(|jRt@Tfwf<:T)>`j:ƉU!jU:f leKj٣BH5Dg:fNyΕ~01o⟵MLfm %I ףPN͒gx\0x-'"QB (  A-7 c[eyr* gN´^$ Y%QdG=W?B_PzD#kDu@ӣ_ /,0Cyp+W9iqb,y.LaFrhfH.'Mp6Wyo^%&A >xN ^kjWDaXcմV-yXfyESтNp3 ơ jUwX( Vtޤq_~ʿ]/3Q DiUA~YvQ|=XiOiƂ:φ!WUVH\HR#|6"%*}}{co_p*KJVhC#ޑ?:XCB'ng7VOƫ kdi Lb(q8ΠCow[pN%'9oJb>0L+ cW}%70( rg::[2^e72*C3Ѫ@iyV0w|x"WlM2ĿQoƝYq/\ſ ߂AzorjT%o76 7p2WG;2AҌ@0ᭌ>n\*upW3_q:>=8/J^+KON U Qzp >?fp'hb&^dg.m oO{쟵c(TxXz  *Aϊ²S|4j&8Ex6Y>DsV`d##T0:δ:#o?$|઻K{{O}[)eg%MKlf`q&G l }4FtpBL~,EDGnՍ0J#Cir W1Fy0 HF*O .;6yS߮(1EW}K_Ǟ2N ty^$//'>I' |)M+Aft:?ypkDcnaN$X|͍<K?_|r 8M'M>ɸ8VNCFup}wtmгjW!  Gd>0ſt38SMF;ցJ:t>v~@j9eO{c=l%}K%b;P3@īc#0*0]2[)8b#0^rz:/'FQK#pH)i@S{ޤeS0xr4Jg]/#؛[Dǖcc֍[\: ,j?i@V|pkISȯ +Ŀܾ ),Oߵ;#ތox7?GLG]hr_;R 3N_-րmxOC/ qSLl 7ٝr| [0Et}uTľrjQ2NPܜ^.Z{?w!`lNnW@>6%WQC'xO4~pªo Co3G?c38{챟NcZ9bXTxq07A 1@Z]h+W@^-һ9׫%&] όሁAuV 6orO ]G;/WS}'ig"tt$)тbctެu07m5`Npr^=d;Z:cXW XF."zoYNF=~Z!ZJOϳ;ŷX5v/T;6W8yS5 h`GǪ? \ꨴ(_2^ZlCTh4hu:70]VG򼶄"9`$ХqEr}(KY[0}p <L7b-/Z;/??Tf#K俜3_"0{_G?c{8{챿:˾.X+io ."K$`|)d2@]I[׹pKHt<#+ s%tK`ur/ޠ׆6DkU/PY$|q 'M#QNn w@f4~G_?]F.N, ?OϬ{c=b8aٹ93dUMqǚS xFF٠[2 4ÉmiߥW^@v^NAŒRkaDA[_NڨV՞p|5;Y 2ɄI3Ҏ޴u(sg3DMr΀+5!zYbr}CWA&$7WwC(bVaMnŰ_TxX?^%)Wo^ U0ǚyL&"Q5rnh3))pvls*` CSl;`O0ն_EG]Ī+sr+U.WGEyuy2zιw pNl&6:8<;0/C!`1:ehokn?{oqlоh!<ϭ՚aQFy 3#c"l z3 XK$})ai<4 \΁5p v @,uź_xsUGOJhlnx5D`݈QrQz Sw Bʹ:>3.lPD"@x}Os]ruC hh㈚<⿃ 〓SU_QpׁW;i2_4?{߷cLg,ykm[h}n0N!!unp`iVE{UJcr}L^2ͩi8d-3aDww}FDhOzvYrK웘gB*E#yɂgĨ8_z!+>j`a# !A l K c֥^$O H^} JڜGG=n|r}K'`b^z+XLŊ$6Fr$oL*@LSD"wڻGO7|jlj@ӁBFMpu8Y#ѩd !s ֢>;;loxNf]/. K'hy@TzwSFKg_oxF=cxǾ8 ?iwπSv(@W@H`Ӎ#vzfUK\,^N g wTݨnE`rGOHjt&ͳV/ J0lj hU}˗*D*f1azh{ugؙ"fS*-+,,b5 ,f @3A 1=jwX9}\Dp@pJD7&|pDvK&i,D`KzjơmKhnfc4Dмq hB`U fu5  ùGYFúA}O#21]&/۞KX>fث@uNā|U̹sr9ČTQpt5Oc=ؿl KoېK{U4T<\-:AA7'c`n;k $4?듯ƀKVT..ov/.eޚ=#;:K߾N7B 03vIItWglX^᭑(2!&HJ'aofqpǬohyP0 iE:Fa:xJʋHؐ/ E;h^_8pUѧ跮5VG2@1A:]bM_xW޷(Ǎ+Kfc ,6C+|<{f#Pr*A6fSe=Q?GPa^cnm۶m.m۶}G} ufD d\3A @GEgr$ⷸk| dee/9?7Pֈ<Ŀ2d(!BhVwh=4\ & j)x m 7MR :#(r25Jx8^|{E/q%:Q8g5/;YaS6lDVC@rw":6ͬ|o礝0mCAv^$@H@ [x݆eǼ_Ř>N8o6gvm۶mvl۶mžk4N 9NT9ѷ 'B!ݒ'ttو8N<5?OVhվ1fSpUkHa;^m8[K+-WWJ]땭\ fI[@P]55 MaQn k|uͦ% F2|r+L{U )@\%[7I`G kڃ]3]&?w ^F'yr_Vb+_ı2s?~1Kuǻ"4껯V%"_Ml+/RBaKv}~Pރy Mbn[}2 ?f 9^u0fQ0YXmy8`UW\C8B;φ3/WʿءFPEte$)~Gyic;D9ZLofK:"E/m۶wl;m'etwN 2qn p:]z;>d M[L i@Mw0iVe3d}<ĿQ6%cB=:ş":R00L. :Dv:H7(::ߣLNpT;)`l)isJ:W~Tr?:r] z][BW~ɾ:\08Ap@r VgdXMZ/?H^C-\3"(/Q [ \0-O*: U RL2`p,.X >-%0+5C kAD <@͈)}ρ#<,7K&1{S$W}@O1K̬;n6_ ױtcMK3 "3CMݐ5A 'Lzo`eV( q#Ol^ 谜r$;Y>PR{CjO/8}+MI ^w<טюrHV\۶m/ضm۷Y\p>K:֒M>Sq'AC™SI5]77:lYDzEo0c&`ozٹ$2jXU>OM[qn DGc:'|2=wDL #f +pR&t)a ^5l~׬_wrɒ"9n^8.jF;uuP<8Jw#8t|@Z]eD ~`k;mu{^?D_'ߘ5ַK}5yމy F2kd'Qy6 ǁ|tq9 T It(ڵ'dĉCd0;a" gQYP0}"Q u>rSҗĿ4U~1,[s11Dlt{8 .5UYLT勑߄?o۶mۿ׶`۶m_l~# @܄'WuJN\O|ζ'nYֹQ\VGaj)8yriV'TNk"+7Rn7?;@*U2HE_f+~H4 C5:'C9HGAYMx.5q7SgW4;捪;M }w^۞%6Wr:=dj1yKP9 2#C= &`d_=^] \; ~ t٧q%q daBY{\N03 YAcE P~/'U)A1xlS㽢?~Oޕk}zQK:5{m۶;m;m}usA5&k0|?;zykV>g`x(D"ʯ`'IVG9?kwEۖ_[ORQ\2:Cߤ(}:쇰A>ЇCaMwx)`P L`Ad+QvȌA<%O}JBv$O+`"1#)UӰ q:Egkj_t5Fdt\]h")::Sqhѹ9a6g ̸UFQ V6+´|bh+ JYn@DGc}-b5]){t0Z _>d%l7uwwmm۶mnm۶'k?_TI4DB#IiH.7Oe@"%rnk??Gqg8SJV)*(EhtMc`*Hā#Q{A W^OKar{зi ~YJ{o xf2Qy9q@qMʢqέ v⿭t@Fϟv1mv *ſțU@Ye3A7/fZJo~2:jD ~6?[ yM_:(3+*iHuh['B1&p=Y0i,Elcn,ƬLQ|ausr=f11o9~m35&h٨?m۶ml;mO:wCy/mn_4 85s9t2ec?? _i\k~oCw`nڷdy}[O}i&! > D8"HGHn`L=$w߷,Kt|ixAv yM uNZ*daas?K9w\R!U YMڀ?/F|\ |JZ F+p5Np,ϲIpS^5+#Zn_ᦊ81_xDE 2:Om۶mmm_8~-J#/޷Yp’C௥OϷ@i# R 7SQYfpWdoG#8ȃLVЯ/?U_>J 2,qgBFgMtwsszp:__, & S [_Q$8 RտP d?;&?'>c\>CAqh}e,;4zi1i34PS=5?֙׿BѭЅ;ALkBuvu\Ncgmv6~l;I_LC?koA// i:ԊZP~MAJizn2Z]MN6>'0Drvwa _޷t{;@m{;x!_ N^i*H El&}^$0Fԃ Ϡ=Cgd|RwFU3W~a_ ŸXXXx~&xG}IY'>$9F"1;G0+?Vq.Cw =2\ q7f{sļ b VQ_וTWxy_Δ{^ۯ>-X~%6 PN0h{dBTI9sԇ͍ܶ_@ ٴ4}H͇wMpgSOIԌP`j,,,,,G eMt>7tLT F,Sm=I `*tj(x B'6KUiN3q3a3>HSFS0O5ԙMpx"3̿gO ?;z$#rv'` =nR}Awwi{ 5aou_gZ ]nmX mg! 'ppEAbsdۇ v A2#y1؊ U]N]V8<?@[} &(vd\V!㧠7-*gcލo>MgG7lWz8sZOs9NEh  (OxnuG)nEPl:̒'tٟko|waK'gC_?ߥo 9IJ~] h@5rVF{#1LX;`NyHמFFx1VUÿe:ZGah"vJq׹ oB?U( &Ty] uuy"2 >V`aa'B^M5?G c5tu xO*є"UEQ{rx&J{<蠂(c.㻟>b! 5Yԣ؎ ;q=:^aC%^COsҡRT we6"\mVFж#L& \ȃƢ">lP!  '9]T}-`g]:xr,Rd P?kx3zWO;/`~3q)i?o׳~6i_fXTc*k[msby|K`%_nZqoړ[$*^ݥth{ۅSm)+?0p.m% UTdKHxoP(dbQ Pd 2686>`TF,іg7@j Mp }!,m>\{CVoKK& 6V`aaw`6?Vo˫^XүY>Л\x ԰: M c`cچGi\f WLݿy.Jj!nj7o H TWv{M?k](r]9/RIp= Rm-EPU N#Ew;%d8TjA-JCFŒ`H!ќ #-!WwՙO5rW TЁ(s_?jaaaa7b~vE b/ٳY(i tQ` `8@5I:l'*5%d=-.BѤ QʡyꋘkR;X^PnyR@P:yi"t8a~N{b`]yIZmyE#i h^qojS vWKٮ]P/31bn лӛ .,,,,X 4T> ^_5Bo 0h1| Y쇦wt84X#WwW\hCyzHdOR.!҃د"*svF<، SU}Y(H&EWOTwLz95;\d(QLcjj7 M@Oږǡ`E4.rihep?|GߏQSU8t-ēY}gm\__LAu:fÏ:7y/$LiCZ0`d44ձ;A"뻛_9lgGmPϫ] ;qR.5vw)5$@75Iǽ ABt F 猾MIR2604?{ע8$?E"/w{fK^Y<'d (>G,@FCVC)h(KW*O$DĿkߎoH'W~szސ$-0 z6Oi܍z5 n)^䋼EstcNhCx;S".G^)@S}W7R#L2Hy򿟚DB"}; 'ƪNh4RE+D~SI؀jQ88E&|yM9.VÊs\50xb 21vwj+]y# 0MFu"һms}koDEkHT4̿`LѯFjr"H$2H$Hl7W5AANĺQlse_*KddEJ #0"<8#24^/O41 ~ t֏!i I*)J^HP0DhPQVGrd/+)ԴA4Z\."~xQ"H$~ 2H$~^sg~ 뾞tR`n}'3+;\^!L!W`5StAv%)ÛV {kjs/Ŵ(0)sC=wc엶; aGcpwASSg(싷E?@~}ƕbwn ( TkOAzz-?oV "uʿ+M qW,I"i rf`Sumˀ@"H@"Н|5Xm~+Bon9^0h ~~]YD%<Ӕ<% jo~9?8(TVx @Q? 8 c>p3 Z;jO~G0>  '??|7M񁯭ocg$D"@"H<@Z9@ 96d12H"H4h)P">4Q &OR1/p:(L@F`NAA%¦߹8[2Tdo=A4ߘε>QFP-7%{IkHfL+U:nJD"x2H$A OOI' Xa7A4`2 E Is}χ%<I W'@ # Bwe¹fo3nյ4UEҨNH`<-'*Ѣtݭnčk['D"D"1  k䃃*l8ҴAv? K 繫Z( QuM74-|z;a|3ύ["P2Lgկ?g8klg}⹙勳4)څ׮a<iD"Hd H$?b;! w!4 0Ŕ(3fK,0{l Dc5>kTA0N0XM.D|u#ud ]]g1E# Ear[Ǒݐ=yATT>%s.hW!kf䭷i;$Aeϐpwߨ 0eYO  BB,м39yXF@cN5IѦՇGDSM󮠞OL]N[= Axp;.&V D:gDi:hyΤx# 4ooJ//MzP(TP(MP:l<8Oaʣe{i*ݰ9ޯS z œcWι~6~x/Ȃ9F] 0 eo$2/3ss G^z^zdީp=7/ӠW( 7*P( 1]?Aո&IPנ\p j3` kdH܋v}2OR[mfرy^Ξ^T^ah%.<s&a'ݲs\HJ818|#D=C zѿɫ{]͗/ B BVGu_4pA{֚Zo|Hu h+W I\vsx8L.S&N/;|t`?_=)X&N544_'ӹ<`/J>XhQ( @P(J'7/Wx%q4u#a3TOлof$;M¦y|$4ouBt-y)j3,sm| ] OV|"8s(MMdPC=5<Il_zh"+?f\~5_(  BO)>c ;14S흜:z[cl#aAia ·ZST<0E&Ay)濁FsO o`hzPJofD40)4[l*c`Bw]pE= <}0ɿP( !*P( ._zn_&O)\~clFi7H7  [kw7[.m1=nۖVܓ{;B0u1i()8ּz:??8- y|)ߵb6gsTϱC?LGAo2xpG[@!@N~wǶ5泤kҷr[7F_fe71=0R<!q7 7v]㖏yZ/Ճp^ϨH`r:FڂP`JϿy/8m[gTn:_f ߏelv!m|0Ru0*q "ǺG냦:8BǿP( D B_lP ?:oޟ%c kVYb37ÿ J2<]?9c@/ n=h6|PwPmOմ^g2\AV|с'A"A&_lS?QD)7M_9:rY|[6;J8SP1253tcGaē@pi&   U~]WqI8+{kkkki'9WL8JQJ}0 %Ȁ߇0B5Ys?-!\8`v7HT$U8@?%;EVI9/1Df[yfWPrNW8ռEaS)@NH$# >~ ]%@ 'oL4$U,(d%4ȡL>m|w2_i0<(\I>mUE e *rE܌,ۻyjYswgTgVfR ɩY0@@Ian1 &@Wr_[x߂Wѿ.?-~,l^Z+0w$BcDc ބ<(5pd.#RkMNC9`/) uA;aWi'~b?o᥈Y?gϏbpd4G{SߒWO:Hd2Oq"[wA`ƎVIgm`Ƙs%܋fMts<CH_k%KGBQx?wQ+?ބ0Ayp>?r@z~Y+5P!IX' ld$IMDAhWC88 fBAIB@zBpP.Hnߛ>e#Wd/v`kkk{" ̘3,~wo(馢 4U\v… uH8y L0f He:zJtt:qm]pDtI`ky!-{~C@/rB5OI'P:x;.! E#(IPʂ2|w,pw[@ͯ @ 4?h6ܜ覼lUѠ雛Bu5ʀzݭts}3#[I9(Y^6I3Qn͠ws2 $.p.IJw FP?~&[[[[[[ /_~n_8ƒ>~<.dzW ePj&vEh*ݥq,1JL`BkU@OR+fcQr goU?|"X]hy!X+@@ R^Dyu4\@1k>\"H-p' ZxW- S-[[[[[[T;D|?iNqq_y?F!?"Nsn"r=} ԏĿ?: ϧc[qtTgAsL&Hpi6+>Ap._u'?9 u" -AAcM=;!? NNxc`>&q.,0z9 ph4pݎ B?{D淶 #~1_"•m8y*Y;Ep:uTW#sG^ *c?d5Eı_c'ZP# u7hr-x߷`J.P8W5/90`y: FerALs}?.}@Y$3Xr 7!xC6jz:Zoσ6řp!Vיִ{")g$vr)= ё$-뛕P5 {*4̢|2]笥ޝ(X2_s%{jP41i%~p?I+_`>y*~Scصᓹ?%8gZy$BJj ܺ;al2@3y=I n?ˆC~3PUYv\!ԮlFԪ@Wk ]`kkkk;k'נ)V%;DG<>}8 *n)gG@> vwٜ`3|'r_3+q=@a~3Ʌ {2EQ+ = +ƧO&A z&o`> q#}RJl&/ҬÉI+wA0p8rlAj~1/<ރ+s%pE߅n_YLKQ4y$PN]`}. y5p@4 (Js,8FC:rUI&O$8("%O%D2J8ș? YJo\8 pb&jKεc࿓[[[[[U;}ŷØVUόh5ZG @0 `\9O'w%JhE#z?|QofaEj ?g& $kˆo3TW?A>Hp0wj^Wsw7% \PӚ}4XmMe63"#{#͊ Wi3 Mq*.Z_k߇m8K0ԠÝ0Bh"(L2 ԪEɍr) SaSLԪ`?\㫿mmmmm][[[[GOKVcV'Hc#[5c%9Ȏƒ|/=h(Jdp1t϶. GLq+ ~-axbOkB} povX%\ε@$ۺiżW5ߙ_F&J,DRX 0A pBAw$ԜxmP\Z $!Y y w2?BK\2 /iommmm}G?JpB*_,#Y33`fɓ6tࠐ+%s߈f Hd}gE:& FZMi4%X}tǛ4`'ؐܓ <'+0YGH;t0=-ʋ 4I3IŎ p|x``{6@448kD~4&:(A@r(<)K"o|?[[[[[Y;?Ǒޖ=ʯ#?R8<GS$wtu.[L?9 ?V@1Fb `Aȕ3 ȾU+4Z?L2E+BcMᘼJVpaZ t-R7q]9h" W?` CL9WLYuQ< 1| hp~<g<(r$lU@rm0:%ƒpw}&#쌗.{j;.GA4gfq-*]0vnz?3邏8:::.k}c,]UWfO3>|xPz@$T:ES-\jy>( PH1^@+ZJ@~ 37&(Lw}?}7B81> {! J`f+3)me[0`%¤y2kN rddٷ*|CpKpߠCOx mB!u?]pb@ws pfdy445gXz>G4ce''Z%}GRIH/6r]jBJ9^/WKi%(;uH1\{{/]Og- `9(IIEʕ:y~LBnA? tR* !VÉ}8~ @]la^jI !)p Gr#c^b+;ZTtW%w[,8::::: o4QK}^#n-'_PsvfO ;bo;Mk&4;n:Ei'nAlG~qu8 Rô)bDF`NҪ[Cؠt/ z|cpB)F<B2Z c}_9!X@1؟n + A DʉDah6nr> g6OI=o_$͂bVj)cANU2P~%u*W vhftD*&]Yٝ vU@nI0\B]"&;w{?S--;wmR:%P~qi >N'{ .9vK4qA򼟤OMMd%Ǵǔ>if,fV;_mTD@}Uk^I`]t gcB) {#x+ۓw;qB1kOi"><|/@Jtz {Cڭ>C%2*{Cap)\ hC $KJ+}lxttttwI|I2{@EG$@qG~"w!P+bjQ*G\A]k~+nN M(9}.g -y3vKu 8(a:&5y.@3)NU1| Ըg;?fHNtėo4g0D @ aO ޷OdwMc]RhNM8:::y=W!hZ *ᕉ]={/3SB u,چ֌4/^\h"*aU+VU2~1<$Ph@B8"Р `7<}HӿLQ8@D) ;$}Wx۳&$ #_Nכ9]vtl 87zI@PPm …;*6v9( o 7t0)/@8QdIsXO |1?8::::I: %f{ÙepJ{ H|Z#[tj+vU3 PkEP!II  zIqL.@E*@>sTO>ֺ?<54}ր4IH糰e yKҙ!/%((~OGGGGG?]'pttts??۟t0}A>w"X˝ᒫzS{ fl32w?Z޶4 d5߫Qe[(iBM:)ÿ o] 0`$wA$7/ f" x0(vk#8zH8-!597wݟUzo@ haiq؂O3 cwJNB_e`OM8:::y` i4YtR7 \p\LWUE-c {+l^oGezZ sPDKt#=[+rZ0p;wғIJݿ!5@8B 5k{GXx_qHt290{!}әH;lj^IHEX!' p%\02Ι !![_eJ>FXdGɠʞA (@[CKu¨e %ؓ5hǡΙ#b}\f k%sdfmBl:K: )x{{̺3ZѮgq]ϫ&W-8`7UAj^Ԛ!LJa(+jҨ~@]+R (Q{o dаi{}=$0ہs! ~+ + >>ݠ3|/ + T(KWUH3b^K@Ѝ3" $( @v&6Ä|} UA)8ߧW[o#Hlvv@d/+!б0/޷#@)ρ5{6@WZF)D@5y-! K|EޣZ}VepaDNAnFA-~nܛACn>onpx8k%J[?VGGGGGO: 'f 0hyU_W@4IT! * ʢƅ@͠bHȦK#r?687.qC }ߺ u\`:}>a"_L{vln\ǑpH;?B &G'/w͞J?]$+r/H4 <::::N:?>sk#YGQ?(V~Ofީ'z/Ŀ6?n$νĔ p?Po/'~&p(P!o#oqa4$ a%}K49KNBFdc=&D|?ʈ\C;ҍͶJ)wg 8::::t GGGG[a *̘Լ|H=He5X?\lPr}>|AފJ|'Z˶wxoXɿ_)@nƀ¥pEeTqs]S.v7HbAP^@U<3#BSz@i0! P1@0YW? Ei_ƔA0 44RW{dFu *rDRj$UfL N(1ߚIwtd>gD̡v~^}z;Hak5wOfq "#aD0 0p.4z0Ci迭S8:::s l0slc FA¯J/Ms[@ɿFV3E e%I<{okFUn:ˀ (y P@"9_2K/bn+ZPHm A?4t i_^A[{n_`迭S8:::SXk$QB!x 1@P~K˰3 ?₀6J G,/i&h&,wy;v#2¹^[9>^P16 eF }`.[cPA4OC5m=Ŏt/|ߞ -SK[+xs|-uz*@,Р?F [=AޅBpa6WteZ='w)}9Y([cWcu `1r7]+xN俚 JY+Hh SoonKՁk7HX: `ZEߺcĜN `({k.5^@0[:Q zhuOPn&F~K_}#8Fya! ޺4\XME "ߥRP2"{hX% y2i 4 H5.H)[ ?0 \-Ȱ@]xϪj>:5v"?S8:::^_Y?fKACf.WP.E$PS0^׫tJPRPVG? y]ԗglh 5:^$(a Q3dlp BpPE)@bh07CFt_bjɛzl J'￳`,к&!ilh[[5_uPާA sU^3 3(3TsBc"֓<pttttS8:::>;kȈY(Zwry0 (r/qWF?y4VOc%_E6bEss?OScvn"6`_pLlk8(4|BФyBaO #swqG@iGJA$l}:37ܓݮ_]m])(, [[g01GC@&Q@pqGB9B1~_] Cpttt/.0J-A)G>C`uG2KNzp]A5 f^uD-l.*-V>ChK ncžm};ܬu/ @ƺ_6! |Hpw57~…\kP~+  uZU,;f0#$rWcIQpC>q+<r_K1B_Ap]:-N_2{oƝZ>#Q3}XFϻfW>*RhoW**&P<$Hl*WsJO9a/%H0J+bǩ~0`O ܎:zN`rƹFYu ꥠ-j=yBq\Òy/Z @։@!@QgjBm,"wq0Jq\e^1Ds(Lӯg8::::;t GGGGK\勽!k#~(YG =>^nt7-Cb+f Mfe؛}1@_O[6D228ϭ/2Nh ߪ~|$+_-sqvah/9:0A̭eOM'6iB 8va0 ( wNL0BF`$s /s( I.G!fr)}sA=YH2{Z5ѓ>K)fA xue6xAC^"PńpT!`x$, W lWpˠ46/ɯQWPXsAJgm0{ d 54QgJi\. U/_N3Vi_U h" G/S.7/r;~6VyPDw>;I0帿uBblq$ Um!ITNG0=_'?˶j"tttt)!bK=/UF+^Y~TgЊyTAv?XX(2` _ /yw_9!ܔA=Po&3- <ꀓP%ۜJޫm)MHoFZb|ZI._-hhcH`~,a(Pl0J?Ic(*&݂x]׶`GB2Dokqō ! p J>jXt3^+L1spW)ףq&O#?I p\4ɿ' t1A!52"X=Z ,+@F2\m:3D ߇#܁QIH@ PKUub(S@@NkƔV1.9vb=q-g[0 ]"fR8i@.ze nDf'ͻw)p:*~Q`3 pttt[_ aE(X:+*fW(V>[?H~I-t1MUA#0)1mɫLYuWc[0;cA o n t%fN3WaZeJJ?V}:I |J[lUg>$4Zq< m wkHpHbVr |~ٓrbow Kr<)\GGGGGO"x=V@sov > |PJ&B 5F8zҏ;)Ȩ`7*+Vsr ?.c, },E$I4kO~G'܎/wcq_LQav`":h> +@Q;x1EAGEtrH4s+/ED1Uj[OܓP{sb*j> `8@XE[W.Ii烪q WTh #g^#1OHbiT &!uFW88kzL_@Y)}9WAkAi̲"% +c8@H?'[}aOOXa,djYqs6Z㥮`թwdZd,A 2o{"=corQAc'j,=ZvڥIb_ -+z_0ns\sټD/i @g6$Gж+7Y!@ˌ&f~/ntU{z?[3Od#iʬg?ZG=xqD;ςEޭ@4zA ) MH'L'U:E|sl2W1yz3CZf_df^p:N,4vK9$ ؈Dz\NLvI޹v뼌ZzlR[&ڮeX2Nсb,?ZC)IDJ1W]j r4:E|\.׏)?CfpU&5Gx? QFN jzz TGW~(k(ar9J$$q("DA)=F|"ϕi_˙큩{ wg`ȱ`Zz#\?}JawUpӈ&+؛G8ssބ%Zg|_(_4Nm5Ċr9]7+;Ifhyۍie ' 8{PSEiJ*GGGGGptttt߷#2/+v=P?G?J1"/=@R?f>)4 WX$~csgZԗ,^{q}xlNcYEwzDŠ Gɾ/Ey}쉠!(@d?a_c 3uǻbĀZ(@놯"iy+l9 ҕ?'oa.[JA n#nQ6@V 5~53)^⟙ e z1}AWxJp#Wz0g 6H"gL"Uzdu'1tI{`XvN&\'/li^; mƏFv}ߋ fl)..)}r囋󟎌 qя.0xĨ3؟>`u91$p\<2VsefupZ #qLx>'LOZOU9!g4<ݒwGGGGGpttt'86xEU*VJ(mzӨD?L N .a凫Cs9S@JڞG"h2&]Z&Z@zY3M-4cW@H(> .ID4o ,Eb׬<L6zl UN70!SokJϻ7F՜{ Që{4AHzQآ K#3zmwF\$EcG챽7 ֭KxpɋFٍ;(%KB kR]U.oS.""~ѿ_+=vJW*@iW)z/Hw~YDm ]y=Gz iyɇI~?kOҝ|v[8T?c1xÓ3{՞O݃9(}^c4/\@nA;o܎7:BB9ut[QE`3cE&~k<0sڇ+?:::::o3ert`FvUX G-p?\“ҹ ql asș*m vRgB=DzK93%w61$kj% ?8 ps,f~fFI-;(Ie;ˠpo\w. 'd~>qh[!L.>qF5Y1@%@AĔ9/y ܂ Eaʞ9z}=t_deKMgS8:::wgf!@s; J373DP+0,A''\_?M+\lrs\4BX5DZ 4RyZOYӾO=ZXc ?ż.E H}D z5)Sq ԜIjxS|_zm{-qDzp VpX[Мb~ZBȂ5í&ȽHŴ^{x|0qƜ]v d5?_xݯ' 4RA4N xz+~۞vGFcX1=4WCLX 2.NƐuC?2ڊ6v8-ĹdqJ5})|3`nb-V"88±Q p C僘-/vIZvX8#ac5g|ZfZe(/8:dsm{1WWi?,Q1/4~OgNnaxh=b0@"0y(^nT[_E? ?8%z:=NdEjyPꪞt|ޅ"bḁf޷1Kf;1X=Oe ɟo0ɿ ))*ZàI$d^VQub>c^dIa;Iko,?}IQn~6&_万vܰJ\ WxOQG-Ƶ M>>F MExa'48EDFP@CVH`2䓐't7*u7Cr[~ IN7o_/ pFrd'ʤAsކhΩTc`F:9^3icҚl]asߋ|z扙ypހ 8椀 |W(8"trD=H4 3~[rOqdѐu!W w1 m։*(e{D0c(!5'$FA#ooXm?KxpY`(ՅS򍚳&eAKүf5ΙҞpg!r H6?VKMӌpl;;tY]٠* Yc5֬W|ZnX?ZLhPZFL0:BH$"nm?0\&v_/^?Jy O -_3);?+ ؞TZ,%yRe(^ ;"6ܷ͉-}?a f,dKu%`=yMT,f/b5]iΙLbOsjf/347 `oDq [ M\ ?X 7@Z<;c\\~h98w)0'?*78{0-svaOahFU u'@*z쉿 U9N_ 6.y2{4+*R h U (M q'q=|/3_7U;PD{{$;'`}':caJ7`#a3`_Sc1L3M#m y[ߝ f|+glhX7>K^p롰VVQ$Vr9wlf.GdӠZzP)s{:tМ4Šh^X1*.*ΟJeTخ8oV8NO{F2/&u5\% ȇQB@nn!?*x$O`6}+ {޷ (0Y6-}My\ .oԚcȴEP 5of$&p9^4k6M `L_\&Y(Z k}tSq,nL f Ugm~8Nq;2/3< qQ:F2h*|Sإg)v' id +q]ʎ_,6ۓ'`-ᅷTjɿZl)e[|!sNSJ]""}Ŀ <kq-ɵc& ;ҵ\ s K֤YD|K5v܋\,ha$T[cԆ8?69OMdMսUlԶ@0$W/3`gMR+\R+Bkp~{X;CNNdώ";J]|(VʗH>e||@(* ^QyLuXmHGQ?bH>&8Ylkn ?(?g\3pN_ݥqeͼ kQZL"k%zwTP 䎑3A:)) L;pHZpHt?%ĮoUb@UpEɿ}KSCC%UK +Q0N+/ۻ'+[25ƖCЧ75@o컳g"h|&j:7aX%av<F=lbje3V0Ӡ [.-o0kq,K0p.؁|lVz@A8d4<QwONRvp~k7?1nu6&gRv+ANy!K@KoL b 0LNrol?1gW5NL,潅kkK<=Iӡbw6fZq{z8;%\$b>U򞞇sobB7$[? - n_m_ۿk[_ ˄K1$D#}Jc#or&Idq[D$%BBi*(rUe\t$7r%AU{wJe9P3wpFLPGUe~ڜ%1 %sO [[[[["}rycaq0 /}A#tDzW5j *ɿ@`עf F_>hԒ<9il7>p d# X _HS'[&gqSOͿYثc~6gA6ΚIv. '-`PH&Dnqa<^6zep|?Atl>%{.lmmmwPjaN/F_vkLsU,tX ^1 lʌkl w.{`Zxٜ@O]?yR_9*(kbֿs 'Ke\-Jeѣ-CŬj1/$ed8p^n@:<_wody bcu== `kkkkk^7 u5p/^ $kLg]&ɦۨ9ACA^%-7s[;m%P&Ͻԇh0'Stdʚu>8`Zv ǏgdfW\`) hnl&^S'l?tlh<(@NA7i8ipA/3{__@wwnk ~1K{]mL~9&+w'`'t9-2݉!\~^W`B}Q\}<0['Y w0y=`@oڄ|>oKɿ&X;‰%AFubm_A8W;[?o~$mCqĩ>gBEr" {ߥ ']'dpNA{u^x_rwc p,0 <3Ӝں|&NR0k=J~yppzN3qTWhń8}>@%01Ry"(@pNDIi 3zW:__W@:~w3 NH愨FsuR} _</1s^q8"qHFoA5)(h-} `69ߧпHrE]1 g Oa/ ˝z<y8=Lv [[[[9q Kp xJhrRy.oerJ!du]ٌŭX-l { _$i=n2$ ԓ;2@-HAfȄLmwt3W=g&ͤZmV TLb TGQ%?d/|8i}-].WU!}.\8b) Je ϣ o@ @Â8&/3 8zqach¯{v`kkk'vtwgN\?0ǂ咃 ۺ@LHl f, Q+O>p7e NݮltNM -~ƴ@= ζ=D|dPg˼o3$Zܒ '||<7bY Q`tܬпc$zaϣx "}DҤqk6xK/P]Jblmmms [[[[Jsc\+9 p%%P"|S ܝK3Nqna\J KB}=ٍ|D8H)}X l'TrMUpq\^(ă6ݴ6}h]0/6f:8JT&PH9ʛԌĔY Ql?xy|lB9k .@x?%&aO0OQ]}{8bBozLI.$yY`QO(H40e.նZI8xKe#VOXm 12IIE qFXOϮ2ł܊k2Pݭ-%Z v!-Otg 4jE|nlōD,*#ͺIq2*p: q4Pw /N |\>??wA8`Pn@o80i&ym(>3ȋ_[ZWt0=ۏi?zds?ޏrA>W@ʵY8y ?1uLa7/F$+r=hL[mmy>eݥMh=JVD@`{ :2Y c;q;Aמpkkkkk EhӞ.rbp&Lw!VjI99Z[j%a 6 =q Ѥg,ϭ^G3l6gWY{.z?ȹ_n_Hgrt\O{ v Lmq\d܇E0{^7>P6Ԧ~ /P m՜_^RE(we1!0cԉ|8|BߓgaNeN<ܝ0d:=\v^>'Cmw ( 1Pgjn,<$oh1γd^'+{]1$ٞ٥KVZ?/ c۫%tm^ 10bIr؅BeIsX߆mB> D_qqź٦q1%ׅ9~]ɕh&(^ȋ& Ѷw<]8l6Ŧz~$XS xrJf1_x)F0Fx#% __pB%:p7.pr)ޞ\27~a?kۨ~_PlK)ۓ7qEbaψ,߂&xx>_$~8ч0%X 0a_}m7e_-BS3~?,@ TXe^ITB?S/b @bW FM`4}bz#!wOQE"ʉE]nدEaBYT8a@/s#vy2龱clU¤ה׳gnϦ_r׻/ʱ ^佴[7B7j7H,UA{'fthm؂u߯ g7|(p}by?F)FV )GD?KZ8;8׽OXnM"XsۃR^O>q(۩hdΣfКF;'픖 -/Ǖ{ɽ]?b BAtA>a~I=)Suy9l3mcV踚j/|CI+ HIXr~\֒O}~_/t  h4h4{7َ$wO\5_oYDiA+(Aw G~KŹmrA@P^Bڲ}_^ @ܮu^.֊{+c]dz{^xٿI˶PwjҘņVL]KvWx@AoH_8I9a*{]!QHə4FSF?S|[jKHίN2IZER _mZ|oV8^Η  !Qߖ' l*uox`A*.n (VxX^ױʿ1jw~Om8R*`a%_/۟F0P؋&ʼ bϼ*em} J$r?rS<#{?8_xl^|\+JpFh h?QV+XybɫMtHrnR/D..;_|{ '"X{\`@qvqBJz>_BIʮRР*IB?h9s_&n3m@+\,U^9T]S\WŝkW [pIhV^U-/NW.t)˯k2-7h40FG @ r# xCʱuk 2hk7 \6zrD= ޣ]5?W{j,{Z7+.fv}@Z?P7E>ae6 +Xu/BYRKWzJ x.W_r."[3h40F2HP`.RXА)%=x53t=??Bk?/X`7̂@[^|n)m~c9↋uBdbB`f\6_0+A+Zxl1_{os2[N j:u;AH.  <+xrpfx "h40F~++h.(NJ@6`u9h$LA/^k?{ɧݥ_XA~n&Ur8KԳ GJBBuB"zŏދ$^ |,,dꟓiv\IJOؾ7.G;\?)Wy/ڍiY@8I"&/~Sh47k hψݼmfw pN^ H%C jCk?]Ky:J /#V0oW8tTO?xu9ReFXt>=.{1|(`"\߸c]}˭v2HXzQsy/32Z FP @3pv:A;63 It|?~?Fh4h43ğzMhM#Xm0PN^-z޴6jp7]GO^T&Sqm+ÿ@{@Za\\&"w{lov:k#7G8By NeTu7Ma|8Cvn']㷍FFFWS˵ LV\m6M@௕57Z?JF&+0-_f .T @V&v-M  `K"{T-=͟ߙhY 5p۩f@CB]SP!Eb꼄/͌nE7<^|p+<!$ M'h4h4EmHVEOr ju 5mZ gxY· ۀt2COQH5)~Ðk:]}CoRl@2k/cOy_ ^\`A ܎-G!+!梜P[@tP=fW%"dPk!HFDSFFI +o n6# _y?}M@H$mb6>^D> dn2|Mq=Ӳ;߅ [ K;120m@61OOHDgkj nqa/xt;}ms-c4F0F9o7.BXgԃ~V(smfr¿ uT$K0any*c󯿇~Q߭{, /)ȧœ^R~P}O٢O;Lne>)(_/M^2-_}VzO,PPp?{g崲*y|bwGBI ܃jT۱'?JmKiѳ7̀jZsuj~\yavr&%z{{4tN}I!1C^ڗQɺbqEi(.nsDܗeX~',]Pa{.60 g(Pu tcY B,~v|W?.yycHr]r +w+C #9)CӜeX.)M#8YyCc}0 Cmh^K >C֧#ZVWZV׊}>ݛib3k%Y1`?uww?5v_9c 'g ϯϹ*BIAP|CZKVm`VFf %|/要cfԷ(!2b?ʋ(jZjZF<*T@:bKuH ~炢@YW_E#65{Bej~qj)Ņv-X\D/^ (]=͕&T:(CqU^-6 JxƉixCy6=>phMt5M@Dpc4}K` HX%@OjZ VXW 2rtHsvwm<5V.!܅ θ |ܜ 59@ah1g*@ :^ ]|p̺"ۄPW<_||p%L/3۝κ}n9Gܧ yl `LA9wL_>uMjujV|]!r-ݸyms4HQC  z̼3')'0*[yKGؽ7)W,]*Sx]*~9TQ7?4'*PT@ƥ?.1m,>A8YKp@.~e(n*d=a_jޡ.ZtӉpqqsX`@0 pr0ѷ08JC@|@N ،\|az*O5bA`'gVck^lsb f4<_Oץ #!5+k'݉Jǀd@p爞Ѷ$=⃳-Eo'J,|vqj@jWLT(W 1gCN)C2:O,JmA @sy 0 TpjzJ4VVg|.鋻`$m3l'7~O4ȉ  A2$G0@ق3Rq7w5L 1SM1{0|hiJ7@:pxMF تzۿjWR+B*rn'FF|_Ƕݏ0({1ۊ: Jeޥ zh@jjZ_%}?}7:0~)68LQ+E;$ y`.Q(!((1$OL'T>ۿ9o t3'"uʉ$!n_|a%d-@㸪_} :@v@,bNJ C}}jޥ.Zχ|< ObC\=mmo4/?FCo!a`kTJ"j*0s7.[97Z 9i&'Nls+OCu|*μqV/ հ  Gn ޜC\h'%cg͓J cyw6Jn8ݍ>"Kl[tKS%;jݝc9$RDj&:]ᾆH8.HQx拥RT R%ݾ%;8uh9^hh?L 5ij5trx2>3%Us _>-A! sҿ\~;jݗ6o ߝA%LJݎZ啋qE^`+te,>rc9=͑ET*J@T* ֚8NٛpmH|H0̈́9g]tFbl6i!Z;+@\u}ŵJfA uj*_~"mI#@J烦)+򲐥|t/9Iޑ1֐Fgh:d.aY&'%ƭJRLU(J߉fz8==>@AL lF4CQ1 2Y:39pru/%k6U8C š]?mg9+2qX,fEvs )Kn)c."d~ e`X,Ǹk9ے,]Iow-ի dJR*Jҟ׹W=;x<9>$Cۛ`M9BCtC PFix^"õO)aib%A7]K|>G{Ï.݌~P>A- KjkW&O%)aΜEl7X+mH¿鳰"qN f@ےuHUJRTRTzEHtASGۼK}'<q`;Aj! 03I~j3k! gR(q/%V1vW;~ q-?]k7y-aPv-,E@1 u83*8ȆʠwsswyhrWwY<_RT*UT*36F.HakA濑Mʹo؛Dh3(2;9OõL@%S.J@8nud__ >9i0#>Y%?Χܥ' @ 4 vo:sT$KQ.Θ MRSJR=#Kq %}@{p{8qߛ&&ٙRH"DБBM(Nw>L )Υ8^}T,FFoSΗV^K1^ky#Ķ;_8-9!"${jå~󣎿X>8kR0g)<%)YP$N@ަ")D4{{Gݞ{{wysuP}[;ցRT@T*GHDӶ6@dDI ^p:8q"#j!ByR0KF#DT@Ő1{R˛vBW†g1n>b˟? . Ò"R%32K} .Ɯ50e~ lиw"&xQpB8a \Rd]<{gc87JR R }?7)hFl;[;:$WsY!F_Ska_Hg`R,j9ُh3)N0-gCןm-Py 5l~]o?ťm>xIw({kQ:2CR/ܴ( &pNXLѼ2" c%ΧD ݛ@ \F2{_7W}0¿Y$lգ_*J*JW!|A.NYExJ 9th$nwA?X,نr*4 O vpSp=Zx蛟BA pI/nxrS͞i䠟rpI` X..`(3Il8@Bg9Bpd1`:H!i?PP*J0;_For^U (JRJR %WKx8t?լ=uY ag;\3d 4.;NTY7üp9AWr8Eg/8K5~n^ VlסS:)VmvzF@_9!(1 ;xJL?sof1 X IvH"/_)zRT@T*gGY_L> ~nd{ mq'Ͽs@3y 3K[Gq\P1Ő3#ӝkyvpzuuu=ϋo@e&BB>X]_" 0NKN]iFWі2uz8]k9GEy8w3``Z|Bg} kty@lG  ,ip/zeJRR- vI:1{ @7@?c6~8s@ }:Ȕ#v:=KlpsdbLC+o b]L3M?RLxߦoCFK~#9xAk L'p9 ] Y?t~ܭm(U/JR5JR{h'\&{?%GogsIdO?3^gv?7=y3$8Sl8D034Sra[<Z|C@=K1mP@kC/nqէ۾џa&}:Ҽ<H8}0pD2L2K5 )Zo_&:|D_oHT*@T*}t}Wnfom@A 9!+ T왟fidw: !0ǔi@rZqEo9CTD[2^m87MRpl(8gr͉(BoQ)'`'Wˁf$N'MG1k1d,6P߉?*JRRwA03H"<:ȞOY h؂bttvM6Iɘ#D\0nI9 Eԇwoܹ?\#W>=szg{}:-uFUCs0 fĊ VPb&'D7M ;Zw.Av}OzT*%@T*Wί>À]S䁝;pAls,F:#-T4V- S"Aސ1xub@b87w^N#4@_|d-uzo(輄/HD9|ޱE&Q:f?j &h!8ӱ7)r#>@ql9M})Cʳ5(UJRR$7sn%HrQ$>0Gx1%9)&#l}N =Ӆ{7 Vi$$4\>gxB!h˱¾V]" O?״`LH]^X yzKJ^xBoS'?E{1=fpivh+5'OQrҩ `A"{Y."m(f沽ۇgh9#"{̝݇!t !QXm]OL5dg8P@!~L(0g%תmdu NқγRT@T*Quuj ! (#Aɢ)k/sA.Qq\է2;)>jS} Vgh 99>~Oś_YB@ S`bjߗ7P6nM AՀhzx&c \ 9"ckMk!.!j`=':툀)ͬf0r`ޖ'IEkϧbur)Fk1k#o ;ÿ _p. py\Ok^oӄ. _ /8#!?@@*Z}u JR.UT*: c5 F:߅41xދh4\kIl|A~3Agsp6Xw}Z'tT~?@ J/#GE7F..qaw\Yc;%L[s:/5̯.fZWDqB Awp< [a˚%U= (JD(JWrߋ}WډZsM$ADl` R#a&hJ츍qY4\-KS,Bd7q9{gG`J+=2ס{L?S󇙛 >3yJ|&>wŹ`2v,z' s x mΩL{G 0h`1W읍r$7McVKCJ6;"=w֩__*JjJ'=H!DG~_ Tn;i4Ἷ6 =MhFDD4wP3l3 ZӁXo2 }I@?liad4,u)nb 0%aʟG D)næ{:gWzN6tvqպm-[(Iڧ=F6K.6)d8v񉎾yndq4ɿ&Zݖ?J> dzv$"'ϱؑcg :{ 9iv>]y_v4.&kqLw-8z K*~zB-(JR5JRIOéfLN|Qۊst4(`;4N$bJ٬4Gk T]gp&י31/bqk~ ^C7T4<Y@ϣqm ɝ렇o׾vr#`v7vK@LEt!3,$7; @MG_*JߩjJi,?~!nLܑ26J"b55$raA7_7// T?3c3/_!*pmq>Mf5ki^/C8Kɹf-i0lf른?M9_D# ?ѥ'1\p1q?5 Rg+b@tT*J@T*z;~n|@X81}\k6pQ\5Wx '?DCM餹Qi3. 􂍐uD`,Ysg#!8Y{z_,m} ޾¥pY`#O7GO=û6]=ݳE8ƕ8[kzZ|#Ea8$so.ioԹRTT RT%LZ<5ji;|N˧U_M;}ojs|;9QBD!sV` ؏ CtY QOgOg0m};C'¼7<@ ѩ6^QF&4,|6+JR5JR~#5ȡPo.wL؈& ws@t`ܰk@Gx jr"ȹܜKݥqr1RM¿bYm{8sv"#9R 73Q^}a-p ~ Sw ṭǛ7@zWͣh;51赶,ر~, ZKy=O3/-81G{>hD:0ùw]R`5KLsJSU \O4̘g8r[/hWo;$S72ҍэM ߴ_5 JRT(JV&RH0"'7:pW'"e0Z#}͠+kKto o/H{5L@}\ؒzhN;)?[ss}=۬mҍ/'v .f6}e šD>/y G!cҼe;Rp}PJR (J*i:Y11or Z3/Z ̂,2 (޹M ר͈/rkm\ϐ%3ʍ9? h9՛⺥K0 ҧٯ_Y<\+[9oP? Qb3~\ L!~<,2MMrpc"M&$$ݳtB/8 - ZgkCRT*UT*}1+/S1fڀl=B殆4&K4_H`,7֨נRoZ[p:sPܕ`v#x޼mM{4̓A)@$Dۮtχ0k-߃9 mb: C ogB^bI~pYRm{ծZiZr4ƉI}1':˹ڄ@s5CT*J(JNO vk9l~y8(wFb|J?mFpJ0'N.dP^fM3S4q=(NҧAutC ѯp5wKG-1͂)(ؗG NI=?U! IN6>~h-k9;.AN7{J)3_noúEMh4~\\Mԝ揮.\d ,mdJRRI:o0iw:e5CHԜ܄ @L435 6R ; &E /}^?7g}-K@ni_=$ 7E#6#^'4?޿yc s~na#@rϺ/c?5c_+R%nRu!B 1?d`sFEtD/ }{RT*UT*z\S$M"x=qڮ;&Z-ՂC({H+Ssל 7ug `tc+D7XVraQ~sanVpPkb fQ(77N=,ZTߴKNK ?Y!Mc!FM mD ͥCyꥨH` $ΛsQ@{ȩ5)3i"4op؍ *nk=:Zʨ͋z\kas?_14wxrA&@ %A Dʹ@ΫYŞ!-qťJCUT}#Np΃0vl G`LI ĊH7 VD" <~Y QZ&=s@h~x\̻0` An(}]MpGVб-C=>r_چktiInHK໻XB?Ԟ'?B>qE&k3(\F%̻&;5FKv? ?pN%RH!<2fAag8:::g%N!M7ю!9ݍNMx@>u~;6WW Ս5 #ȰG@%<~!ruZw4q@LsF|\y7)nBvRT3?o<=76 t1%|y҃3#xl$Pb>F 4t)Zŭ q˚ C`h ]%/rw5ğ%?פMU7fzYbR#A(B rww  3[;w(s7wك   L^HW!U$c; ?::::: V |~1g +`ps pQ2"j9K(dEEƱ$PNAژˏ1ͭPSD+iM N8֡M| Nf)?[ñB҇rZaZϟw3U1? nYF/I(Lk2H__`xBA@_q*5On8:::9^ iEP(&( Dԏ7P)dÌN!%R\p$e&4pLg1"0C@mi}8zFRx J W?H~doz[*y|o5&y\D/s?jWnp6*J[2*?+=wvsMNF&_AKDn%`).^ B^x ?h҃x}38:::懯Kxwk#(&R(z0 z|@h^  %ph^~w%mVEZ5ό_, [ak ?נ-SLo;fqV6f\gx}ߨ"g|Rčc '@dc@=' d%,^qp \4 .ϧ(^Wtj܋khխ~ӮGGGGG#: +u;3W˽ҧ׻& Bp\IzN!Dxh` P݄*(Z)j%4ҡsI6'ڮe^;SY"jjMH K [?!?l%Urk,pǗR}ֆCxG~.E1H;Kr ˬխ. $DRahٟ{*.{|1.!h0 Ν8:::HzHU_\>T;ow EьTÿ QSDLtA۫|B=ȋp4(#LsA-Џ#z9ǡ1׾:e 7$??DS=7vRg_:LK !`zlr59k9`C.cԿ9Ǒu"jj_x':@oC*ro7!$7~+N蟫}_c骑 J^XW^} xX'G3*8D lXa;w+M#HG!UUSmN+E] ZHYq$erɐ98ʫ/ y9@Wg =p@-@z =46Wc Fe9w^DP렊@GGGGGptttRm {Aqr+?on@.-IM'#Bv.90(7R!Ы%׻ZTmNg]ht]k?ܩuQ\o ,Kh8|!Hw} {Xc.#%7F~91t6?#e NHwpeVD~ 4ޠ믎?:sN'uz9-D^ Zr¯no& `0@ Ƀ"J?q%6 @+5K BO\),'8[jP@MzD[ -w__l]s*lK@k7Ɔ"p?v50<_N+2??z,`s}T`͢P8az@$Fe(dG Wzo//xO4"8~|>HNi_]Rk247pyUutф(c!҄UlwۂTd& &E% 5'sكӴqb:`&%zJކ&@vG5~#)n3--7]`=~:XA>Op.mGE@05#I%oٿ VpFc׊ &It-"\+&=p[;I'/>=i=<Ġ Q,mO67 7+0 ;(; WnOS D|L@=@Y(X-ErtiHCIDħ_̳gwmjk~x[ce~at6*\z~@{1~ \?6 V_AoXߺx0_z1H,n@w@Q aJ?"i S߾ÏX˧ "GGGF_?lH )'Vc=Xu>.ARP`/2 Tk5dT]}㯙Ǔ8/ezJaPJ!~ݸH|~Edq4R,QS6;;i 34}@M~Z#|Nq8 "P{Qn#וp7F{uO,/a;=IPؖ[i (.k$HxٞcC?c7?@g&o8:::J?p GA(kUy!!9I ݢ$L . /h'y]B1>bWymbW'B,o p^S84RJ7gY>?9`;`䬓Yp'_\MV)y zG-㾆v~^r Q' =gr.3<,U(/^5$#_^?(g%T+5Q Hϟ~£_SptttcYV , ΜA.Jy~, w&P0k0B4Po ֺ~.69 -0/ZQF\#',b$ϯ=3,?ZCXgZ@ܹoee<Ѽ ]ڂxyq;>d.pl|!={?TxHRE/|?aq?:W֙-GGGN\1zť;x&BH315I4Ѝ;Q.yIŀBͅrlȪkjπ_|2bS0T"=6<i1رܸouAF6=`ͺ=xh矚 o4י%7.u *RE>zL)E"2"i\~#T}^>J| A|I@'+~:3+~u-~i}^ݱiˌeHzY\" d|d`K (S6/ƙUीad*/ۀWxUz\xG7}ZO;[nX~U Wi0lgT.k+xH֞ɠq_W)3}|kH{MpcN |0;ADwG) G/ߞUKmډ^oIHzǯw=KkM`83~ek'!5r @VBч.4 ~c@(*{D Tfh10{ i&nK4?K#1?a$L644-VYW+ 9WMp:=){co`+  IILIhX?m[^>Fw/CQ@Rc#sPse>sYpttt@9 :1)]f5QB>32x\j *c?%O.@Al>܉쾐Ti'`h?^DFfWߡbө yXJK'D N39nAXNQu<8r?fq"ew/}+>uF9[bbB;},_Я}×&E?lh 4l6.^ףj"*2!17F B'I?CUzLh O '?+Hẗ́NWii?E>*fZ&+=FlW&"Y+)PPIpA攋.>EB^ %Tu@?^Z{O4 lWM$)5+8\X8 p|ba@]Mכx k}~@ 1u(p!-;(:hf1UT^j׷(%?+%9C (ULC }zZZ@sE`1DՂv!@4 iџ ְ%i+J5 %Lw)'pヴ&NUyw 3Anr!Tpq1rE?@oKPXT.f dq A"p'u1JcLvưYz\R0@ >)+>/}7ޤM߅.aו,Ũ2(\4q h\L=h.|ĕPg<y1m HB/(ˁdзsn?5ζGGGV-cB`Q5 |5bUq' TK BϻU4: @vɥvh#:|m+@3;M݁opC ¿>x:w^gBK` up|+Vpk }3c9)&lx z};$T= #e-vqu-5c@Fc@ \ϾxdzOo鿅[ U(7l] j4-SzU&g'?>߮8:::oXR^dG$n46+>}@Hnp/k 偨p.{wޟcqHs6Y"GZ2F@z0F(9 \@ ``sp[k|ԍKqcmhrHxJڇuYM}H^~ 8fe oi/tT0jQ-4 )V \ťNJh(ҕ Sv  or=זZ u{ؿ2}13 ߤ8::lwb$[ gr7_[m=_YfnMW0 6` &\ğ/]uu qLE-py_ ڧ]r0#=b6iꌃp)؅7Ր]~F̰a}0[-wT\f-?jehFT O>G[1W(r/eHznگ.^˘w, %u?>'_On AW>{9MJpttuA$v N$lNNqkHJ^'>e@*?]-/Uq B5gyIPpӼ~)Hʫ &@ B0n 0ྂ M?VAwȡ 0UNfK27{`M+d^ 5FKCg>?+eNzwB1f2wkhzݕ𼧿6~ `|Lb~.T 8vY p$JNwIV]KWTw8IXu8倣JK*[k, (~twV 6(p[Xxw=-㜜_J; Op<5_WN 0Ol $Yw_`)p4z~4p~r5`d"pGԨ_k0]>5Sع[ $Q~8U<[A)$pgy#oiw9iW鍋1o`Oߋ(ǣ@pttk5!u2z?Jf/Zx^ `jNf` U8%Ւ~.st7ż aOTH'{6 Kn`B!\ P=вylmQֹ#:9ZxRpZxD͗f@'.-{꫻ˌ.;o}aa @\"޹hǍ@6rXE>PQwϸ{zv *$U*YzDRGx]6Q"-H5rMAH\rLP={4zG(d|(PyHpYzE,y@WEk;~cyB#Re\wt3Ao?a'x)<?7_O)@^{IJ}DTx9_[!Bu=zM\dA `k@{d"0&\>4pC߻`t wR1ӄDԁ.+4@'c'}N"Bb1]AA?RrӔS_q_o ̥yN[CZyn2{wuЅ vD ,ѬIuRU+k)$P6 umF\;˝fw.9֥V|CeLQDMʙHݾB_u%![}vx'~u<'xM ڱƌt|uԫDSM@+BoC%\}#e{I45T6<Խ"f7ԍN";щL(r.YF + 0 R"T \jx#&M(_hF(_a\`ߋXh BN`X7b/TTo?&*`+彪* ~ldi$/`ScA! )ABNl ФEG)B(' aA\ zv>ہЕkZZn-+Ei^w'&^4H R.}Ox≿+O<5zi^jKḭ} BFi)R!g D.Y'/H10h4C8LX =PwɌs $>]N `$߂pk(Q>Y1 [:S."8j~]/9`nqt\?p|gK kV\Z d>G-d؜Z0Ib2ւwڱP}\uÖ́QtOR4X>u֡< 9( k@Nj#|yi#|YŐLDjyU@`P`b7,m$'i%r|HZEg 3 'xZss:dq>pfd?v?ՂE ~z쮩y %G;Z 0b`S- 6͹LQ,l"M8%ApBpEzڹ{x_i&-Gu|<|^C*Xn( !djhK&UJ=0PC%եM\"-b_Fx1KWV I]pAlx9. z`5l2zv-Ghdo *AL5yk^ 7fvm|fš`)3f̧Ҵ53Jrks/Rݩ _=׌)>^ra G{0Wck|X A*xьky7O<'x9~Tp]O#0ׂza|j 1\_No"rѿ5 CaÌ!iPLvHF4 KPjz<&<066P 3ʄ] L@-3T"Tȋ~׺W5rƸ)FΓ@- ^p~^Lޙ;?pc ]'󾟒 sK\0=spk8}!XЂNj꡶쵿:GcWNNna!?T7R>nFL$;Ώɏܘ)Amh֦(PVbs /yR- J3!;x*g+\Y0K`}1oC[d n[VOme{+smx<ӑl{,D diZDXP*r _c2׿1[=e| hƱgƉrxrYpZJ 2'={"I+rb"qPhX6_y\Ō+&%2@ dR\ڳF6􁩧gBqL@pye;?NJ, e J P2Ö 3;nBIaJм6a_Q.6ӑ[AGs.AdFt5M4؇?k3Fr 5P[y`f⳸G 3j^ZD5/֮Zzc}}A0__#N{][(^h}- 򺯾P}נgiCX=j#irJŜCsޘ*5Q@j n{h\y*@jPSQVwQ]戜LƎܚă;AwY'} x \P W9]9z0БִW9H )4@DjY7:m?oL=w~?;uD^+e^2{_s6_=t㜏h>!qn#.D,U(4 f[a_0Y#'%x≿3~ D's39`g1R[Y_S9 \jp@#z0|"@Qqƿc@G3Pq?%塐L"Sq%=k<4O{[?pD ۷#'G  +iEb)A2=O!xK?*GF^v&M?$UH ^ŌOv]٦P.|uხ/f%9;!i[u1AQ ST %5Tp{5x9i( &Z9_(``8C5Y(:D~ l:Je rC` 6:C \p dN@~ɬd oJ/Eacƿ1k`<{l+TV Ž߯@;wcP O^GH.WFЯH^Qx}Q81"^c8<4Łw oA1{ڐy y] R@R9ʼn|i{y%diePFN9 OwJ '.* (~&J&0P'G3'K@[Xb)㼉d6-:A8Sw@,\o,,~@p5^ 9sM]aE.K ` ]38C(G(sƷ\ Q#99p|Da;v7i.O:cObThGk,h`P-vP % lx3Ԝ9HBb->?+~?o_g4N z|{87㶶6-;t*k}ֈҳFO<xNGsyTui9~{6= ,sɷzo->52c??ڄomCF5`IũN/'Q{_M*ru/hrXЛŕ+{Caě]l3@m(/mS0vK휠01)P?YUKx4ۆ*ȁ0<:b/:|דΝk'{s .Bq>"&ib7+Wiވ;b@pSQ}켮gP (W@&E=?GCfAlZ#hmi&+mKf1Gf0T heTL-/)Δ'V:I73VChk?t;_!?_s]i.H5mifA`~tW@i9.ЅjMD3$=k<O'?/Eo{Q!,# mga| c &>{s? b@P?n9h്`uPdBr4.ӟsaLeUͅ@RPń>/dXI} `,& m+C\A~CfPtKGBL|X 4) B4ȘHRP)6{J0Ԥ,g==Lyܡu}݆q?nŤ8EB! |7PՌn [cD]~"wEFCk&km-4[_orɕ@R]K"b8ޑg?_^F ^~Zknf;ho[{c X_m%>$dwnvmW]̽2rqz:rqgWm{tGx] !a[ n5cG,;g;82mXEG4LSw}Ƃ椉n2 qg꾔SthLD3 Spn"1 +S9Cĉa3X*PRy xPSÞ8|j*wɀ48L)|]NY>3/sJcb=#J|6F>߃\N߷ 4 <'3:[pf Ħ3"$056Dlk8u>g"[gh< SCsslh`UF'P3 $t .e0I~{pǣE`{+2#u,?#|8@|mDB~x?gx!u4Vp jQG`\=LnownW&J9CxzŸcZ3$?i'y_+{<2GؙZ)-xۖAv5?|ANI).6Lf%F5^3v؛zБ2)!7yx} &(siD@nc<9W= T͓f`Mu&]A"TI92k@LQa!8 e [ir6&ky$ ?؃5H a}$lBֵ6ל$KM6 RwnGN OȒ?E5& <0q0A1 F @4=xos_̿c{ {bݹi|~Fzz.}1:2Cf i?m'>-xR7ahf-uB`s辩X@=f{8(m՚={ 1/qrlk!ml6Ɓ{kN`8Ӹ?.`;L( 56&pnnO}=*'ꫳopYB |}!'- b1qOguX9OI<|X#i` Δ/Qx) P ;Erje`,h@[t>}_5\4j'6!AtF@mU^w ^Utn{WᏣ~Sq{yP' @xݦKd,w p}k}_|r #XnPNMp}YB Ut% "2淉,Zdki0p1I|N/-'kz(jwY|;8.kTBw?L @P.ҝ].RCM`oՂ`˶=N^Y;Нnm'Y2FII^9,.%r]J]@]nm_ꤲ TYGQ@_}E௯G~l#(":0N.}hȴ6hdJt^U2;h32.0Ia7I&h~EDAݜY+O'Ԃ;h SkT MP+L(>i4{0Fp<% bE Yk߈\.v}j#<>,_ i,\kg^y^]4Muyb ƫ Voe[J&HX }d}o%2rTBۗ"Lp.LG K@n _ \ u5>|AlI$S6d|x 쁁d-=[?o[kkfw3?ah0]ot:e{Zmۺ1~eUfSvgXvJ]cw?jU@ksُ![A!cm:Z†ߖ7RXZ 0&S:8@"}8!RooPXc`gl K b߇q_%d0Ect <[?,A(T-Pʷ,oJ\sm,-aX@j2/Qu̜Ml2E f~6TI"#/|UJPUß~a˕@jRs/)pXB&i1dw;MsPB+Pr,d;RlRAϋE8JijdOʟ~S>yd,dGbq ќ]%7nz3͞$4{ ~WOo|{Oڏ۳G =I(cw?^yGxkt 4]>\ri^pT. Ϸ?x.DI ?ت\#`>SOZrrXZoV9a~<Gk187Y$tLDshuate.h&9 0 Qޙ4Бm$Q3 ")cI7h!JpY|!@p-[*@> ?Ar/ؓ("Zg^4y0~oljc Ӧ3(b?'E> TC /y?J%`Ȇ؄^>j+I  ^k {%~m y2&t8yF2k8d"!ќ0aGz >|rY?nx?~(xDyEQ3&?v{:lDJRwn)ߍ{;N?!؛qKB$3sr_6:c:mY}uk;nY'ˉǖ@?5d|b=;@SHM<j2?f@O04墟&[2ߊ5-+)6; qwLAU&K;(_7Y_9+Ytق"jCVngƂd:ޙ-BaOH^]4X)wv(iRO^,2# !TA `g,0V-նP-/HW(B6/nT#R묙mUARh\#[=Hlvoo;V DtK'?fO3w7s8G^~2,$/){q( ެgL6cG!{=OW2o(iƿMi}_=>)6`@:/y'L` 0Vk~[r֏%׮px(gCKXSwTaY6( (S E\ 2qP`ѝ4*c,%Ce+%TAO'Ex>}DtA~aL 0_޹?ag]i߅qM~댁{Zmvވk/ uGք^W@wo5s];> X(AwC,S(9(JCOuzcH$\X̯P/Zީ8IŒhC0@L7al̵Y>2/hr羷-{[d"> E;G#}ŪxDywYzo{td.\t-1N9͟X. {ґmŪ;9vnkC%iJJ)M)3Sqzklݡ&_d@{,8\O&)0A\UO5D[(Kj24 ~מ^[| N 8{ vo4 Dfnsgv~DA (,E"k*O2 J5I)/v\CBN"7j3 Q]Txr][nPg8nF5Nutw>69on|_gIo=30ܞ|md͌p/Gl?bktea=&pU̵3Vdl3nM >c>?]{OR>oۗrKv'G}mss -6?DlfWs-w `<j)ߍ\b/oG&$}@ekU߯uZo|-^'4l?!^36 Nj* ʜrj-|`f9%RȞ:o޴]F5`J;FC&\Ft<)׵Gv ox[(X T5EU+MBߺhy^o>%BXGNgj{kzS]79oxcJh [g@/yz,?EwP ~oݍI y~=a*Яi }_rכ5x®B(26W Q-Q?,mZ3 fN ۯ9juf?87 ,F󼯦Y̺&>F D>^sOLtۦK=RK $mhEM/IyVmJ]*, `p3 %8."}0*M{ދi&n[גv{ .+`uYĿ8Q^A#`& ~.j%I NIdžs=},\%m PA81{iu[M  WP `;%;y&v`0Kkgz f6ؼ]֠r\{Bl,6X_s_L&֔kҒ|FW\[]j=wfdO`Et{uH7)sWsI:٫GAufo,| 1$8Gl܏ϯ{h]3L9 A^n%?>}_kܓq M*'ILԯ-)m}z~>B9~9o c{\:O:Vv^'m틽Yc>@0<=kyJTLԼ+e)ɊWo?8PǓARt ~AЄU-5̝x  T`3;(<*l߿ᤠ2pVNI@O_N nV2}D'*__j9k{ANA^4z (E)nUD}t ܷx6G=9i^N+ A_Q> \g3\5Xt%Hu&+}0\OɣTJ&+:~큌4fϼZۜ?o6ǧտe\LKEGYAdm@X H?z0O,*iadT?@H:d{'*〣OX;4}55-7?}?PG@# ux#n[c7.#αYp&߂4 |c^}r0:ݷŸuy-WOV0zqm۪n:mYQfp-|mNl5Zp`OwP=,)p"7tNP)[[ Ug"NLW N҃CIM.3=}2YV@M۷0dBy'^K& &2ҋ`Z+ 4` V-fr5*@4J 4MKi D=?G \j IV>,' .MЃ˛cD֎Eq rs9$)$T*SJq}Rp g~*@LlA)l.OGl4];R<;| b"COC?{@ 7gc@Alyw@0iq:/YŊ&ai,|oreJ ` \~2Zl2B Xd.2 U2ړ`G2pBin%X{#K9yx nO72{3?z13qߟ3G@GyZD a<~l f6rهF}}kgZ>OcNK_5yjf{]@gK"{i~cl5)_}N?B@(XTcm3???)q ͈Fb65 u?Y:2t#d(s؝; W'lw ;{x>W0] @+,ݶs3 ,Y L@7s_ Df hĥue<_< m?8pc X>F^Ke_ojuҥ_`t t.*2*,֋8k\^W?*AXπ&'a%१ 3Ey?\;Qpp֪Β]~RZ#%R3{5X{>1Z/~4ُc4ӯa`fyS2R~q qm` ̗;)b@F1}}k6޲AeȾ_t]Er'c`fhMڱVsq{6EL q6c-[M^9-OiGƉչ\֯:6_N?ߵ}PeXؾ\DY`x{w#J8Ҁzbag@W2$ z _5P2ke-jު*2P^KC b $JU WzM,7n IF/NA& ez]c25 <)>j]DY[@Z)*)Qڴ{c&!8rlKRd7I&ws_j X0< d4n񟯟YO#GL&&è١ɪZϸ 0@`  )m&E0n ^x^ (^+tMQ4#e=nvx_%0wFZ?AcOk~;>FhaF;ݞ8K)O> c y.߅'NP^TR\@5 y98x7;oqkAǑϮrem ``a ;(coV1 ?^6GW6'qx5:h5\}ߢ]K%}k4/{P3 ~'lFĿƽ<}_v-*ߦw]*Kڿđ5(ixanr/>XD>R=&j_Jt TNJ^NfXDx  7v)x{_ }V I U&4xq @ ARZmH]]T(1r ATbp=%orwW ˎch&`f,bϹkPMVK@s*kڋH_ }09n[l0F@iD_.AoC$"@ApK&ɇK䓦t=fw{ouvkv;#6сWo7?F D!5{Y>QK`w'j۟Tg:>Oc?}?KT",`96HA>ˊ䠌㰶?[;8~hB޲q=֯ fSѯcww5[?Q~֖ob!@UtBg'iEIεB@pD}-'cVP2x*?M4}^cMU}bz`G;;yͽ#; .Wv HCXV0U0eUoh ?!OD-! />Ak_ק:)V{(?Sk%HPPQ^`°%ЖF-'t~33f,2.#o֗#}iG˥wo8܂\r274ck@7Q4>fP,cb{77k ~pmY[2b]L_ZIg^'MW|`wȈ~|Me#ٌfZw0  Dv d}ꟓT{(fpN3(Q؛|?ir^n7?c_wk޺}tZ=C_'?G-[w##f >~X??!c݂9Vaxu>f~]"]Yt7ڼؿ>}߷em빣RMmKz.UopFHkYA!ft 0GgI s2"^}*⿧NAz%';*'T}&)إ{d(i[v`22-S_~ m.&w[G#| @DlL4,m-f k2m:d_W۷xiY`P9~)3' /W 87Z0 6P fNZ H 0tsFֈlXX+t\NL׭ Dװ=CӨ#7 j#(8{yh" _vG6x?_#V_?6c<͌Ct ȟA%ފu |c~^N5#kwHOej`~/c!E>ct,g? ouo1ʯ}=]?RuN[ ާ7N2Fk<79*eT_zZJ<]vS݇33>c3 AXZ{VONMy-~fSץ:͌Nt./ZDzX&1uKSmt/ r{IV(\ŷ~ mq״3^0(PŶPOWx#xqaEgp pZe,V /A:uV756NͫiVQ ikŭ^M=+H *p9_?kX ΆςJEApM%" ߡ-)7w .2Gsάe*& `+‚y>@ hC)kkR/F K[} `'7&Hgj!9!%M/5S|03hL k $ղG'-{vX=Zӭ?}tYӚϧݏ#pP8ߎfaeOtЛD_ |cO5~X Y?$fDgU:۷!h_K~S'C|-h[o-Gsl-]Dz5R:f9AP#p 8ck_T:w],-gWr `-?ĿYL,@i*Iꁳ{>6;}r5"风ƪd)TW>ˬRkf  ޛ{7?Ή>=='\]Da.W^'ܗ{{\ET$RpTTj{ly.%?bO^㭽u ߜx\ b{ ?o[:1X! /ns//YuH(%e#M@0PŶkd/aY w }oe&s]#0뽊1 `4'0}xZPps!M bhkfwK,V-ΎpDк,ج nش.SX>\`[U{$)rFxk-i?h-{m|{co4_cMm<׃gpbrRbr2az9!\nˬIW@=!{_gܟG>{qh?g؞(8m6=^8wӎp47kt~R+ u{[ܼtv`0R-{*@D 7jY>\E:`SfD &FF4R{A[`+Q" e/EK _ ]gjoQ){?J"?6K Yh l%mA$x;լO7P<.Q.,/ȵ6[Iد4)f({j:*TFqE b-X~!`Մ[e`y=CP6FkFf͵K()A@]L̎⚳I0j.7sQ$=EtaSs/3Hӯ֛|3`$U3F1|YTZeAs⿙D~ L?МkL|WTqeXŸlc2A0)6%wD A@(k\>g@r\z5wt_$>g`T>[h3 ocxMj: OFi g*mAsoC;}׾eڿTO $[39l-ևֽLykJSeR q[j2cOkU?2olcms?xc ]l*]5̗˺paA[6}]p%O{W$*JZS闉HA}*|O+_-\W1Ь5#6|rd \Mz@SwhO) \=iiWd9'YO4)d2t^/r[]:?s.XX>GQWdSN.kPPG XJ&́Klg=oC^1P b0{T!t}w>!sAk_D-Pj/i3k|h231ӅWTWtީg-@q}%N3Gkn`~ѤЇXs\d-2"Gy/ /']+hchZkWoc+f 2 |0'TQt{m5j'N^%+I<8jXsGl:\~79!na=nǚ%|银q0X2`0;$%<[EЯM {!ls9Qf;~#p@Vu{ٙVJ/&NƬy|/-xG_#w@yG3&3=^YפW^)*_眇~ϸ gXs>At-@Mկ'/,)\ϓkq|0]W5pA'P2vY{g$Hr*X4+׬|¢kO|(AG38 5;DJ0EUӟS [@{F@d)IW]KfڼSw tKvۄuskN]+05Av9iUfe#@*au< ˬdT@H aZ-$5K&:hƃ9v}{m[X٧-zo+0M uEc HۑTkB%)Q2WȌ1eʮ& R8)׀d!{s4/?vX[j?c6(ֲuOO cnC讠2&`0 %2eߞ_ڿok_+vuxB˛5GK6ڰ^0-K`JVsl͸chmxls9m̑~uo\{$?(}J9CS[a;NkݟŸq9Ot4{(+=U\2WX.ʛ$޲H?JFm<:~-DEAMaJHL[7:zW,:b p }ϵR|K!Lg‘>Q?au&<{@ _aI.A~-r*Yh_]' ;_V혎>^MX"t/׸7 n,B=J NIzU`#O oNE?ܑ3dAɔ g @wu_Nf(r{a MeJ3Ur(fTI&$k|֖o]>cs*#+ΣTЦf  <67f,E0;(L&UM1vX)H3R;ImfzG읒TzI_PiL4P_v%ͽvk0 0([hP2#)ns&i@"MAT;@C-v]ƛ@Z(,E,XOB;Ng`f3R._y)K}G[,ʗk@}<.l޳!ipO,s/cLEm1N1X P? u^bW-- /ʽ03?2Xam<\iIrHvY2.NuŸ~:y4v3U{٧Asw)xbHd[܏c* ‹ CIw/an#`v1! r@lteb$&`)`1"(xORҢƒ̌\JuXmeB7L>\<=xA8F u?7IW(%6[ ȑlK"|}_n俊F/뱅ٴ D2}'4vU -1F ,aȒ`z_F a󳶣|:ᓇ/?8 EG'_W?'1DQ?0;FYsRLp4'zNXt;D:nm)/Tc{ x+;-1>_ܠ-ڼW)5f  |\OZA5tY5` \T 0!%ڼ|8*Rm^́P[{I ˧I[ jʿ J򃣘**0来zCLI c Kp¨sz5kc7 F}{iK [3 rkR]R2O__mFxƣdezam5cyؿZUgm0qXGC&_R0`-1(DQ()p@`N VؓZ$Pf (ـ{.3\ui9zf[42܍"ԕecl:γ˽ϵd𫯁J׾}WkaLvx,%}|65f׌#E^>i6,Aov\ .֖Eh> `7-VT>}mp5.ן+z"Kt $_)_ j<66w 3^R ϋz1w<)?4tIAU]@ FMe @_\u\om<"8/Jlh `m@Gzd= f@x{#8[f0x| P_5fͽI\MXM-+Z`ĭ/D6() Cn .r脻I&1{Rج!gC?B #o8@k )6;>!uJwW=ok_Ok;n]׿<208akӶ϶<ϰ)wYGnf2*Xf@ ((02 {&fHֲ&r-S>rfg 5S͵=M^}`=xk+:ާz?a!3^ss <_xg꡶}^nҖ߲@K1p199o>qȝк۔cp"goQL%JpYU,UKQ>4+ .@׿[@pSa,tY^{͵ZR~n9dy~)`Iz'oZPe. e#r4j9.Mܯ,Z֞AW×[OQ'&m( șe0]SLILt  ;I!=`['2nR>k7OJF?VN.f,G ?0Cb]6sլ?VP^zmqy@gsYڬmэf&؅89%A um"FiAL r>7pwQ4mB4jn:<}p?f6+`m}v~@0QQ}jG90 Izɪ=ok_?kQOg S8SG{ 1z?ӏŲ= wğ!x0&!E5`7RFLvp䁡K%6pnDÒ-JU{PM>F~@ϡ_~X:E@>~ @M?/?C_fhd!LJ3 ,o~xHJF o}tt]Ty} iKVhDA |TA`ϟR' +PJ+7[Ltx% xӡ X`׋g űv Y\?f%(.+\~ |8)ޯ3f@@poSԗ?̳hK 4%7YMZ(G+R 't$K936@}YuYSTHT%0s< |Ǩ nvwu^,v9$^@Y"& 4@L";5@ܓY~o KT#_,rMr`1| mñFo~^f22p5:j@kJ4q"=nл;&3xɿз'׾ok_JHkבkgF~mp ?p > SO8ܙ&m` f;ؠHhHXUɛ!$_-n5f#fW?dZ=ΥUӟs$V?c}z+?ϩW "8ιQs@W @ ؛K>ǟ=Pz#,|ieHyH27ilDBfDYZrFZxgՃpv`p=wg 7̺Wj~ p5v©` ){*h8 | kUb҃r Kc:*`{Xpi~/K\۠|oExޢB\}yE|l3S(?sps-- b}v⾃9O5$WZsSA-/dZw_/߈ &ޯ_;^BVF99fa =ϳ8cn~Wݸy@ѦMߞ_̾_2vg$N qNo!?8Gk1cZ[A%| `G!.0@ j%а5 ``Sa9>$ 9]{КѷO!'^k?:b=t_?Xksa\>Y^y?׭Y̫|sfToIXs'tu̔a{30@7Ӥ#ש %*+sAO:(2}!*kV@iq[=zSwTGcۖ9ve3QMYbr. 4Ȟq;edDr%Gc?< Z+AXsqGo~G9 ̎x~ǫƬCsҜ>S>z./g|H,>Fkwl9#ʂSJн=i0 Yj>=Ȉ`b I޵ ؖ5>}o}31&=.։- +܎u ؗ< n nªӣ5]y@lf,[$B A4i]bK|ܫS}/v|x!#pb}Ӈyy-^qg\UKd#uۇ=Os@Z3F KBlhakg v? +i)-sOt .q ,M+xEsd(]*e%<4< V~-Infኺw}^=M1wZ.RH6M* ,_&8msD00)ޚ(@dSda:4th'@Wx1UbX|o_joN{|w-Gzof~۽}6=X ܎>-}6{Jn)BI8l@֨/At @6bӸ|#fi} Z^2dPifF_`FC3~Ɲ1Кe@ z}޽NH{ A"}z|c>Sߝz`Vgk![s_fÂa_b_hW6 30sK3&E|*g4(#Bʯe/جTt`UH_W@- (AC NNK@ỔpFKQkQ#|F"6xH-]| צx1p/ef\kKu(׹CrTbfNp>?aST`z&Md7S=\[%9N:{ZwՖ{'z>'O"tiAޛ mM|[~[4Arvnq㡫|in:v}Ѝ4B$?1 Q;Uc@1{\Jp!}`NM  'Y>a!a]պ;3R|{wFy >} ps3cQ-vf &jLJφ~ $`S`1<@<_9?)KW+)SK5.'|_C»ǿ?x_<{֋VNjǃ! WEjSYEKo1hahp C,LYvb<8GtA+{G+>}wOq*4_5++X>}6@Nff9 & ujA6S=|qϋ+ޜ?7^}CTRbQApm2͠:~¼2翡ܳ=rfiyih)cS᠀) K)<%6`&+NXs}]I `A$$-9\$.KDsd*F36$]֏ڽ8s%=T~d_to+\;a@?ì7kt:wK` RlAӁjbV#Gx+AP)\ 9_,іE\ڕC~ڽKzwÐ~o|z|c>O?xN⅑0cZk/_b?q/k/v-a!DRƥa}"f7-`[Wڥ4]bMW0kk-w7`3Ks3,=0 z< ßaYcXSuGpY "v 52K6k1JbMғ&Pwt}  / HڧpP.y"Pſ=P`_{<_ohV< h'~)s}^yxUS_Dkex/\\}xgWT q2&j̸fEX%ymJ2BóAp_;-90&j$w䣔l@QdX `Ph[ P tB))/dC.`iڢp!ѥ>S{;[v9ۈ3 lsdq[w}nxϩ7ޛ}V$,?Zs!6HemnH00)gj <eI F $x@TZl\_`֒kuCZqgpwp\ }? '.k25gxcf@lO3 @/nZ` |oGi ~3(8@0!-11j.[ 4[^ݥ=il9-tOP_Ae\Jlm_ ˮt?dXo]sɖձBImFN4Aɩީw.}U O-@74"9E ~[`ך*`%nk~=mgy2W.pplOկx/s]gFm￑Cߦ KvYE.}E-怳db*p7^HS}5/iAEjSp"T&SO/uHu lBĈI1~1ŕMang6mmYӦP}}{GL .A׬1dN{k5m;f{f@3hI۟Y\Jg`&p;2{I 3E,X(3`\@ְ5#Ul .`P 'KBݧ'> |c/m Cڇ:4lЎX%  vP ,l> 9F8-x"|3%hc-/kƭ~~}N>c5ݿc=mڏiD@G%WfWscLZ`ݚ ƾ@ 8y;tvx@~ g?GCAjt>jK'Y,H>ÿC u]kzw_֯JS;άK#Ei7(+0L#?T\`5S?Tc"R0Z`ab˫9ڵ TGDD`Gd)h> 8)6? z=-TiJgl*Ypv>g?0pN-p?ZǓ(q0?oG`gG3f同ãsIjL1}`>əFE[?9TfB<^Joa m,)|~(3䟞'𱏽g=i,:~XYq_$K/72$YB9pӑ}=Y=W鳎?=+ݿ@ 9oCw tJ &pznr5wI~ _axсIZs[c0ѬFL/ :;41't54/)| )` 5`~nֺ}?P\(Ÿ{]ec| ދTㆽV&DK͐{y^z^Ә̄zkREjohe>ehȆgk Eұ 4DLo:`l;)s*Dpf%$-^~27FOS_r|^'/}'M؎:ԍ˲A$4|ZZex@?ר8su6? ciY<97] alR3Y0L 6=U2aes@ԼYF`+_ln@ȸ]JO֞=(ws>}O:@ d ZV@[~k'E)y`)Rb&69O Ae~tMÞ􎀲Aw!KAO1M>%>=yj;?uHo$:ycY]ߨ|.HM.A0Kq4ac 4 D N9tfFg) K3] pг`a#^ߗ|dK.- Z/ >O`hˉr_QQoZ48#BdtwE|$>gŽ# .xwWsy VC>15 {?o,Xc&z ƺB^gao^y`lƖLLߢ>\ P~ՈQE:HJˉ lIº?BhI2% +^2'_A$}z|c>Xz"=ޅ`#Yю ' ߧrs G{w-W`_`4j=(p[zu{?Wp$PRӕTWz + _@@V6,YA$ |OzpR8w6(0Atw. 3.??'<@I}p;)]hZdX/?/i(:abet ׽ J V^?;B"s9 NFu:7;_ |.(mfFۮ3N/,8mR&IemV}'  \SvI{J#& zp#Kp]k]9n A)'T&r8@6lEɽ*8[~|>PǑXƱ͐@p #<^Z}am); < a@Pz-)*oPqKf cp8\S~"x@)$PNC/&ܦX'o)/%:=Sh#|o0{!A1r`€+̂W9 zj5ÿWJjD-3ƫOHUn)UԈœIܗŲ B\v ,ڃOEA)ss. x= p*_HK=Ue-)}s`Mezo۽Dz002wp X@s,v+piكoS Zqͬq440 MUfſRg/ Wx$(MUKFB2|f$yHdޝ;# Z>}}S?/BG8pcA8qD^r_ˏO[c?.&w{'{> )EXˬ{xKO=}?  ܧ{W=mY{5Z OWާ`>'ؿGః-`gptA iW#@?ϝ-À1 k?S.I:vhVA^E'r qDuP!9``Oɨ7fV?椫4mSŻ2 7/ HꬼS)2NŲw**D bBQgeM`?@\\jgW՚/YbK _ƬuCs<߆oMwv yQi0;=45IL{&F:'֥],ĿӤQ2j29] ;-_yNɒiM)Ϲ#(`\ '> |c{{g@i!}@v8ShMP9?00dEOM5e5'KkEǩ:s{~~|g-A b 7,7@+ =GV@Z |ئlx0k`@+ť^#&ޤo{(%*J qjyD9? HQ]F2pǏ;4R1mÇ_efV\+esw\ 3oxnF;0<$N;Y"oq_G ޿n# wx:-,juwH_ |cO([dq"NuN>3լhD`@ancHd9;f\B//߇=<3_v{O)#Ak_ %3q-9_ @ e ;]%hZ[Wc= %|X +88`%cRdCm+0U` aTeYQ:^\`j7s{V& ZEX X<J UUCQJ^˳w^7 Mz2-ԒGC2@|]lۯs"6~$,d@+c@V4@|x_׿ X}꿇]} (Hn;{T_q0Fܣ]*4=;h6!a0Vs:3BR*tq\h<>mlYс{:n~nZҵܺ|inm-%FrZ4R(c.ף7F{}>&e6,OW_\gikNЩLsڀE 5 𶷽oE2n J:\6Xiv]!o궬SsgKItҶrζ |C}_ YirL.A2}&KOѿKKiϺկb*^锯_V$u[@@(`E 샷=@±a.x \&iuJcUf5qayDa{pM|Tڹ H䯆,O! \)5 b<({hy(Ҏi5 !@=S_STcz">Lr1tI02!@w,?Lm={޽f۴u4o֥҃̀&#][mDz[p>KnjEnޖ.w! SOr3Dj2JTȃ1}& Dd @`sz%E?a7v/a}.G '_b Y=: ;>?G[ݸ874tnX;Aϱoɓ ܄MtA]ڻ6?Q~ @>T?m HSgp ZDϔ=A' !v}HLJ1pTs.5,eqF 7y *G>$*P)UQLhZ {  ,QN^.]WσJZ_<c?Q] ?PJ@4% XC|i:ï8bkh]"r?a A4CI+SPt{g # '(iK2$ #ВsrH(Xe<ԏ3`Y=.pe.} KI[Vs_\~sn5cx6F$qbi"ƺ6QUŹ6 Lk?_2)Xa g?\ Ѓ׵{ǰqm/(IBo{;]'U-4Y%=;Vp[sc o\<As.apPd*Aq[?ſCf':5S7J3NCO矟ûfRo'Ka~:ytaY|/ ~7)P%T =jfjp5?wr)7HdYG?lc g4#ZM!ev|W *K@ _Ѯl%2Tx({]s Nj @BbЉosxKu񋣾|H;\i&@ )*~s__P?UV^Onz piC~N  b~ 'zV|uW,~{6ɱOp-(z<42fZ kmr_Ɛu\G8_2뇏=cit_H6x>2 ԳwiyGO-gZ.rv':-@8,L'D' ClH!S? @<܃asgѥgH>ʁ{|>x\<6JWݪ_u],Y?$e'X@k˜ Fgm7v|g C9ab n A`@4mT(\Jxԯ\cՋYphgn fר .-G.2yk v,pq^Hf><0qb&^->n!}z& Xފ"dR/jð$uO3O[s=9ߐg #^|;Y_ra52s7&XfmA ({Fe cc]?CG cYr6G`飞 }5YP d0-dEь50@Gn}wPP  dl[ pm]{ggl޵c8@ 4ҙqmJ{/d+Y$^?em>:`+-@ V67tyO']OIje U<Ǐ]aC{m2r%/wP.Pn;\7p\NW 9|f6kfI0z@ |r?5Y̏EHQt H:qO؆* @u1H ⟻_Xy9odL#>>0_\"<|812 1u  `:ŒRl%mkwX=ާT &m<ږ?{v=DǺ.wk֙n^~; m}}ƈ[mxWkMj.djAQ J1ܰ&L ȕ &DuKYZ/U28)4f 1SL'n|Gk}PiݷȎPhw5j&;Ⱦְ%r}icv }kƁ:5~$?Ǐϑ@Wwޛ10P?9b{)/_y/?R3`fSѽ.O9U(A?J@`ɥ 68uQEH1Q13P֜@2 0ԶW lOpD9\}!cx2V_O?w %<׺(/`*A_H\@dzNl^ mD֍ XKJ 6;5eJBSr|ow(r~ A& ͡:=hqo=.*g6=Xz.F<޼Qa`Kk$y=CZSJ] o d *jIGD KwI˳f]{pu%͑T'd@mj{o2j#7yڣ2HO!PfMm\`jMa}mao zw7M((8Mú}s~|"(c\[ù Y)}su~aZ"Cp@A'eq}ɯ_Lfga e@wYđ%v4>^4+oH.Tf7 :x뺺KE9yR)y=UӅt8~+kl|}|4%Nsv(~iPIkPy_|g{λ V _V^'09"|D)TYnٖ\@R29@15dA(6fk@ӱ  d8 _ Gj^_ m]'! r&A6Ivkcoo |+zG}K-l%khaKkL瀻im/kj0P*'>mH+$4JD8(ցa J@8f๾߹}Opr#7&ɿo';} Y$={jƦt3sl eܰ>ln:,03~5x į8'!CGGS{ܶfq~:j飜M? >I\M;jT4;s";gM,@R6 N (#X&sM"6 J@cB,u]/_OrO,=<du0 NX8BZOt >q\'3w"ɿ3y]YHLFV!#s.Fq:N)7f%ۓ})bSvU{:b'1`%@]cc=!N #=`krLd:=9*p?{ufC7Fo?|[ h1/p v,KB@ŵk#S{-Z2@԰-,09LpKVM ˷u;DTVVD{4ԞF:K;% C2$Z6{g_j6DX-0n{h~>4?(>$jyV~߸\M//S0ȵfO LCپAng?htvh4.9BXV5vo~_ǜ׾&w&C2q޸WGqҟ֟pw|ߜ[ kY|CC*_s5){p'5 jc)@hmh)7fdw.|*+;?ZeKLW; ^3ٳ'@8(꟨ ^WrB8O`Hr[d>|*W(g@@C ?hyo.إ( < уV~Og@!gw> ~}G`k_s0`]}?,h鯵Y{vL6vcіuqC4e Ǣa㱤 ~dGY]VȚ P`MI.Dk c_ރơ'Ln|tq_A7 ^\fWĉ]~@Y A)`-\a}GWR*ˤ;ZB&$i,W٧sF|B'lc؎~e]5^I_¡?ٳGV{&̜}?4OOwm6q)pPmUIGnS!?[Xo]n.x]ҿx%?_vM?uգR) A޷}@G NKYhEPX8&qխS}{$X6PbAG \Ѕ+$XQ_3EI;|Aukr09~E'^??/R__XxՈfDV=1c% 7B&9糩0M|L{J ʿ( p:&P0ALGH$،z[5[< Q?29okuulz'ܤ.P"THjM.#gQfkRȠF3!@S 3TdCnWfw@ܬg43DEeu%N"[s/`@1; o{/^)q0f0k؂0 e\AJjsgg`sƞ RGҿ0Sto $O_S:AE} `P! 'rX@Mb#Am ^0q@D='5X$BgD;JfU'~ɗ⿾g]K{=p\M(o䄒> g=H4\_Xuů?M/xI$5KC\k/MP)`&]-fCyrSS C(8`tP5x^} >}e<@9;/Hץ0Iht9I3h2׶6[G׾۲F-hP 26E(@i;k3% Jeu(q@Lf8NX`@ th{RNSf$2O.oYyGȹ#[[eil̟>ELST͓'KiA [Akx`o \^Wjg:-WSr> vQ'2!S6aL)~L (,es<OZUH&"Q'+?ӆqƊ?=,l7!]*KF֣:5"%tGYrKWL/Pޞ-:j`z{L5`w6hz҅)q޵hm*4%gkHZ4pxho #ϫx|?l8M?oh lSC'Ç`"'wwkAX/K 2FQpRXafY#Bt@Z`1Yf,:XƧjT2 &B`qۼ.# e-y k0oc6 A*USKYf'}*y<=3f^?.L\~AeZ[̬13|޳eo&~~2 y,A}\4~{┫kwύ'Hq[E!,E҃!Pkv 8O%O% u̓tZ,i*} `JV +F!:^]L^]G⿰w? NNM :L 9oS>TS nke[O' ʧhr?D2{@{\0Y~ oX,S+noY|utρG4Xp>>Gdr=M¿-~[-6{v%Wa@3Xh 4ԊXB$$=5@9cgsp@f^ʤua@0q=0vcn݉(Pj@?'}4(̦u浡u?~\?kv].bfm>j)J: $ܖ R!d܃u@Rw.N 7M*OqCԗ$RN P<;VrIZh/ .r`Tlu^z&T`ܟγү*zC|l76NR՝wU.@}MdaT"~mzg %[5؋9Tx* Vb մk1!9eB}K)p5PS䥌ƕ5yOTvT i8)ʮ ~sJYJǨ$ `W#sAz kyKYii=K~3k|PW8eM[d[x7ҚeWQ0ZHtqUO-7ˀ-ɖ_FC2|jYrRfy'y@\fZȑA9Z6O{|'|8*Fl _Ǻ?ĕ-3Yj"3ɪ6FT.&"Ws}>: F(ޘ[ͷB`wM0R"4R~`Sgl+`0@sQa`}D/ t|;zsϊ& ޿~܈.(h\/0tbrAGU%_,*kpYyq>#g9qqiW% tz!N8IZ p^Qy%~Î~' z>nBvۓCVh= F^Xܖ-ŀn6FB2Ԉ+t($h hX$lA"| Sǔ]`ϚBx|Oc^3,x9˥YO Xu?_/Aek#`7ljP%^BdQ}4{75Ru QpB3 )w  +VVft4|j<_ ${^F@~pMYZA"+38ھ E~@s]W RvV_esy6x\^َs;~&6gM+6\A0|! R9kz E7v/܇4y3XYo3z,v<"C4]>K>ܧπ~Fi$4b\GvimYxkB3 I8 .ȮGFQͻ  5&AڠǨ3\QΓ2  D{0w9U:-mXR'giù^f-ǿ.,Q2 F{ uaΜTIJN?H,K|5M boHMߟxNk$ѷEgQ.Ae$g;F= {cq.q a3e_= [8kG]viy eWV=m~廵]2~` vmpmW:p!賰?7VX`;qrL첻b6Fv&/1 ߉y Tb0?Y$(&%R<$ef vf.!/,#@68n8\m04Af.Ќejep+0 `݂QO'LQZ`55#% ,@ R<&ok |f"X$_eYmX%vk`0[ q|;Y@ َGЏ?*?#~54~{qA;:B@@*,I ښ euP!팳PvNiTc P! ;]A2OUDZ7TMl|038HffGnC0gPY8ee0 ,2S𱿣}X[tv fF֮g B?0 L02'D2qЬt QF4d {Vd/evXpqw-t8 u1; 3撉F6o^s4OYk.?w^Ld Xқ ž?R󾝘&~nwЛwtRk!@c0V~˵A*y]w2B" Z:Viی?[Tٖ(5%uXſ?u? ?w@p ,oH;`f|&8}r h8 'K&*ЫǍ!w{77z5˄O.ݡ$]n@7g@1HLAhʄs]?`"M`d[8k-ra3b4`_P;B9uaܨO4Ms/'kPBUB>h_*'f7QY+R5t"w}h2*Y+&1W`p..8G6d`o~ߑ @Z]9 /I=r#= d[w{wLnr[-t'ZWEd6"u:XxAA2M&h>X' heH)#Kr,ZM AaQ4?>[ů鵪;*Ab 5s?puؿ@Qb.ؒxxA0AR&da\Vm c88fO|=@/*^WkÎ$N$0PpdS": 'Scrʝ$6 `` |MN'(B*Pyq\V,wr?f?Yu=;/8_߆AXdm<8MY@?qHsc F;M?9#)/,\}uM? H+Ld<63J.O~ü߻~o۸݂P_0@4BԄf"(o+X8AB"Xo2rhK(`͖՞d ,@F:P3r*@{X za묮:L?>ۍ~A$ُf#渿/c?hthmL_Qw f57w{X*8b8`7$8 dR)o0_1dDe0y+!?NKt:@h %&)U2*|TZ#2gz v| יPe]7wy,z_s\Zƽ鲬ɓ 95/T؛ s#وAn;jc ] | rO:+`0o"6T`<ŀO`TAIsRДh㫥{>‰}~EYW|} ,}Yn`̯qƅ}f l`&Yv\%wiY ]egwXSɽP^OG7\bx,ې${o2'I"J0dYk$L>y.KMgYof!m֊@L 0S4 ƹ6lE.bU';5,c 'FF=ydo1O$)Ǭٺ:L[@~v{6 ?xB $?5-PGs(.YD }C~+8x;Xkc<^$@ GawtRyr^&s__ Hc`_ @PTu]]wx*"m'&44ƒ1?pX!U%4гD}~ ݖbf73,xnwnjC;;H Hz ݅|:K 0ͱh ׅZNzw{_vuwwo7LB)=g>#?OOc6ɚ'Hs}Xov%G=eTc`p Y= TX6 Z} lM&HX6oc9iz^ŝ~s76#Q\Ky|i@ GIt$A +h2sA!0XK{@"PryV.yxQ|{e/~uvJgz,wsI:IW+K,wQa M;VR. T}B~\DzR&7(1zp6f}ٹ)K=:J+w^8׾  )K[w2g۽E(hKfvlzpwv62``3Қ0$i kʢHg9J76g@0"L jw% .|kcݮ~y}ij{d)&7_>'6Y{b6T,?r^: 2up 4kF#xMAg jmZ۽׳&vvytܥ ;\_ |_o0^jAvf`\Emk!*9RK oDoZo#/Vk/:NP}=@*`P|#%As~`@0tN|5T( [^)#nG׼ރ\&I,ՠA?/уuDip \}'Qj5++ Po{?1b}g`tzi>=p߹nv<*  Xq{ ={f,H\;fe!B3rAAKb /`U |z w{wn]}>r>b/BmF kq eL_XloKKϭ?,E6E& 5hdJ -z__x;A`-_߄7kOq/\.\o8 PD!`a FێYDd݋l!%k2#V~ Ka{g;f؋Fy?R΂񯨈֟8BO$U>}PD&]+=|nڗB87ȇޗ >Yt4ݡ.L8 +o|L&˹g 1\4rrSwR Mx NqPZѧ`av9}X^݃li.m!nwNl1ҲPҾ1&s&ЈiSuK$HA[ղwrm}ؿs__K-/K3w5c{~+u"^??OcVc]v_y_\qܤ6Zaq +:@&91g>480,~ud,7މ0wGwsߠ[/ldKTML | ȐJu'=sU˚~t{/<}yT~ U33OWOa=fZǵ! @6c"@u1c"twSnt8jjk>fݾs? 'W+2X6J1mnLmQ2̱]O pin(=R-X|stc_g\\eQn9sq?0\ߐ׀gJOEGɬL^#oۮ%rI cOWpb?wߺ`p# |>pH-O'u>sP3CӝOףY}5 "XevbvNV[" `K ն>4g\g| o4$@#|$Ts+T72 #| h3D7Yo&E[ `\dzfk|;5# rH/])y7?p%O4~:V0h2!S&SK_ 0L9' j6<|yK%[uB;׾ҝ@@$vgq(`@~64Q' ,i m8HCF>Q"y iX/jߡISijh<wOzz,U&Mb/r\ʀd>NGiIW~ZbZOLϋ.dAM)7NtlWTQ/?"}f\OMf @$wҌf[N){*0+T>Lyyl Ze%PTwz74fa!Y:fYc>M=xf4)gz=>'c%4"P4?()2 W7%Xܛykn%5kv8{o@f @ Y3t ߮ Il%-0/}n&96) ŊrӅLH}i ɿ31< Hb +>+@'A@{ \K6 G!xNē=YQW!@R],VOcugO/79/.<çR_NRCT^jp:pv ί88V10{ `F`.7aI;k w]4}_|i41vޝ!#vD5dp_?-=46zߛ͛ZKͅ3`]jϤ0Wu~&I&L} h-9zõێһ r3Z 3:V}^ox۟fĵ1qzƍHF{ б ͂h-`Fەq|-Ia@>UsgB~FjZrU=L8qG qzO񯎁m-g`001bAz`0>ArFT^2tݫ[K4׬l'q'Wܵ[{ÿ-Q p E|[϶M\G@ 7%GXE"8T#遶c0b1lm` sڴ828ЭpASx۟fz5b+'c1K)|ca#- Ll^6جMKQtېj2@h1|̻.!>c`ҌS!] _*Wa,_2& Pd\hLH"avPFb@ILw+>w(ca(beYQmN%A<xw/^xaNoꥮ/^?<hj|T;ꟕoLSky~ZF5_L %p&g: :a7щy2Mf{$/& T/Ôm"s5sڨ݂޷L֦wU_h@s`Lk@i Rp2.~P0տ9df)8ğ̛' Ix<ڿ~mo{k~nghƒ 66 "(߰!A p>؅? #8h~[bM_z~ܗ?v͹{6cꤗjAR*~,0 m@-g@ݴ3c?MU nx|^_ҍ4ք] NDZNdQ,̦7gP Ү|QYٱoY}yaB'.g}*w j8_8mZF8TGipv&4!'=5 TJOM E/ݬzi,k|@!3Q[ ,Y{_fViCLXlB3N-0Q)vyAjYPg8U۠cV'v;huqUrG^V ?ww'Z 68| D[d~c,6m|lr q3̎ fAd,+@/6~_ y.+Q?mL^66%M7=Xe@)e%cxDO΀3! |>Q>u I5p;`508dXK dH?$'8+wy5?/e]4ԋ?|%Hhi??'K`D)mב)[~!U1: gpz^̼9D% D-Yw\ g8mB4C7D;L.8EOH*s^~-(jz(FںjQk78n~?~q[c ޵КUsw-Ff +q  w|D?Q>q5D#23%0l%E.sw[_lot7(5E4q'499HJ9`Bg9k%j@0jx}bG5T򝏯SŪjUl|-|_B;A,8s-uQt2x~.q׿s`aM?M_:|+Iu∧?8 b{#+% "߈{<`V !϶}iC/cp|e\!\ ${J 8Dv(tw4Pd 6ſ@deB&X#$ گ˗+p};E~w{o3VNfzRS8Mf3 r9BD3`c c5A4B/ V,[э5߅6 EMB絉oz_aX0+7XUk ! {-#ܝ{4߅?] U֑NʞFxAy6+R HQ5, ƞןy-~x[ebVW|1tUkf|: Y5qeğv&: Q;Z& M<-~ uQTi(%Hϑ7 H9*ku;+y1v<Q{7v,_S3o@_ϱ ;hk)G074M15tAe6~L@`2YBt;$9xwmoӌΏK>fL'N`X93o1gpZ>-, &i:g,6V H}$qP/~>k8S[\`fe!jPpch@3$TuSy0]tb &! jz שf>؟=8V=n4/BǯD0 Ů$wK^>}l|Aᅜ]tPK~8/w&=§iqf }b=.ST[M0(>K#M '8@@A=Ю~+Æ8'pC6w{_ͬ/o1 ߡ7ki VH Ff[?^V2 E YsI@W @ɢx/ax۟j|k?E#Uu:zuI;ѸSzdHɲO_Қf,[omd,Kpnq6p~[z[73BaN`kCʀj9/d͆,VD"s}Zvk[k\H>nh4pGOV&RIzCl.pvp L/@n`I,wdcX'sVcӬT}v~5O/k(VO,DRu&ȵ]ɥ qтWrMߡ 8F:55ҎutDC#,%>Sg<0ſAaPvGY2Nftp_+yM X\rkĕZn䘋~|Fouo7k}ك2L@e #ݘvw97.H9xL0*92|`w\ zG?i\_fM/*?R YZ7HBugfM"#mi4#h.k ;`6G؏_?kw$8I $( ?R|%Mɺr+jG e m m1hu3q KpսH5J@g pNey=@9M \8] W⋯Mڵ.}v˟{;vtoy<Lek:v|GT(RV~x?(?;%8PdlX7.p9 Iձ_8w7~l5~qya{T!zub ~qk3 h9d|@MiL>%~=Ara׳F\&{XdC@} :)Kq䣷Kx۟h,7آ]$ɂ?xP٠y 7B㶝ql#RATsd _ [,np|GmۏwwPfM@ޱw:&9pYw~@r A%esgڿÃo{&ykDP_;&z@mVS0Gϧ(l Y8AY«vO@:uB饕r(?gEY}}"l(U?} :A_2ׯf ws yeKA@?r z)]lqUv)/)ap.Q~# !*aX7(aշk_s]HDq< ZўݘrvH~*Qx|Әx^3!D%^]3oa.8!(Ys!5m:͌nh;l@G-IQ)EףZlBFbL[1KMkܤ}ߩmt@r]G@|5_ΠyNz6믘'.udzܻ7qqn2`(5 ֥imG&@GG߶\2tyArl}wC.3 ';٫ݡo2^&ke~ ڀ ѓaVI{Dt226vXg?P^p< 4,#'();Oz% 닔r'u,A愱חH|tw.Q_t«穏@~] k+) TDH\ʑj *8WA55UZ5kc}ۯ>aO_L_Bo٬w,5qNm0MFj[yRE#~囉6%kv)~=x߂C hG@8Io{;𶷽- V< v]>EAA\ :zpLp[IM)Cl2؃7}?ƗPlehRD>}}b ;[~*6dJ=έ ˾XAj:(:|Pz2Shf\Z㺴Y?mXj`jMX㝤5 5azQ8> p̓ Uի+^xP|xn:,Iڙ_;V3]DfqHvL~N&[ 'zf2`}O ۂCxp~\ulH_o/wmooOūms@Fǥ\P2A(ʟߩg@D)ႜd'q>3zT]&エ(`@mrh 6sQ"`9s2/eKq/ALl2} (%)`9.@23=39Ō!mc} Xn &Y,2A6ASbYKP׳sQEN_fP@5pyd0Z S) 0K ,Wi#4&xYj?9_m'/%7TJZׯr;rpf@zN3|M_q,@5G_2J2BA44y *8ҥ3u?c*:tdxpA˩b_U>2y,'+zR?H֯H[ads[ b >>%'%_8t@#P.?2wACyzx2@[m{b? Niq×U[iCBNڋs;X)mo_l޻iD.hj˲r]x"gk\>X'xFĜİfGKscLaG/Rn3+F_T)NJ2n$.Z̰n-`ƛ5~ه>a2e[ۂo fP ?j<j"MD O@,krgH~Ǵr`gpt yH"Z({.7.۹P=^ ˉZxKkP3x@wÀ88OY#8|f/]Q{pݻw^ Nq/7~ݍ?[Ku1;S ,NTE[y1yUӗERQz5\r@Z[SBg=}O?p { 8hp`w4M׷Exb 0 @O~Ms0r i6d 0n`6,ؖۺ!PŚزhT@ P²B 4ٜWUߐb I fsGpO\nvNh!ҸZpQ&n\lf]7DQ TdRT9:3_ϒkJ C?yuNISB,T#HW$Ԡԛk)C++F:oazU\G ̹䣊/JLug vO^m$i{Y;9F|L$_)$`X D9/}`d6g+|g5#p#?o8>4f@ ,PX& q%Xp@l4W2rp7 -E/ҩ_j!ۅΐVElt4l*2 @39s31Y"H}U ]+;vP2*.q^KOs ν=^R^&c$8_|Y+?zi2p' Z9>י55A+FJZ x_̣ \͊Trw~c]]~X8jow=^ 0\ LoO|Z&`h aF# S"lҚ֥/0oug@EDrE ɬTXV-bOϭc"y:gA;7gL!8aL>.ՎRDc<(v&t"/*h ͿjdlW[eJ'YW ~Z1[uZdzU}up@ Y>vvdvяQiU1SH=Vܬ (]y &_Y@7a:LXcW:u at,at۶!S9@_°2ATQ}{:NwWo찪G L?mOsi &9MYCyOqA4šd ɴ<l=-,4^>c XuTz0njn{RZ}[}1}Y=:adpoݛ[I2hǺ;nbF Ʈf e66#oNaks!͜PgRU4v2 ?S:/5Ets)<^U[..P@tr@.8g E'J *uPIW'OKT Ma_=x>PYg| ?ybO8M 0o##xۼmGxߏ&Sϴ$ZgmoM~}Uxض2}pkmk-{g'ecJP:v)ARXl M 6 Hƒ2M6ľ mֶO~%m,;nqzY̌`3Zk|_uIIU7Td i*F…t$NN;-ya H6`LX $6CیK'V6_?6c߻MIs\!HH8}D ЏHK;\49 ^۫2 ?͖;Շ+ <-\^LIPoZ'R''.S\+sn\UDI`&~yqpM?8

7*h v۔;I̳ ?۲[yg0!;fGZ0<8. T,A-Dm66Y'i`ﲥm/b6[V 0N?FĿSe*R6~Qǹ(¾!\ԧtǙA28xyN> @f ɿD@dYg|d.$@ ո3! tZ_ڧ-X M33L%"epMxS沛~}\vcO~y:lUH-IS~:)4G?hm}.hax ) .kq$aRDR;3<68, ZWb5 v[qy$L3[ P;k_О]̩&AXٵb[۶eegkI#Z1bFp2ReR5=1Q%MՔTj-[X:ƿ,I e]^՗. wշ|%_Oċ\'(S|*~5t>#@Fb+OLD`xjSV@ v>e/ӝ:TRsy ||iXk3*Igd g;G:=g3]i 8,ݝs"\-b32X/3E ?Gc %Ђ-xn 4k3R)JevK̺©? ߷9os'߃8<0L m󟑷x? RʜLͿ:Ƒ\$%zWw,&b̄ã;mB$)Y~H<#bӿm3lfA046BdA4YѨ s rv#z`2MN ߂[n܏@?l{nϲEO۶O(;{Dtu<7sz8-H\Lt`|[@v3C R+8yzVIbf6?m 4{ ~kƻ$oJL2|ljC~^ضZ͛orlN4 FLdB}E0f*z=`L{w|&;jX~ܜ{pN]Or2 ?OV_w*رD/aRx^r͹ ^qW}kwj@r d:/3`9@^_蘎b%?7 `q%Y{|j:œN%'_SGŻ~R_%8 YIo͂oY7n8J8uH3k1x}~kpHonw@(Q|j R* ?O;!)yÇ~q&#~@H,t0hmMF=m`? 8S.1 #4iԹX#mYQQ&f 9eT%4J!*]Ӏ1@gK2b*k3beKT7M:CuQX]q_\ ruk_1W]ob9A(``M>}8((tp,)Zqrk'; _׊|2~ ͝rf |X ,- X~ jǦߚkE[dE-mm@8`dL#Tp&A9͢r?3$Kr@).e |? ?կ Vw_ h5{06ee;ɄD+1zci`+YHJ  ` 8ǹݬﰾhneh~{t?у^G\k,kLi.ۗT1!Bv?ſ{: >o mNЀu#5N}rrE )mz ZZP3cىa@o0 e@x <0)铦 Yhz#ng<'b!t^q)p);c>&L?{wEF^_Ƌ \3^^3?dJ? `Gc*\|״z9!z9P:>t *\xa^CqIȥE<ػjQvQO^a"\ye;ӜfS/|J0iJH4m{ 3hUJ&-~(21JYP.(0MDSpmg5n!ooF>nG9}'`baJkMe j柙)'w  3GEHxRɕSY3^ߴY}Wea/V =YC&2:LbA7[Ck$"HM/@x2u ǂRmq0w>1}mmBzt~gP>Sniu悓RFSQ]p 9 g`pf 9``h6H77@Ӏ 򛤁 Z̙"@+@مi0LL@Ld*t@> $Й8QD$sscexvOR-S05Ok}ǫ tq֬}A @y/lYE`S䗬<" J1s8,6z9 3@~Kj {fk#)`͂gbm5 ?6oox}81 g cig*HP=CGs˵k> Yɕa4 mf AP&S` `]z VCN7uO<-]Is5 WHS}<"W,Q'P|U,˂~qH̺D (k.ݻ/8YCQgiZpJcW&]+emƟdtIvI\H Kkn7q3 ?u5o50d]SӸf rQ& C>ꞆV3)RAoW.}z:x ޺ߺw)/cP@#a$s]gy o*j 3ؿWW H\Y;+/AdKaR=hr&~sku 6ot5eYD %8Yn77) 2Qd62=@ EW; *rF1AF>PgF@mU>YM.uǰ"૱ ^t;Rkf<.&2⚁hDu8λɗ@A65g@z2jc dYH`,@}By1*  }O3wK倓t1gB ]H䷄Em(լ\_qS3$E]Ril6j)~!wt!ۨMP֬e@m3QSo`h`+%f 0oq ?>? 2$3?!ID)3]].ý? sD3=aǝR%(\̯]/;Wi>T}0@=0 66|sk02TP:mk-ކ-'66Q2L{EY#`4Oi qSpL֣X@[:ݵܕ?8%z5r"$db 􌑱O^({z[/H* W?GZ[3@/|&oY?߅?}`[ymf@g:@pQs *8E?3Q>*% 6cKߌk_ߦo-xV@8v[K٠p`{'Ga`4,X!OmjҏNk|5 )OnR\r|}ڛwA%Ǎ&º*O"5#JZJZ S+.y| K2u0L&K''H`M';$xYLZ|F<D*Y?D%oG!aY 9kѬz%}uߪ]>̿SAkAe q6d1h˂%oAX\g64͏st<Rͫt9p5?SSLȴ?#hA5/wD!'00 /e_K+eͦ~Q"3|k Hz^%YxKXj9bn3s`wp<8lo{Z:{?~ #00.,e@ 'A/}b: BPj POA$rs8伣 o=,=6[ޚoֻ+[`$Msh&ؔ )c'{gV+' Mָ \0 H4ukpsM<0/{p} AȠ0ؽ@@Ɓ@fLl 96C<6Ŀuc띭Yv]V  wNRTf43?֜ݕo|E癇O)L$/Ro }֒*8o|δ0pQwQ%& ?wL .7@նu \Wi"P\;}@7LJC.MιU_lPRxA/f%1U}?B$I| %t@ s& m ?쮁Y.A/Iw$hsI iU32b!߾CSmn!F[ 6Nc7jTՙ޵֣'1H|@ȧd  f~=lU٧P?30Ex}= cj^Sƿ1]b_tW~]˾_eA?uQ5sp]>l nҬa9G>~o{ھm[[ lXӂ6R [\&Z3[p͐xtγ[IR.(qhL}bh 9_LHc2ސ@if5PaL;Qgf/@>ſTS ȼdFY771Np!0aپhI`{f&sto)mL_SÐVk=|Q[םW_k+bX~5D$u:/*sٽWq,_&/H>qmxm&k"ߕ~akJO߫s`U?tTBu_'8uZu,‹|<#\*5A#05^ܙW ACK_>\:RCJlT/uW^s>z+0z\Ի~v[;m.7` @ϲ\?Սrd; 4TC)hE N oV8"2 g %T3?E?;)5 @*ݣ5?DM;֍f |WC>%>$J8eMo榾ѻob낙l(=P|};IJ/+9/uD!/jh53 A$йϒm 9=q@,S0JʾJ6^eLK`V^|T@w^/v˼n{T PRmȗ/r 5\M`%!S A)8Nf@KB.<}_cޏt>.]A2^O~ŞMTM&q֎gSL_QB?F@ov|f7.Obs x 2#GxV ǣG,;·ŃĹvGvq vtZk0k=C "T8m P`ݖU \c[u"o24{-K<_x@B~gNϔO8S2:rp NлQ]#i$;2XpJn>T쾾'Qtg=-f; &.v"LLM#Xcac1oVDtWHxhpuw_vE.Y# 4d`Abn^p lNרk6^[恵68Gr ;]qKpMH-}pWɽd;ŭ[+׺tY׀ʙo3^K\!1S3rHgj |'P8K/gE=^U\\Nގ{U?pZ&j6~kci-@H?K?F8'c"bm ؀AApDK독EoDfp{t܂A43kŨ-U s' _+1X3+]9탇oZ'߿VH!z,o_sd_/;l*@&կ-;Б#pfiggy} G 8:Z[fȁgYjbb94|yN%Qs1d c-vb^f h FzBpq rο}3zϵ("מ2a)urͽSvC13h5/dS9`3=@%"e@?}˙$~~+(ɫ~ƛ^*pJFA~>~<ę,\=b"2~Ayl);]d*.S>ɪSP%y-&ތ@;t|[ף= S /w\|Z$j?RX%AgI^, cpL`0ӣOežj˕gEʝwc<5mH>{䂔ǃ&S;a6gg,z50mjWal;6H{,`c$\\ @ taܙL ZsTvB{~jfo6V~zק?W?o׭R}}^ \;.c߽8>c:T |CCG0㳟njav|qAj!8y{ 8hca qĉiqWӁ)mhA2pmr~#+ S%qyiue K&m A2@6-h9dm\@WG EJ/dwe {p;hm%Bܨ 8rk$3?N ԙi\x]\LL bXkwGX=NE{Z.W2 `k';u\3ͥ ˈAvOKܛ] }@/3 ҭ7Rg̊{ ʎK@Y4G)䌂]eM{tt @8\\3)iy瞪K5+{R )nb\MbfkA\HDG#w;.?fl?oe`eI #Y-5+.0W\g4|QMu8e N0gUWKU^4'@~Y[o H~Vo?0;.4w]>lXnZokU+edm͟CI$A %]ѻ }|l ?qx|ıCm8[erU3ps0֋WA -ן?]ҟri$Z1ův<t*ej_e#@.y - ,p0''²` 6/HVt)v'ڱFf;Av>?ooۮ2{nB>wa]4..Sϟc Q>|:WI]V?s9Cߟ_??Y׆纙JR߯Gn_wSeo}]o_A=;!TN6-WC~v??cӆb,v|LĥL?8ptX8l7B3/ͤMAM:>Bd&k+KMf >:$RE &@f66;,YAHlaIP';p tƚL ld8Xmez2 | qdӈ+Jq69M yE sƛͨɦ*Дw_/^ C)D*DMn_W uu}m_ckA]* e@1juJ} XiAõ[ҫ5{uC@g@9 x /lKDݲktP/h*aZ@2 MIwfVQ͎e5f}ɓ?ubf+@] +X5ϹSg/K9d<~gj qz$;v#LX]?fצL u,m0eJmS ЂA1 G=f\?SO>LQ?p~߯9?D_,"`u!WfAMO —}f ]ε|Ϧ8:uwv~ε8Fsh8qb>w1J2{;3imî>ⳕ> e[[ΔC֥cŷ}Ibf1۲ڮd! 5:Dp,,{A@h%v_HCw*7*qSj+g6+ 23_v ~n|\} Q`Xs?_>x}r~"e3q}Zejvd&/xy.!_?!ĵ%Xε^\rƚk͜$-,ŝ 's}G`~YC'Ve5h_/s\|ǛM\׍ (_@ۓϸl֬vg۳+pt)mVt5UL:"2_J>{ 布Nr1j%,4z3}(`m?ةR։u<#;ZfON\U3+Xt7hkՒZP %u>mf(&,xoĥ\^zVIF8/Y05;Ҁ &>+fG  B9;z!Y9M/pNh+|_m_ecNj@椹-90.#y?;rd%[ū!$6ѿj]oy 8V~C2JR)30pʻs`JϺ)Y(%2)K +uk*\ױnU 5x=Ek$Iݥ*ϸ9&d?G:?F`k%Y痔{'}`zQ_eAhՄȨ$ҺSMa.s߆4ڸv=%[Q+8'H<ͬe>m`[f&[d6e*iJF಩g~[D0a68εи&K,ܲ=be 2fC)BM2-aN|Dup;܀QWykd0 a+%Žl% G3b0(=(B ^"!*)Ux^I(ŧ@C@װ@mEMo$jNsھ[So;+ւ=L_aK,ך[3[yb// ^ 3ݏ! B^s zǍz?۩YP:ߥ'.^A HH36Hs9&!u:]~* ) o%Ga? ͸~5.m| ^iu_ _\A]Mւ[/o߶aP)?ʼnYjǼ_%C/?`njn+e=jQBHVg2빋C+<܄"z%ijP (e Eo?ϠCu?%F$`H Z[{iI^cO5)F[p{ 5:% {Q]$뽏[k̺-E.g {MGca;`4CLOoL &k]e/Zgݐ-ۡq;~͔<GcInU`VKa3xd`Q.-Ge/#$~k!.21]k[I3ֆvw@ST~)v>WG &4A6R-gAb>95*~)5uˀ_m@,\-9D{N_>IԱ}ZX,J֡ORN'3jt yy+ qU_^8>ϥ=Q֎޿5 .aq!DI5 #J@Q*;ïATp `&TCzpϝ~Gx?yO 8֎寗ƭ1R% ꄀbYy|,c=[rܤLѿv[i-aq`%3dZp0 FNCӻT\_ /"!HWZef\w[ʼyۃe)3?Q? ?~Y3RW. %'ӗ}n e~[՚|uZF4y{kݻΨǜN|=w[bDcNOž3`Z^ >SUp~G٫-36[VJp#_@?:]nlg7Ҝ,\ 9LkÚTOZn i7;)6<@'AV%,܂ ԾC x "5*qzߡ_鿇 D}ZPr?Mʴ22Wյo+?X6#]~1_p[ ™X@[I`(WWΕe h%(/)3yOt2tgy\ȻoS!9s%G M?Ynw z3q+)7Ayn m/WrH4 %biS֍n6K{3j[;N;bݖj_OP345h&dx2E79زa '?>7j(\S` inhluinHRE߱|j=.d HDGM ] kG].Yԣ/cjD&@ 80{މ2em e½TXJfQym[ `wsa.W r2`+213PJ-P-]['<$ {4:ۯ~ëyUH_[WM7Wd ~ӟ^uPʚU \r3 & Kz}H$K @ ]G8*&Ź q31r-;ioc ̑{?=X᧞K^zj0| jb;ΐޮ>z%c[|,#W13hOl@GF6a?R ɠ1k?6 uIQ;Լ3"*}Qw/~DF`_s瘿gSG~qUs| q*sUvL4_WM?Ho|c_C9 < l$݌[f[_hfg[7S dr.?@]Ld+E#R?P8AMZ+f 5)FC[1=;.Ӽ[w3hW*aLԭo/?9X@8ʰ>X!2P 635HBQ,_Zb 7ޘ!ZqL-3sSue (x&S&2c̍[)n;%LfeϦ|>s r)t\h;=p_6槴⁡,w)EJ2/n\kW ;Pۭ8RZ~)@\;~6d_81|(| 8JwcOW3pA8Se\j6AMUgXI* NI=pz_KkKR+Ϭ $sqZm˅-vrSȖcGp`҇e@cCg ` &`.1?Wpc 챻tzNJ30N1)`X w~ T5ZYߴ4R.% 5e)w)|Zn2JGP-m$9ȢdNob!.Nm+9B*Rn1xSÏs_̚f]Ǘ=LW?r*+/++=k1TPDF~9`E[7w3g}o5?\CAQk 6aـ_\|j3RBCø{moG `;CcwfDNJoPݺqY耸 rSZQ~YFdLL]ucp[B J_0lm7b: jm H3U b Ԍ{_ZlX)S=)X*b\f@< 8l.bh ĸ2(ps(֔/9}wV**Y|U?ٯ˽wŁ"UJ_dP_?C+PWflOf`#` X9"u.j|xw׾S{3ca :+FÒ|$K1I% mUơ+`@`/ym/0ɹ^=O3P& ُ.~iYv=#/C@??kgA( ZwKE_^M|)rT70l(%; ekya&a6 JozONM 44Ia sxS=84#Yt,6,(9D(|_, @`v#~@q_f̂'ҿ5e {^/xP}t5V_a$=R}Hsdp|,;gv|AT ^`Kկp<ŃidzyaTv)ɘ[)ƭͷ&.s:̌F0KEC@:' .dւ%ER_6<'@j/"/~bUMuZL<'LlGa\!+P\`MkuBSo6t6'XXD:^id@γIYc#1EQc섵7$z lCO@V`w&6۬Ϯ/of_H;5?j @鿜2oJ'ը 罎vVn `@faGH.G7ǘ~~9;.o[$?We/guV=,V3j6Iɩ:ie)뛐ĵ֝d@rMmXߊ)1SOf=F{ ?}m'fmֶi ms&K=:Fe%үZ^5hh)bSf76\m&&Ye24J څMO &qCk>H&(k@0 "D",+ R/>\˳迯u<O) ^?T8$b8l$b=XU^{<:Eœr[{i*N4|C T<>Rg@p$.z)SU'T'4pw/E֙4}(K,`k-G>v~߳ 5씿+ֶ'Yψy9 15uϕ-x nS9[:.c݅i\ma>g>^˕6ئoZP-Һ3Mxe+4-x 졻tlGKrz"kHK,fhՙ|v :1# [RɃ{s)?zߏD~3j52bfIW~J_^oƴ'YUpU/砀$= $3;Z3d{P;ǟbW) :L>yԶ5/| }MG c6N>ނ?m_i PKRjRAUUG0#к߂ՂL# @@S Aޠ4. m6{`)a Qug{S)P0lG&,:c0s,E@иP?oI &̕++ QuIiʚ]HXW!Jڡ.>vqůƫ%y'U gGv]} Xg-j7 vÖ:4 2/S ˌ$N^uMEds llh rRm_,0GTÀ%=Ev4RkP<+ִ#)N=ׂRdocd5k y(UyN%cc `VMRkTO$XJ $O^8f@/UBxI%6Kok)f Lm)n4VGw:IAևeEYo=#Cko[_wſ[w{+9 DP3NUm 1u}GLg6~./ `;@DQ_?υUПGuqc/5|S?5cB X̚{{ C_e5{^]i! 1V,:Bu_SG$:8O>刾>tZOJ+(@yA4f(3Pf{kmo[Ϙ# S i<@$4Jr7l;<@x ?4x PG@!]`5 tf>uS&:Z`=payQt"Ich^рLVV):08}. *1^D=`ŇNǤ:e{{ HK>$^'UN_|]*/A"`ASG(OTd?ASO ~tyڿl se{lpfpC9^>Zny޺JpCe)qT, ľȱKa#pn C؏k $wѿymc~lSwp /q|Ioo q u^"3_GF?i,qgg ׿Krk?t}}`w=G>Fo3FŜQsu?)/?kqQk]2WӿUx~Ʒ%?YE !՞a;[ }{򞛊O~wO;i@#qߌۏ{R ot:w}FW3j:Gʿ/lBeK@-H tu_c0 & =R禔![A&*,|"v" ~% u7 .y"DNWD%C0T|>Z;짎s\huuK)`ɵh=*qUȟ5\ PKy *a= 7+ T!>ɎgLM *c׵̽ϏX(3(s5+Ulm?*N_wWe/Z 9Ku ɚ' ^G&MxZeIeV{I30w{Cnk:I&K,J [i%Cm=4&ZcWS Ye :hY9Å;!V!,y(Le.)xCWь Pr0jm;J^ фФ}.8_yxK@"pfԟϙ:˽{?\>yTsfJ3W@Dk1H󇺦dX[`=@' c؍ c膱cWL!v;CLyZDA,C@k*mH?~Go>3_G.[`w[kW`4LG*&`fg|)r )f%d1sA#- ;&yYB"U'LwCOy:# /nphQ_k9@4 Dd7Q?jT?%_We/ OOڣ:{vWY/'_SO(&Jyh {uLkXYrC/:s3MĺH L&&L N/V֭Sxɲ@h3 @by."&]ΰ]3=L^J^!_*}ntysxsf||rCuꈒz"_;cQN /Z^^_B ~u!X~XU?撝okmH'>m*FP!p&%wvC$S~sǾN1 x-4])Kӿzϕ^?xaM ö- o -)Eb4nqY߱5rf 0.[#3JڀI4iH?YOk<6/L/ZW@7ϭ.χW.;~iBԀ?~1=?Y?j_ek{^?UQ~zϿi=n6X4f=%xJԬ(l֍ky0#| ɈOǶLu5v[#Нpw?*0e3%w(n9:ONHPϽ nFT; ^DG= gT &:,sUo8S=s-xi] ⳕ>@ ]57?SW:X87#$T`7LPR\UՎ<֒KNCIޥcaSC 鷒nC lmm-p[߂7[ Z\e? E`ܲ6&[ x{3ۺk^gM k6*Zp6d6n)g`rF-@L NSti,Y%"9E ~8pW ;Sg71/G{BϚ}?F9GG`+']93k_e{{^'D9CH^>. RWe=UT~L.O#|w[y8a7Xw[Sm|M Gf.* wQPÇv }~`|=8ZCPmm 9@@npűB,6*|Ӵ p7 m%(/u_4`OЪ )HP*i2\ŪU+a?9 #r3luʦNoA^Orr,+ /[=Z\NЙ\7vZ̵Ɠq,s T4Tſoo-OS7Kg>QR8yܥW~whC.;LX KZ=gKyξ̗ Md٪1%:vl-چ.Cw]'ۭZ}o Onm0@`> lͬT" |YMQDZ A%Z=/ edrCֳI,?װadi`Θ}V=%?>ToCˏ)k_|z/5{ؚw;^ e ǝ'd}Ӊ.a$x@t'Y> #h,hiLAt~oTG㩜3nH`M,C֗6a䯉j-Hal~ H9g#@:ƈ9v57dVn3:ipR;໰ߩf=R<Eypb.Y=pYWy{[By?^u}\csr? K'wt1gaq.):&1꟭)rf"򯎬˓a2q38?KcgڸST`'ΣڮCw ahv;w=N] HldQӤ/5N6qoޭu{]"wwBG`4ok3wf6͌!f[[G.| K,+e# ?(ذ& 3p>˸u'UУvi5&L;W^~qSI@+MOȻ_Qt{眿=G__`u5)spqϚ<j_e/{^?bi@^WE/p˱)לWM`)i,I,k_Sd Lmԭn"ϠrtVTP8n|.!A_^k\Lb}a;W3#0 >xI֨|1Q͏,0vdFߩ}?y'Qʓdwl~Yy,xw|8hǤ$=OO"s0sZ#m?[P_Jpʫ]3uCKY(_g_H8DkLk& ?x 4Y߉ W9Ŀ.ǝY.! N Ҽ;vף'`F6ILBڒA'3f|oR33sgYGí·oE` `#`G;s#D2`Z/oaG69S`Y[;U95̔ҙFy |zor(;Pȋ<| 5+'οfJ\qaL;2s j s5=/{ٿ-,'b Rt0T09y 66u+NlULg0Ě&:Y?I|Uo7.m-̀C \HïPi:A)ׇ}A~wM,X_ HN;^q{=PfaH(ѲLAᧆrV1䎷k$d|e%A_H3I2 oGg:*$v 0)bSQ Ɓo6`>|oyT3;(|壵Ices=kÐnՄ}C Q~MsI-P6 ABm{eYg`7͸?=.ϩkljlw\Gϰ]ycmo;AD@wsL] )t 6قo ɼN2%wIt-yH_Bpv~S?o{ԣdc) U?Q_;|Oe_kI%]5հFH8=8?YT J2PaOQ6PXMU[j5̫ɶ L$\Чq'9@,-Nok}ӷ5&wcq' {1BdYo[yuJJ?߃T0.t"gw%6́< U-W/W-$\8#Ex%wɆL:gg`;MftܫǞ`@JOI9LrshH@S}# q5.[j\ /`3FMc>Fv<&e'1ɌƄ!aQGXEP`vlst*\-FqkZSؐZ~Yړ,Ni G@by׋x1io!xZ֭??NlG<k,)+O:"1o?xvȊ5ܚᯙ/7?mx:M6 ~!a4>y+sNf@8>Z@b \LN4Yi@pN}-B$G 4.iW^r|g'|ސɃv fs woC0?z#t4@ tڱ@ACF$C;yB'm|RfJٓUʆ_Mn`@:ϟ&PV_.b lWLn⟷vfa/|u}4=b_)5Y9d))ca8l%`g@(8s?mf>tqǟ20s ~'UbӨca5/Ǫ/l(x# #x, $a+@G`{L\AZ>( ؇Fea[WIXpx- Z#{KF@m_ieT;େ/mjeVMT 6焉;L)XO@$:Ϛ@,B/D4 <΁gskmtSӬ]Z 3؂6ǘ`.99mz^'cQL-(u Q+zDsy_?>Oypu4BZKu8?Z^_,_¿/t[@-EM.Pn=`; t`AOhtSkCMԫi_ (ѿl[%ޛOSa~BEj{lMJ&9^j>O!JRށ ]/)Q9D31qȓFs&ѷrAIf~qi&^ѣLEK. 2'@rk*dG ua%3b:f`,&Qr&*YrT+Xjn L2EʷmZEB` :9/z䆃0<`4` kWc)Mʔʿ~2_2[ S?W+z~ =?:jDߟV`@_b׊Q`k؋@_ ¾b`˻ibNc G,ga-`~ƭJL`Di Y!'}G0`!9JR6:)Pk3?E~ŚJ̟d-Kvw) OBԂ 8Ŧz$Dfp"Vtl]dغ-ە/kخ[c@ڵֽ~q{o)]`ݛkzd#s7X^8~>}_ov*YPDAmC[Ip %#0Zq,ph`2.6$yGȫy U~NNI8kiFfvnA'P{*?8`[[/~l*YoqLY%)0 r ;`Fcg">n].W@\ 2"z l#u,ԯi׮b 8^m%8_\OvWBOg8;}OsgKǥuA?3~9oV >˞j__jט6OK{]>'i"PoOXGOLc~ԑ-lN 1%tŖ`/E/kjo'EM]GOj}UMMH,;y&k'5%%KvZ7gfked^dp//вzU@d*Әڹ!n.G#paMb7 +``!3&N֚n. 5.Q?!|ɽ_K*6H>-k w/"|YO6XW JhXeYſP&zv7SPPbxm5a)8p8`(% p*0 ;wr$ 4i8nf>'h-bKh+|KZ+Ab[XLpՒޘGG!1a#W:4# -8{9ކ\J@!BW\\~`cӟ)P{s7ly7M4I( w@bT@Bԭ/ٝ2_\щȨj8+Wjg@(XP|X)wc}@^b dnjGtd'\|qi|M"i4xh]%e\`Q%?z>\}zB͍ MioS:WY*}g~)%gw:۞?9E.ko{_kN] 1ȧ o?~|?~,|hH Ucw@n x\&8QSJHۓn%xOWfw~:z ՎM.[}GB-`p_-Awtڗp?qgS:X;4 x*.&=sk-ɘ0Ҧ2M-UֿkLbAM| MA]'\hk.d̮zw/4k}^^ݕ1}}}=^n:sCq~_4z>A{0uuM%G٫Fr< ; - 64 Ʈn.M AG788qT %ӥ+߀+ ZF6V kM^Gb<1Op5Zs+DB02xЈ0<: QIg C|/6QfĿ&gXZ}ExN!Xgaԉ+['iY/6 B@% /a|K3?b|sy ^OumcA4&A~qѐ<ԡY0W 8-Mc:v _ZW>]xν O~ 8Ϡ9)xAs"g).J4lj{,0O~RoA*7Hå̈́jHnV{&iq̰G$[wt)5ݘ7/B/-~b5.-]pcuVk!j?Q[ѐJ@QNF/\= !S;Cm|"2^e[%ư-Gi(#빅lun AH1aCJ3HMHcqH0uܸ|3HZ֨ɉq,N~q2ȹ_߈-|uQLHtrKF-.rm)/^%m9_y:>OִE{asa8L/H{#Z_DAz6=M?}_~*0 ~)ßVP?s_b}56`r O?'Mx}ο;x:L-O]?j۷8~{ۏoy}o#2t|rf?(bY|pOaR|0qY 1,1솋/~mvK p%Xg;V ¶U l_a?WJ+kvD<܇$K!Niffwk'_'fP>DR`lTU~WJ[MP/*yõ~%uJ¿ʣ.]dP  V%}%-e|.ڣnku.#NX~oaIMJ RWI~ `clz3]>?ƅU8߱3  _3yЄDr 'c8GǡgC@ITR2HnbRwkmX1\0 nOFܨ6ROHVq({{Y7"aHGe\ Uu%u;o'@#.'\C%+?EF>'fY%اnq +_cb#5?VjۜSxxFpO99͚8_G 2Bquc-jpl',3J:F,u \͛{îfO2`t_.iF}YI/65!zRLVA_a218O>ؽFjgn#kŅ0`@H1l0p"U!_Yh$:\@k `P+/e_&N-?Ej+FʄrM^fg DLO=+8D_52Si5??˾zϫdp#}?t~d|߿ۏoqo1]1H*;%3K;`7_^U|/'Q? d¿fk: kAK]'_P=_TMAsFܕv Dcr3hX['+}٢EʿuR>[83@c56 Vlr0 >$Yx(Hc7N+f՚3 @U6TBrb# b*yCq4ٺF.фn)o%m@!:v kAAUJ}.VlY%= Wd l?N_MТ44T3a+Rw~6 Mf3g?zuϮ礪?竨xmmF6>%?֨?#㷿xL=߿Ox|-?:.>8 p$WOiƶSAL*%8LWk͹UCKo8Zgq+j0+y2LHF S}dъE3Y0AZKǚAӰEhAJwIRv$L&EKxmQnA=k/'K71|}pݦ?({gHL 4mn(Ssī|Q_l'lzP2Z l/F: \XeZsqe_jK1gY2 @tÂMRM$1̏]Xo, `c@=py v>--c\~1E="VqEZ^6k .-rw-Q>j^fVV Y!8 ?`L7n.)iɤW=_Sׯ@`@v 3"ұ~|Ҋ.3?xmImY/b˴8" Xi?2??BfM-%cfT <x{Q' wئ:Ҙt $]&aW9.=]oJ,N.E}߽n7 2ϱ~u0FL ߥ:ؙ)`ufx@lsB@NyK0:*GA[B0@VrN^x]s:eJHfߞ;.ZB]ׂ.A YMu^yż5 T|_w_k!^:'[{A͛HF%Em "ڟI-".kE^&EK  u5WY\RUd4$Mׂ <δj@r-ڍ8rUte BUyLwל#ء)ɖN*ӯ{hI[U oC] s¤9u߫!mvd9(ɒ D9?i!Q&m ޡ/_Bn޳WkkԿ\49Wľg`4t:vz@?R_%}h>5cF+p-  |cZe~੺PJjSwc{wgm%+Q{1h팾kW)Ȟd{K B8Lͧwu-} Sgpub~m0&~,}>%2˺>Vn8kO5&߆U7]~A)Dՙt8BPv|pPrt=Ѝle!}:M\!8ª qBbvŘĖP#Zۀ%xMŭYXbwV7{\T?[ |_|T`_,#5ڠQ?+'k{c"e#6 ̷\6h Tye3%3<[3HP+E_d+?5_>}o}/-#. ɾjˑ8 ˴HP ]ǯ_9noL 3-'MaI7gN$,j{m8J{ۛJo~S.J&Ůw{ِWgs{aY.L#j! 4  @\'%'t9qZ㋮/[0 EP| %ᤘ $|cWljO&L.n˷(C(J ?S47w\Ex_7 {F5/S0ZjLD3%FbjqFKO;>kݗ2P8⥋b&( Y3gS^&@4 V$Xɥ(ɹy߫6a১O/=cӴ X2++8(cLǟ.#-uYpM޸`"3pyN/?n?'ӟ\?23~ǧRV6A&MdEc=>o9BG@5 DS:+n>q_'zx]{(olɲ"ଖDr]o?҇Jp~nb;$k_,v%L| {xc?#Sf1c'<p@e`d`gb׹vZ?2 :.\O]q f":_kx@ @itbG.hzPŜ`Z`Tj12ll)cA,5 {_*/vS/Oc}Gcm4]׌B ́~<&)›xD@Ox\ւ6&eR0 5s]8BUOXuODMLPɀ)!@[JbO<|'*ME_#i_Ù#P"pBL4 "f!a/>TGh\_sD^0{%_hu@$ W4T ~AhDUc#Dal{d?.U*bo`.ߋx8o?)S/%Jj6WsL3 ?e7J2 j,cP +jT]l}&HHo$+&ugw0X?ۺl2b^P] I="|%w rzPP\Wљͩ=Of;1f?8&-c33V@H瑽_Mŷ 8v;I^ܺVAKO1q`XE=TRE%[sxgH}7JL?{@O-F UvbL?m>[[(j,j-5C$آߋ7W0ZG/û!{?ſ{bLApnoHP '$w$*TuZ{A1o s>QϋGg [X:4Et$gE+G: u#V7;7,, =z R.br%NǷOwjZo ~/[5j{X?VO'ӏS@}D!CrdEz |c\H) IH.m_VCگx3UL3itrl\HS@yAp5JoN*߹RV$$_cnJ3˿]ux'rr/?!Ϧe+H$:s_*-5@@c$Q1 6h ~;5QYf)zl:ɗ,M7}R26`\<)?RdΎqF.ߣdU~oD;wc{:[ՖWM馟;Q x]oF"Z -kk.)~Bx%L_3 bf(T3?f)4Q}$h1Q?܁r 2Vp[[S{cZ2[Mp?`% ֲIM٨o+R+{"|c<'oP73 PX^Riz/Ĵ&#Hxtg2 K2,"zOKO~Q w:O)6+lΘ}'k my_ %6.+`X\-3q-lV#yLhb2`-#1g]-5CqRyGҗ t D >7(<`Vpb1U/j"q\2d?J/v$v|BwFJ}z=[hIfǡ۞wo}b/U:PS/f'a:;䟞cs_+Ǖ(Ͷ:ĽW?,aVgyFA. og zV=Ro*`b pn `HCfOPT b!`"ۏZKv[F_k*|Q}kKW@F xxi,#;R{ghRO$*\zXd-H2U騉iGMb>{̈́3 iob='%5\w$]cugl܍3w^H>v`Yohv-ߌ H]oXvywGҒE8-1ν}CrJzAHkcM_`N]裪X>w޴J9WefaJUOLiKx^'ҧmL` 0=x,:׫`w]1)+1}fPPH֥n^op:WAJiʤ4& [OJFn> Sh@-Հl}HN~1b%JՉn1:z2{B~zE'.(Y}"$zЖƦ7-@w+ >m_N\Pbz u*S =-2)h!8.@-w sR,e#]$ᅣ-)Mruj焤 hۖ#8>2P"u?'<{ @p,|xJ\j7@ z3 3Z6ZP˴+h{?_Ȋ&n_' ?yL ȥ#TWO០$c> .M a4 8 nmX1w7'_DC ~)UOϵB&E^zey$R<} {\BĨ7:l /0HuN/\hVmw@ũS  NG+ߧ>b/ pAL4Ig3pkB&yF4&IG:6v]FlOa^Y_kKx |߈B?EkQ?¿,x(BTʥ؆Js|#3`9 bS^BokOOmTSD@q*}<&WEIKK%Kx |c>c"흯? zջ['szzjh%J}I(O@$izO_dU_tӮ^a/C mیhw-@ͪx{o\n1 l3j< &઀,fTwoVի  ; :nF]a @c~FyJ$$2u\oBl87{C$noؕ:AC6ͧƅc9,8ԟ,tVޥA>kco~j#\A p*uM.hs5z]A`nZ!Z@.3 N1 hz-]AVRUu#Ok-I';twOԇԅ0)w(51nXȺLY][ ~cAE[cOzX:g{7V.۹q攎2-Ǽ";~rKwhW?Mc~>Os_gn&Dt֮ (WBûӝ/0ZYB%~3= ؞ĒsA%l:HFKk'NT=(QWZR'<Ϡ?Kg_PK/iLcZ%z ~DAx_Xu>(XTY_[[10O 5@7h>P!`Hw@rU4ϤfoGuqB_5V,G(`0Y-d;?Y|_wc h,7uwQP!u?8VNSG)PvkvJ{Ġp~zf(/An7S @o?d6f-]5# T~թ/ጸ):ߔsoΨO<2W.pMx@D6n)g?6ߍoutg{ ӔN*#@]zO=HB:Ǹ@Oc'߯8CMR{&R{"lI[N*w lF"Y~›}DiŨp]7z]R {b =YF܎uzR 8ſ/DiXvyXeL2 $KǶ6'$Q 6@"0WL|#Z. zP@L5Y[{Bf BH6n]hj#dRjrXֺOWۓ3`t^Gc>>@}Q-G9O:,!I>8uMgvWB˃ )' N7AQX_0kcpa>]&5ZJһUZsвUc[z`vy1u ؿ'Rb?U_ײT|(0(CF5u* `1/Zs_2 i ` j}9[{Fb'*Dk脌*m~bѺHMv}|%A+'㻴}¹`V7.a5 ٠1 YٞSilsAzޞ ve+]/CT +$/ z("So_JϕХ.OF8Nr5yϟ?>yiNAynoQnȕDS^ԅBDTpt4AHDCien' yҟ<f+~Fk`]N~ !MRUO.w#S˞ hz,^kexG 6蹀௓c^2n+.v*T'8η7o3`OOx0y9  +ZT\'d|P 'H9 f*oFZ:p+Z] iP8ZS#~(.C{ _]y,5Vob@"ȹ5}±6ЧMIPrľ@ Hٲ8v'ft 2pu0o'$mڿg'P}gLVяl1f2KT^{TmDA b ؐDΏm%|{vߟ[4!~bt= v4aoU+njQ POE_bԦ8G|?&DxjH6a* _qӇط L"{[fBhOhO& Ղ4ʼ_86c[)?==3ϙhq0}s7 ] fTyNPrۡZIm/"@(} j \,OZvuۡW^We/vz;vWOAxx;ƷϏ  S2. Ҁ"JsW@NhbZ!2|q yN.51ˮǢ.kٔSZ jmݕ?'l:96tU_?G|_{.ool X)n!M +ߦLr[GX-C7y@JtUGCaXp:%cC' }:qq%f<7HIo D^-wD׸25ŅO3@x OZvگW{ֵ{edžL ,J?T~%(G!4C 0*Y"g23Fwl02}Ivs<#WZJͯb8]sP4]%gpSJ_ï+>2MOQjă~(D+}%-&t[w\~{&@}d2_?!/{?^gDD4S |(.WOo7;}x;t|n`"mulL$2VTT$ r ^@pc0` wut./l&%uG*?},Ҝ~&|Ց\:W#3O̓h@gkS٨ڬӼĿOӫX%&Kպ8N;dntRp2qM^On:Ь뺊 DV[s@u?wSOfC)__nC* L-5~Jm{#A@P}@(q|c&6CW;BN8r)5m\\JwyNȽgRN;(]GdsX 䀵 <p0# QGFzzѳ`@'=}e}y ZjOEڟb[R_+ ^~@@$WHru!Ke{^IyOCNՓn0"?g|;&xÍut(ӴaXfv(]ǨL" F~nzɓMBM]Hx\W2+BF`~FNLџ35Nxȋ,ϫ+_d0$۩bv?"ϥۣ\4L'p3e&vf.Wp}N3`*.v ގ7~ {F@"vkaDMDM=CXoۜk]ɘP]\SBxz&Joˮ.@~sM}'pyL4wb+ VHB2P_tkZʯgֳb7o(UjVپO]Ǵ3,2x#'c _MM^x ߃Fvӟ|.HC B_Pl~zn:º!YwGjOq~F\)hDEDhm]a. pc-'0=g='Y-ń$`2{>Եſ ^DGr?YLo)_O#;#@:2>xNX?0kȒJ G_wzS^+=lP}$W88 pf 2UwnxۛV!ǭJ9q3 0 M6QjHT 8VǶ',dXV%k\Խ3[`U⡕zB๿.f:j,VMHp@nl̾i|~=yxЇ;o0iuhzD֑I#?PP]akdW ro(6ߞӄ*iON3ϣ۶ `9!{'y pxC OJ&ebmMy&0Ẽ W H{!; [A1drB>! !SBgaO%#Ѡp) ~ b6yԨ:TV8Q g`4""y=J#3E*h9s>˥OH$>œmSH?~J%_/{t! ~upfnf+ H\Oww/ =@`D Ỏ'ńWh2?ac+]./yqTK,x4b @pPu-`1S=!X jU~K<8V7`a >NvZ =n^Zl&$? >Z|Lccg.V.W:瞊Gqڣ)PHB>a&&D7d?=m1b`B v?ykƿ!H؞_8|'V?'A0Q^.v9'ţSu{Q,~oPj9]4852}J|M3c )CO"؎ǮzEdJ{ЕV)s%@|dmρ e?{\~R(U`zcMjW`O wpo87"pۛ1HǍl&br)z1GqZ?Q!煛Ԃ UZ kPBUȺPM ~Ͳ@cxznP:{`ֱ9ɏ}e'lkk{_ _r&x^' d,4`t'ܮnaty>ܭguH݌m "}&[;m&LG`*6X8Qt>) ^m'w}Ȗ`߄6(mr‚>D. $u "`G]To3ML7|u+0Y~ϴ4q`^N ;hĿ%'4@1 fH ؔdn0 Ƒ _<'u_hXYqL_(J@hpE#yz $\]Pkx}en=:>_F~e/{/We/s?y!m4{Hk <0v%hyp '~;oo>{v8o78&,HCr 6JnM-p=f)?%LuV7)i5z>>fX fӿ%kuX~ ;]El='p.#g/a@2uJg +[UI#}f0ÖVSt7(!@Amze- [ DY~Ak{Є.KXۖP "X`ny3%-In,zWJh?/q3i47O`@ONvIL>ڃ{Jo)eL=I~= ~9};d *fP]&H% ]_58np;Z@L\(L.ߧ? :2S7fpA-O֚c}f`g ΑS%#p??zy V _O.y !<%5y$4!w:Md&Kvgu^ %)aB{ DL£kL(<_@@iX?|!?:/dY#WiTLesBϐP}WE "I/ Te/g+M6n}*KNN^=?! (m{Rs-F|{x 3f)U'I, (v>N*0$%O["P3W#s<NOC7Z7yFcM4֚/K/aύ,yZw Xsp82F5"y6~ {Ha@pZ<pw6{@}@@ 7S'o?\Вy13#`}֘ZYRCbԁIjePF  {7I,ktE(JFp_5`գ8 ]tCPac#0}zX{a$e[^_⟋L~ݭ->QLp-$[FI*w@)'?>C/6_eo/߁Y_Z=W_ ?XVs~y@f/{?^g ? \p\ws]tǘ'S(ۻ~۷vnÂI.Fc*z`"`O.{sS5ol$պn!+Pu?kt,_}S?OdMZ\Cg4_6Xx3>a h2I}OBIpdFvg1!LX@GA~Y=P`JYAǚMn=տu1XxDr2MfjHWAcӓf6mm l ^\e[HT h .AxfU 9FTQD52M6EH&ӖDM}I *`f 00?`@U%a(jMZδyzbw'_5?#@>mԐ4GwNzb~m6O{[rxx1?=P[vРnLߛ ;8F-v |X7k%=$6 "-6ld]Zp? 4p֜Zˠ!O2Bj&ԚFh˸~F~Mc"??j=8F5uIP, *࿠#?!ׅ\o^giυ_! u>Uxr,#J8‰Z8;+i_$V?)XpޣܘhM$ǃ.G`#3.8NnK幆{%l@o׃"6UvW$0 w>UD>Ll%PʪJ>Gr1/ OxB[@/wA7 2湸x7k=Pv &sA`u y 4C f-2s#xnu/kC(i -}$S aoi.){p7n ^>PIj+5~IDŽ3c߯|BчSQoɑUMew2Eԡ?Kg'sG8B$Zh*4~aso8~u;F[Uxd X{$`}eEh ~սz.{].jʹ`lwL&Pȳky{?2K[FZ#}) d?vFC~) gM 1Re5ƖLa\Qj,88Csund!ؗ,Or}ܔ04 GQs(Pz$W4TR0 t1g!ЀDn~G'~9_7* @a`~*' KS Ky~b<9?J % 2 U i iN:fY5uFЦyKC86BȀBg$YE\?< -X .fE7 4 e~} q QQj-#D95Tod'%&F|tHߜipן~Uǿy)ïGԚu0a(_GYQc J p>x}{ae]v.kM;Ȟ _4Ώ,դ5Iqkcۻyzd g@"s?:,~&@48}x#OrZK`u ?>f#^0ڴj{?Q ~5A5O:5 eϠu&h &%@E3MɆ1N /~M/Xrf }b8y/Tn?К{ۚS Ĭ-X cT*2R'>iRU9lNS_9Sl*VF0.PrGjā`>58͡\`"㶖Q`&Pc5!5sD9*ȑ R*ѿCG{tew+_9?Ǩ[n@U4֧~A wDI.e]guhCE)ALFWkZdqgeحqi-$>136@2!8RA774V rʙ<> i̝^;p`ƞ 7SX?ӿ8|Mm3yAg<<6G4POfy:*OT^ME(΀E/OJ I<ѯi*q_z(S@SpFl"SϑHݕUzu ÔA:iވ )P9PvXX:"# 1l kne&W҄Icj>(8?Lȴs$Eː{ @4)U*L Mgwe&]{M3u_E `3 ȱsz[Zkh,#A0?)yVcđp*w}[|_ `J^m48  _[Q'I\n{%iS'ёdhHLQiֲ+oR$3j}pƬgcմ?O| Ly0ky٪P @p,t-`A&X9ޡ΂6-A VQjIsn F4[ϕ`kḧ́524d΁)!@K?!M6شDg$?Fu{#տ=3| xQ05U[tAB/xUOAQ]v첯5HMgtmĺ'eaO{fM)zܭ-˺~⿃Nh`-Kf{ Љs&iL@u$uW_l|3Y`=0)% Zىz4v9ʼqDVEHX 9D9E3*u~"scunOY.<j_mR#M\~`pks-,Z^)~j2 8 4{jkLmG tzT5irc˵92Y?ۨ8p_v]]{MiA`,?=53k$ @G P!!t. iL:[/X+n8jN/A9+Hu~XJ#9: P`ϙ9ډI? A*bN@o.bg8'StKcg9s? SI:ᯘ~`GK΀CK^vĠvI4,t64jt#+ie:bXIoc=C9ThB&;w&!CH+:Vr>+H|;L M݀yBuAA7C3MmQ`mh-`28&5-l?7>5N/3=04wmWY=% E"_dMR=Sf>;~O8첯]?}5BnƮDgT^DB~Z`)kB}ڤqq/JPLN m N(~ ?wr kXE_g,tSDkfxl tF ԅ;UſkZ+(ݔ;X:O]k Fb.5k'ؒLCDKx`6gBPl 10OkWj Dws4y\`) E:CD s`e Ua0- hbR[vaY4d- ma[ mܺok4j-KAGDpF.;<}ѝ.;\[GupS;G`4 xkz@9虲azW+y f~].zAtx;a =9':Zd*>x_JgN;Rnw:OyY?3~Iz+!m5YƬ:[P> a@|VMxyz_XY4(n߉ZH1^U `gH^Р+< A4>A:)p,my]By0&RtH6.Bs`b$uT~P|A:cE*mgvtʵ $dP`V\al$ 0!"-o$I Ik݂$bOEB-?sCLhtomѲlY|_4qd#иdvBL@" ݱ>[ÿo]?;Gm*;HYo V&ω\5]v?taf27`mZڢݴ,rК.wD}mY;F~/!2R\fVޟw\oi.;Fd?ELT4e,g@Hh0rGDav Es_e39<둜w/_p(Цe@̑sVy щ p:eUۃmx*QMcf e0V8e 0Rc;45pY{wQ sGd,jc,.܇ [{ Y]ǩ?wė={=\ǃMe?OGJ/ˮeL 0 @@;(3 @O&[s~%X&3f]Ss bgB([9J+Dcq#eX  ,"Z؃ XZԖ;۷!۲EǶF;\uo˵KNhy4cQ~&W3^?dfu'e]v._ir'0pDžOc> gmbf\dzp"S/h@Oud"(;-Z4BBE6װ$ubz}NBΓj~.uy_ |GDz?"Ar Ώ߻ hixP3LX 6_OY&?@k``̹9SX+nu{1(Lxt\?N ]8SPECkVp+fF7oz6#I|}gGH۝?`HgF3vM.Dտxaa +AN^4vvѠ>R0v_vdu1Kz>P'yQuDqBFC -XɮD,eȜ$T͋Y8Pq9>&! Uhj*tT'QBs7sᩉ.>Y 9ATpnI|,E@`xޯg`,&&ʿmx4|H;GPhؑ[cھbs}?c˿ +#!'9mdg1 ?2t%!9cr`$j\>pdD,v|tJH,O3A9KQh":xiS9]7#1q|7M*jƯ،z%ʽub&fgYANS^NWq fϳr 2mHC 0S mhyMm?J]Q`hP= 66pL'z&Q|ɀacgC4LqJW:M=^'O>paaJ,,< ƅ P4{kz_CJ=_оDX;|>H1 i"j@2[jec%mi*{5G= 4 )-F&n*Gޕ $:I}^蓕:퓛JtxIBORYΧ,9=Ky4bm K9yv>ٹC =Nm NAaP]-nP}!vRϞj[$ W YEN<6=`Y0Ow[̥-nSOT5o߻?ρnv#S9Ú|4/ػ NR5Ъ$ ("񬀊8W{M=ށӉpW*Ӝ OԪ@O9syO4=c=iuPלi;5U@B,Ibn3fg)漓&@>WHm͜[+S0I]>gwA  0_][?G@o=~$+!A}$wv;364VOn10N\% |yY`|A͟9V -+׺տX:+ǟO_ϩ %Ŝ 4'9 ЕDFFӫ ?3@4mH/NYUKc,1NN,g6?^:9zǩ?O9T9W$9gn Xzz.~,RFyag6lcl x:Fnp| W'|+Xr_/O=?τLt9\Ԍ'.&K3{Us }/ᬃ :d3+UArT;E4ANWjBe;ن3NJ[qw(sJa=vbR9PKBAj+AgyaѩUo?eldAQjSY#*Ҭ$@g7\68]fM[~mH 5%RKM LlkcoN&~wGm'g^#MT*'?YU3+sf_="߰JJr@׍oca9 OSc(yI]1Rz\|NzWP*g+. C`p3y>>y?~?{Gz z<'b(P]I(H!vkX9N@2Nqgr(g=\W5OΝSB>2t6s2xkar a~c {u/,,,߆^gx_- s+/,/|q8v./vj5r0-yMo\ݷo|jj9x$P `ͅO5W鸛}5[RpaF&ҏIi?Ϳ@䪏]g+Z?kBr'.noH8բ `IuK-nfoz71|T0RGmOM4d#j;I01qR\4/E^9J9V`a< P4#'md^گ?|P) L4BHA:* + f1$t1z,e( +ZgʑSJL6pf( < 9OۜU \<#~= $\v/X?2`Ěl(d#Z=>G}*tΕԋ ^ 83A+c *KNƼU7gΡCQ$q *όz:aŭ+dWBaDVD>[&50㳅\̈́f4{H4&45_|_ZQx=sB*Q9QF3 +/󿰰 +_#+^Iy;+^(*P19To>5tayv#l͠) V > dvɺ 2NMCQԦnMi4YӉ2 u?'-hjqdTaN=r+@16] -OtYrKD][0} k)dcL:[3M]!^Ѱb T'îܫO]uԉ&1_}.K >$'ZIi,gxӅN\0)bCPS5Ꮡ YO?(ACgmn-dh+ 8H1& .'!ҙ?;tXIꉹ׋INll|/艱+8ǟ`< Կ9sB5lF'(krUޏص9h3āAk|2 Zмj3/RƋ"vv̛NFc:[c-}`)nN;2'%W:%L-.Ēp(jMTKe:rh?MҲៈ.8W ݵJ$1{[RPbZ+5tA]p5u/m=ZS7렮MB ܎Wga'66tc* >GT3(/w K40+ M9ҼyNcGvk1cH2R1b4eoR`]AP@2Ƅ <-9S+{ŵhqa8FM=%r&?6ݦ <_sNu{lm kkҁX.wߤ[ʌ:(JƥdوC"y&N4c&+r|@;@Pr8r|O<{Fe52f 'ތ[q!#W lLC53F(snQ/'S[xC5.b(36P>Ppf+#S܏RQ@<АVT{?Ug>̾f>aO>kCA>G8]_4hkߖOlh:y3i mNJ2qm$fﮕoCqB<UCs?ωm7666^ƿrO9|Ja}zTG?79P8aY@cN {Kb[Ŧ/6M`lQ]'D ̖6H3 9 b91CIQAأQ r|07[c6 uZb~Ud9̩a-j~#|*PuWNiZIsCΩ&sPӏYcC+(NB0ieofEKJ#j@S E&N V[&Oe|~_ҍowY5ZE|=C(Ԟ(*hfqi"g%8D:N9hF3HB4XmzTZh f738ҩ@DRl$ f̓i@6gM,*b@F|TO S ;3>7/}%OMo6|* pkSYb}#C4TDQ k [~p@aM f-r{2Y7@C-NK:q~2\ 3 @ţ x9/o-=BE^V$M5N\NʁDAjYPCݵ9R2sD(?Ma0eߏc',7lYG(A 3_;KC2uPGrWݨ[7`?V`(EBj^@3wHМF"m~D5i[&Ο^y0Uf豜X$,nٷ}*;YRQ ?YA5,IхC9bpݵi!ֶ|:]gٿ1?٭3f{sF@k}r{?SpJB*Sӿz -*`=.8h1Iì-:;[Y@8|ZPN ?Rck:A1h(6|21qnz,@IA'<~'otB8UTHo絤!'e:>oޔ~?z/S&Nl_$ polll|=- fXVriPh7 S1?pPnrw5@Jג ]up*@6J8[8B Aض=l*b#D/c9jdtMqƸJ0gu7tCU^7\ P *Ϳ~ |֖?+*7*~y{.5E@#āpHx45g0}ru+?4,_wPPRYܳ<[q_P6gDHbw6H**[#~0͹Ij Dk_ښj*p5>߆S@&RP". UTlTk"TTfˎ])๬*c\A EaKEU;'ӌ?ϟ Eo%( tM"(b1gEHJ ʞ#?;gb_SwA5bP?Zk?`ka'6666/ c*UoOa P ?쁤ٜ7MHP$A4v3>7z7XkA\pj:7>bȵa^k$Hi9ן檺<]'ƝQJ3΄i (;DMu\_xy'ZMU?ckVX&L( emǼ] 2O%Ai w :e+q#au7cɖ4H>5{eOdeYK 3?9K}"*c*4"ccc㿍xORK&)duI:U>+F!`%{ixr3@s1%G{{xek(@Xϛ#= T $`?ܼ5 ri\O uj C%pD(h1N6C<&@5hĤC5N>zӃc \%_O 5~_/m`'6666&^ @K1#dya^X|' nI&2@XO\uwX@RwO.ƟBe0J 000(S$,(eQ_`0TB gUʥ2C1ciɤ{!If<52kR^ P]+土cJ0Bw> 8A$y<Mf<v9v, (S%;?[$ᅪ=~z=Ju-@]_wߍ_wWllll_ kspi~#45;N;3<'@8j Zt7ւ}VxEA^6iյ( R[jh?1!MϋolI.?Īda;zPXp1җ' r峚y{Zq]2 R;%rH u,y\GFflf CZ%H6t \s}= +rgk*rЕo{oӏ1?;uΪXMU~Cjʕ½vnKGB_27_1Ar Ym]Բct`rsehG$ h f̧OdrF]V*P${t}.A0jPhX3oz:?b19KfGj' G 4鳛sQ9[uuS|7_|2VD Aپ1TFs5 jٮk b;2 \LE>pA,C?"YhJ*TG"4 BRtAN ZNyjh;p {rwB5[׊ ߂d }M`̬i ?W70_מsv:M{ /ث_`ccckj^-ZiPG2~c/OHK,#Ŵ !z94ƸQ.N\'F!T]*ՂrJ=&$ʁY*Ǹe+3L( .|sf4_+FBĄL/{AUs5֒瘼9_T$<U+PsX 8c^R 衮P]lZ1\ ajAefPIk M.}Jt6,o+n;7>ccckc'6666.~^CZq SErlf`% XcwvJbQ)H+ U5Yy^/g":~읥?z!`&HG;-_ܗe<( z A Č4̘Ip iՉ\vƁ EV2C 'go$s&ʿ8In>D cqykg??1~}Uf2~?{  iXu]ƔJ RpB8>eX830}44+%:H #] q tK6T y@L&1>UZ |VTq]z`5~\楅uX \h]vKO̿$2@5Y揅@&͆ AAL8 LIS'<Kal֓wo'r}Aҽccccc'666663{{s0L G&jgZ>ɮ8bIV}ŲK@w$2KȻqҾrDL^ot<̿F2jJq%j4smuj?,ӟccik`MD/wr*r'Ё\Uqϣ ӂtR-x~Ї>\#a4gv*hso+~ollll q5XMϕR =uQTb& Bc葇*!d(2-/d|Z Pe/Όi=#N 3hX!(aSkٲz8s?В$FQhkuY=NjI[]yΕ 26t ?m㸿\Noc {uM-+c w/5N&((2?u_6x `߃)5Rz}2ൾgBrӿ h* b5i $~:[мA`>pu7C.Ν 6A2y~B% sx|^ϠL@жOK?m* T7f,\ `M@wn5^T #R3NAr ]WK9Wo x9ǹ 774<B7ch׺onh~ R+W]]._aH@h u$8FؕHSvku"!u:@|zlY^PƜ!"F #xt[X+]k%4Ќ~_5bl k!& KE濨i]KB9_Ju1ڄqd,4u7Y[ ;mv(3mЛ7Z~0oTiϿs{Nlllll~V= @g t򗍠b``.py}څ#!P9܁1O+Șb뵷^W-\wI/VAޝp4֛ΞP"z.%-l1F $U+^k uv >V4 ?Fd.Nvov|*0 )h4Mrk\}vNllllw}?,! ]`@R94i DiHWcҲl@ӯCD2v'a\IP'sv} \;(X& @`}4bq B()Xs VXߤj#:κr\TQ,1Ѡ6ơ>s_W/4:XiӃo[# .nut>Gx-5{o;gollllIU~q.'גwW H.EQ}TvEnݣR&P98B&+)A'-8[m]]OUӒX_e`L GAGj'K%!.%S|}VLʵe7pЙ16kfYwcϿ?Z+LO}h7_5{;mlllll_ZA,œ_5A,SAYF%~n| 0:t#<4gLFPY@'Ъ*cP}刱W=H߆v]ٺaP XW_;Vz8PZKe]顕hNk=JcN-x;i?:W?qoadG_]MLS\f} (Hf\;aAMN_vB=_μe}@hC(usEUUc7<=1yeG8Ϳ{S\?'*߸~ ;>J?*VNix!Zp q!HhIȠ82N[-Ge C=HAO'=dQ R VZ\\?:a^_8__8цWIwȻj}=* 8tp*aњ6Koۭ1iLx,,G.ߒ~w Ǒ?JaΘ x%Y_|ԟiÿoccccO`'66666 o8/H6@AӍ=餳 ~EfLuz> f DijMj*?NV sA-AZg+oP.:peBRrlTm~pΕzjԙ]zxx8 В#^=_5 pe@Wi%jZ$%ZWw 2l%ി|hP30򻀟n~O7cccc_NlllllI|#W2vL0-Gw5.y0 & K[ ]4Eh2ZA<0/R1V=@s5:T$p9Iߺ(v( bR5 ?1NP.sG* W%u-8 4'ȭ0R݉+GAE -r2IeY~(r 'O>%Pq>39oR1zCk7FN4FWfT_'6܎4*N+!Tx`v'VIB{"K?|O?8ugg5޷5F Fh;t+~12cP^ݱ" IfB'?@q7m|0L3@@z3'\[3.Hܾu`O[3 n T5{A$E`)+0UK!Uf>"Ff "yۡp@rr}k&>4{'FAh4;:h4&c}'f }i\1%ky?e@a0k+YI _*?szmsCn^עwaj(@$ ^*!P\F]mF| 8Se^ow@ꚸ^j8P,J[#uKxKa0C]1s8ϓcĹ#{C=]ww[Fh@h4AKf7 _.(:/ H'L3i8'Dv{PJ$#\ ߞe+&)J~RP-34I"t7: @I} l>b[`Ji98&]A8 PR:^SyjPAf>X?)^t\*crǑq鐔ϽB{h4fth4o,7=~@ \aӁSq .1HS++@*D.e N{*jHPZ>tG|,#_:ꋴ*jKzݔWgAd}РTZ!_?cx8y=Ɋ/4s5B+p?GpF!Oؾo7FoC'F;v= 'BS$媎gm>]!ic#! 6ڝUFpTC2h%|gz^̧jhż%4Ib'k+fP799kȌɅ5UEc#TBf'zh44th4Et 481&tVL%5H% `CA(Sf"Y ҿXNmPKW55p^_ONviQLcrW`-?qFB{b$@h"xK h҃CAb%q=TNjs!~9~wgFF(۵Vf{S@]80$+¢ mƵK`5-]~~(jVg %p2v@dutLdBmԸNjK"hXHwC\4JEcr>Pʟ.=ǚ{OJJWlh4tth4[Io~OLpSKMV5 !:TXiYg_$O2ͿCv%nM񈼙 HA0bb;K&N*-q }Q;]K+S{ hqohJ/5;cD 3GW>Bt>A>E|JH?kRHeɷM6wzFh@h4~~''oLj_4_xzAU/RU}k'Y vz D:A1_1^_$EIp = :<dĶ)"*{@ Lem)/ӟoG~!;;`_/Bef^?^F Fh.oH-Ԙ.(Xe.|2KM.dDXxs^$ei=$EL0(o4`*_Z2`RLjoAqqh%&=b, N w2H%_4wW1h  z8gA} t݃c9#e ON"F7l4ƟN4FaOԯ/{?8ӍZ0t.{Te&X4ն{Rg,Ϙ!zeFQv@ˆ>0'),HPy>6n!CuOS-k'e2tdp5EhzrIj d''TF)(.|xǤˇ\y=h4߄N4Fwz&^Zcnǔc.7=@|T_=J&Cңf <e=9!!H' ^кPI kf  aBe֭ d@蠂cZXz$qp 2hI2DU#_c#u M9Rnm6Yip4YOMf#.. )?]Q?/(4j(x>9@i6 =̴$}l8.}Sh2ȷ2ZDg/(`Tid F FvsiZ@mM IH(@Bu\R#r8]?^(ĸͬ 0$ɀcP`$y{v!n4\1f}Kg>k@jQE'REQůB.~1݃ 9#bh M5@[܇sWp1N]gmn-`6xi[  2nz_cõyAFI Io5_9pG/ǡ O ]a]?@k`??g]O(@MEQ/Hг+@̀NΤ_bJ_PN\{%w;{G$6̱kH(79͸l_/]‚".kc,xm㿔{؄C\_\t4_ɆrˇzX}(@QEwzyvLPf&"Х _r{_Q.*$Do] FCC%4&:rB4@ӑψ(C:oo]0?u*-e_PHX~ @p`&C6]:LyW:znQE (w<~^ ,D; .=!c̆581wuȅf $vdIP& ,Cv/>X# Ha:(FݎB~Nq:!m(~Bxoއ|s+EQ<5PEQ]<?lRw6jsm//q?@lk$3h.L5- l `4f`S!~ ӔЎZPqO= `z M(?|A\gO!5-t !xrE?d@w_IAC?( (DKͧCOnu@D^iPO7{Lk&`'""e?cFy¨HbqI.!@wA78KКNer'C:s]l_qC:,g(]@"دO.. ;]-eÕ+wcG$EpjC6=lG?u=): y&9l@.Ј`&{ W1yoBwf)K]34CLi~VH=?'EQw&(S>4zOpw"w/ w԰N@vţJu訛j QsK@qNa RpH.q/z3)twi^7F麥CDnM:كAC|] Qq_QE (w7+]9}Yd$@tDIW"svc>\.(qDq Sm g7xA\e?i $¸N1nZ/$@- PN)up鸶O$}2x-}Y(u_QE (B.>G*eJ?3; !OY!Btn߁PڡnKThŢG#}tjI/f*_%}@4MM4D!~ooc6Ƶ%]fç+0c:7j?Sj]'2 ߵtdͬuM0_u N,y fU Sdp#eZ+"BB0PBp _?R>@_i @4MQBׇWnSD rM0OݞrPJ*>yw^-Y- SA"a(.%BIߖ(#Pr(t`CVl>u-!?!dOA$\/B߼ 4MGM4wC!z{iEx4X@&j`:5tO$(uGXW.6 l!Q,@W?Q~ I(úR:ih+cewoi~=4M|>Phu2}5qgZcxSzD5IiO'iȫ?}`>( pItΕ`DvpjȨ5dzj oX%"gjy0Dw 9\ RגzӯCD/X'Y{JCYU"Ώ( d=Z1ћ߫>гB՛Wi'iB|s Od&e{fɁI]I[C%X.121$sѭ¼ i lT |k ONr:x<+V3-G%LB++~tL/̰NlyFd5M4?hi '!z.9$%Ɠ/< AOQqٳN~wp !"j$KW?/}@꺎OԷh$/7U'JyBIX5q $R}(vP.}Ƿ$@4MS i*|ݛP[͑bw'9DH0enFxjL2jeYK6( _S.SpS!:@|>xEJhۼ"B>r\3{7ꗷTL-%R?Iatfm#aɺ,?NmP_EQ5((,H=p&֣:l$ϙx,6H~5t,] <#{GW7pJ KC?]t!q÷*G3,2{~߶[_>>trj-w=kyzQEQ(}^ `9"0lew#_W(EQşǻ} ICցԬ!d'"uxVu/6p EHq˗␘,k,ow1oЇrb^$(^nEQEQ(}DyI?2 #NGհcj?cNI"Pr ;8d3p)`^f9zkyi-a fUXOcB ֱ 󦀵9`QEQ( 8-jKZ+8?CvܡeӚ_#ۑCHSmh30/V g|-; OG|\Gľ} iU[W&-EQ@ (R{G YҶ"@HmĚWYOLGXR;,GXr 7@aGDw]C߮&RL?"&ZG <|CG >Oۺ_EQ5((~H!k:B0:'zyx4{p {Oz{'"ĮC6|!j)QEQ@QE;y+|{ґPwrJ]j́}D6EKH5ݥjT;k-4 %]/_-uGeb@kC {_m(EQşuCl_a!l#1Srh嗞r3@jsc )SOк `5Ğ?β@4zX[/]kpBJ*EQE (~5xzz#!H86޿>OKHĐ EJJ!@Wв:v{Xl»j̪|;Z_鲮ÃO{g[CeWEQ5((2ߟvz_ ,ۤ+Gʚҩq|| dA8 a).D/N`ŵ>|}7b ޶w~Ű=5'&_/; i^wwm:YZRVxKh0,!3@)!~D lO#/(?G5(Rb<_@`9@g++B)0 4nk]V2Nk򵝟Bs0]ؤIHLHz3ҵ)mcr7ĤRAHLQEQ|/((vgk أKn@-PhVXƄ*=:|)x~B?8:]#@G̖/\kEQ7R ('o1s&8dͺ|OEj5!rXđ.DH}g߁ӉQ f#&R޿G/>߅` FV]Hhpkz\7(;@QEy 0 p R!cgLȎt;R Y5e ypHoGNWzHGWW ڦzzaBtǟbO((((5۹]cm7شvYau]@w3W@ƊtD*Zܗ{Z}BkBy%[zua[>{oVNEQE@QEA?/,g{|Q7{b&=OMKF@C I[rw))Rz loBf4?B~5 2ƺ=`]X쑻ݭᲛ%ќDu8@QEQ9PEQ6HY?i k2,as.FOoICJz=B+HO5st^9]!G12N^8?Ƹμc#q}!D`{яK|%M=FȒ=5K(/R (7.!R9^7)!97L\ "}է _C|:#[}]W9S.rӹ_κb54!kEQE'@QE[!{ǻEʣnmq1(I5Hmݭ_ɏ@&5Gr3tgl{1FnԜy͚oT-/(F5(j?Q x:nW4r<g)Rgۻ#ҝJNZ\VwʑNa?X@@xA|Rm PEQ((ϋcClFS1vUom(R)WH{`őL+tPŜCSH/_ |Q;⺀5fU[Nz!Ȓ}ৢ (@QEQۍopz5+$B@zJj֗#bq3[k_}?ت[y 5,KCtG5{n${p](/Q (Rs>k%<|VU ._VEQE5(6~`ٔn^FHY³n?D}5:586yx+w_qq~a0+0ᬎOG6|S[E?b7&9e YƘ~XLXz% PEQ((wX~Z@V s*ȏS_؄u6M1g VR)e{į܄Ƶȁ?k#;Fr$z@?RӞgL |H;fΒrB4M`۩e 1"J(RjvcFY a<.$_L:((PEQ7;~#${Nŵ]]Z=IwF&5c&m"v`{3c<|Ʊ ûSHM՚'SUfhHbSo((H5( N6H6Gv:^](K;BJLL͸ >x O24ӻgi-u, 'pH猁xd>rQ3x^cMZ L ϵTHP(EQEV u_vz=pϮ&!iO+>޿l7=eu-^H`R3{`8Ж^(zKc=p4VsK%8֌7j@QEQT((~%y?|skU|&~Dpڮ/?Ujzm7qMZ" A#c2~U>b}ɥD xq e1H%g| mɅ)(@QEQgl#,|I<6dl~;k( F[Ddh7\xXZŠ<q_8Ns7\H4M|?Til0M4M ii3ύ5V9ABӋy3\le1Zc! $ HKyN'/,5M e3a+pQi@4MkWo$g%-hFP>'T`4m@XL\rC;c2d$X(Ww LE+RwuY\+o]1Ji~wn4M4M7i<565*y̭ZZ1L\@֭69(p,Õ|2J5 dK/#숴*K$@o^A)1aJt sdμFY9ʴK08¿ i@4M \&_H2@M-0Y^;QI%lYI&\+Q@:Tf̰ƎT t`T|YC&mx_'OwSg?811ik5K5nػM4M i';i!Ib\&@*#T%mϨudEf%r.H&rk񯐨 *ULT*2H%L4bIeiδ1u]uUbq5vnsWY+s7cNM4M i7'ng$UN)'W*#jơav+JBƲ@)z 9*%{M | aħ]~u?]QYW¿2W_/,7nվ@4Mtii~K_.TcctJ,ȑ1K S08GHC 8pnƁH82R:+D-w &t%$ٻsv]ϬBz_/9g {Ѕ4Mtii~|/ .x ˲q*`1tojHDP"*PvZDB‚JT ^dH\븿*ſg,?f?V#;Z(23˙w;fii4M4n3G9-9DCIpN"rHw@onL+^HD(R2ҝX"^00񟞸rweef `?gVSga{#};i@4M5,A~{ONZdSP)\۳+0팄׮ #QC5#t\Rg$ \+F#+- #0e( >?5Sty{fձl.m1 """.+{UUJgZk<4^ n6wuv#{SG|뜗CVgU$46F$eٖւl `lnuyw=]uݜzecy?"""w׍s6ֽi|]%?22mKV?PRV{Pn=yh;uzZY^]_:DDD?-W=`[WL<fYfVwfn9Gt;őz/FC!@DDDpGO{^}Tw|5$մ8r8\'fl)oau]aV{rT<籙^Ovzy[r?"""⎾WgE?Mز܅kvVd\ĻsӞ~DDD#6XgBѱ BZmS0L >٫UP͹vwr8ud@DDď4h_#@cj dWـUG S׷ydʵ:U,V+׵Q?xi """uUs*a=Z){PJ*ke71ks:x w%l_ˡ#{#""2I깋a׽@,6g󯪢TdJ)٦Ýzt_>>J xoWh?"""!k1UY&|PI<>?k:klmW@bZ0""e3ϩWG1ظ <3HC0xwʳSSǶ킱iG˳l1෾oE5DDD޸G/\4y)e ~521vir[踎wҡUU/R3GDDdwGm@q9eM-f\4hJq~zߚ"6=FQ#+RRʭn$Rĥ&ʃڸX:higMi]fJQ*Wťts%kl(R8ZbT慸6Q<+)طt){-[j},=U*J?HItHz&!D盏"ɊŒD>-Q$j^\<;mj=xϣˏ59zhjR/_n h)`"l>QI`˘&L>vxC>+G_ir%KIޒnᯅ]xpYck>|M5_oջ`OV^ԷX݋.ˏ|}z{'3Kʦo(/|}m;.?:˖C Rf(,X?CY(Oփ/j_輬~nz5"xll2,qo+O !G"@͍ @~wG+@@.,%K)"Ibs D!}N| c+}O?[F@KJx^}xA?Xt w~x껂F2( PG%/P@}צzzh #Lk#*k#* *M؞VmIENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/16px.png000066400000000000000000000007271512772601700231200ustar00rootroot00000000000000PNG  IHDRaIDATxڝSJQ=x&P|JhD$XEj%(ZXH|Bt! €j)ZΠspQ^tɌUr$Rhs=ݻ!rEȽAY?9$˕-VԦ}m)B`>ox9Vw@_ W؁ Й w}?dL/Ÿ0/U&]mOy[}?JnV/k l $yK xd }QhXwIENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/256px.png000066400000000000000000001241051512772601700232030ustar00rootroot00000000000000PNG  IHDR\rf IDATxr$ɑ% U53wlW.,5khsy \r"$ iC9x Sx],/zM]o5/^N^>?iBD!GrYH8ckޝ~<9@qs+$0Nh!?7g=7ky%Z4]OW7/ᄈ< W7`ݼ oyEHf^tc>g.%1hYb%ExV~dz>\2"볝K o7i5wo>/e_ 8\rB/O?%PF8$4)Uo 2sS]wd"zHjl:UW*gvN:xÒ"4P f0#̘IF{K{Y1V]m߾ g >ճؗWKgj_/X_s#cbA@b )RZ g6#8ᴫ O@%13p;?qrۏsgT_czz~顷!1)G7*P B!+&:ω̼̠YJ AE)v$0dg#ZjUe{#"K%$qS mK.0믾~j]VNBhjU_,|m (MAKXV1"` 4 5 Zp`a@P(c/!ְW{kUi} SQ5)7wK \WY#Txg__]k"vӲ77{rH=Ï?4ml}D9Iąë߽6y?w6AukUzUo-ڵk=̡fƐ`Z#}zF`a*@E2&@ ̀0 cr"&'yp;rZ^]_&%4<"7z՞ݿzz9+?ܚYM\%\ZUT^=OwqH< z? Z۪N>xd)q hd !pC#Bt,J@%ӖYsĠ̠"A9$ bR5T" gC+pLؐPrOCGżw+ߞV2UzV1u}~9}߿{xxF#/%+ODKg^޿xbzo5p@J6:<V3]Fʀʎ@)>MH4 h(Hi(A5JvXl,Ws97ipF4u}KF.L8lnڋ!UN ;ܼn-˷u]= Qy/~ի۫_x7]U>nW3e ; d(i/|#F0 "Rq +n. <% 1C0!Ҁ 02P (ĨaF,LT5RhGuQ*BK5;Ea89(Y"2 0<66YX8]~v*57QD߼ywo/ ׸(ݾ7׷༾";pr"w" Ȗ!1dС2xdH@5X0Ht( `;@ ps @4V @ѳ8@ԧ~LL"AZAA *jt5,U-OJJ"$AnP5k=>3+a$IVx^G ˪EYۧgvq=e2$ΔAHg``@!eSE"p ¡Bzwd m"1$('@yY) XCʑp ;00y zwbt'6/UaWN>'_}"oU,lUg2ކ;C<9 Je1@YH $K3U.p]3d8r&`eD+#D rb{ d r@E5 H$dJ$ II(L x&ҲZֈB;\P&*Er&ZU>_X_zq5]Uw:.o*b(CH{ !F~;62=h) @\ Pb`qB Mx@ѻn R Pd4ИJ2dJD<0QS< $RL("JL` e%d#dHkFdqRkVT{^Re/uӍrۊ޴{KV"\B{71QMԣE{>&>~8+Ha tc"V{/ @H@ g{y!!3ÒA Hpg( xF8#iB"kW%Sf`h'vm>D!,Bkr4 }<0s/FGMe]JNKL iIKQ)ѥ~cRx硓zRy-ߞH+%D K0 *# IdlI( )<&Ў! @C"܅"(/Q.`n-I((U$" v>C3@c+Ouŧ}&O0:@ h A0Ope2 ZBei?AHH|/]B$BlrYRZY#s pp.s8 2=kz&'IR;R~/PI]iNzpf5n\4Ip@ѶAڦ"t]T< }x{͇m@(K`'p!00ߴ;ms &Q9`!prJAyL, !dhBd󌚉H9h!5{3߿|Gfja@*rM(?-댦*p!u"5@uty/)c6nq_>$\ #a0wh'#2=@'qN WO!O~$dBVmfHP R a'%p%Z׳1\]]]~{Lu}xPMs% ^y[jMOdA}9/zZ'1w#Hm5jz#ĔƌU6 psp80Gc!nHH "gh B2 8.v۾<% # 1L`p@OօG 0 r %Jp_|e)#%3 dsi6` DR4[U+Wh)3'P %3zeP:_[i=W[PP#PM +rm[[q{ "1#  RN$@= ) ЀAa0xt? IF@( yjy[q&YG;ԚG3sOr)zyj?09{^m` Aƌ#{A7Gz<-U@+\O!ԡ3[ Ñ&n#P (%tU_0A€3 HQn <u S!{җ";Zj VtZumѡgk#TMumV=}ʘ Ƨ6=FSϾM#<9(VmĀ AskC"?]P`=`lL<B?҃6D;d鰆mHa"C'%5`-zI@ : ABT0((,L9 ?֦GO+cP~{Jp ?x4LE8QBSVɬ$Q#3 m:`Bp` ڄ\B` CqEA"a=>=P0{Nu[wBJa# 2Hx1ĺ w/A࢈,2l&'( 0VpT8f!Tz?y"$t9jiWi(7nC|LkSHA`$"*k&|F4 exu*$ d}~'ށF50Elx|r!?{]!vB[i:) (< ]4!NX"995XhHDt.$Zjժ#Op9V3;`yYꜝZYz&n` dHh։2 [gDU@ L !!f ~PNiE( i}2 Zt6C.OhA6 B!hZsW )d  `]vh&'U;Vr*/oȊ|u^Ss&#Ukә՚lݚ:`5Bp&tޜr0J"8,100d`pOVf@*L6%5$4FM>UUaxY c=]@i Ir9DZ+-Y"APA$(H9ÇLЕ~ "T r@#!d.5g9J,$!AR@v s3j3qyh<40g1;BOxpıo P !OM +gD";n"$N`I(Prr=sfe.ZL)/<. pIE 2տ.[s iM:r)C=3vd CFKA#A^ؾ[`}kAgd߈h[9@HF2 x9"| `@xDS_RAT lb4H ܺ`B``ỵ]<-4{MK=fOo˹O j_no^ni:5e4kl#JQXh$C!7~5tƥN1S!6pк @#u)u0gp[)uIw&b08nѥ]n$1w] ٿ= { ʂ  @ ZZog/'3>h{ g]q_/T@eHWes #k^Gv<"Qq&` uCS[̆NW# .)6ZwvH0#$!ZfЧn)']ǑP BلmHc5$hI`,+/-5R; kB f፽*#@%Pv3);sSP|6uҍQ! 98  p<ܨ+!ةX@떾gGx@(ܐXaJ`4pv3&A+. #{'S%P!uCK Ҧ$Lp$޳ : ! 'ZX8$$))'!J`zx.ufHIc+3ܜN.&| ֝޶בhKNRcPB s搵@l =@Aш#,u7yԕ| HgIqjpqzV ' }SC+-5O[PpG3`}FțJN\Qx߄KuAWjb*FYkx Ǐ  W %bkZ ٧P,d Q--NuWe"F1VNA,83‰#)(Z;^ M B2aՀG 9j}l(X F&ݕ, H ]@ I̠>3|"tJhvQ0(l2O8:[uݖ~)/ pZq0#F7m {!RT-%rD vG[OuKm.Yi̬w-dy!:Dbx@910Ee-`˰` 0*|QYfC|vD{XVHr!w,NC@p11>@B'yܤ+"T,2&` $E ڌN`or^mmu*| Jg;󫳷WgM_sp\(%}. +Dk kj^m5fx0A !G qGIч}Y TVQ Ѣ怇ªG3Cls>̼AaIDp s`mCdGwr 8:9y v@t Z)y>(+06úZDeV)`f2ӕNkowqe£N-"J}Ej5YXcqW<ó K"qFs2o`3³bDA}u }>d huTVpu݇~ø_FBNB@$%d xDC[ RFh 7C\ְTr*烙oc?mmz.@38D1,2PT'W mPbg?g:jRX+Y`nh%Bb bʼn4GxE =5ă(ރg P&S- #!#2tƍ)oBvOS-q2فN -q>,q='~)Y? &KpR %NNL&ػ[ ³cˑoC%>ZԒHpKV@$"J1ó0q0=[ MhK q K& *;رG H(BX %B+SX3`یM{3:*T7r(q3mn]?ڽWOm}.} bJ$ew49J@\L ^# Rڴu@k9):*ݕC Չ.d84JbTP8eVjXAW^6BV(B R  oFy!J5ģ~6Bt?HI< jݬˉ+@g|DUТ+NzX?{z/}sr_o 7GdQ'ȹ))YGG܌,heYqtN*+Qcp'$oNWQAd>mfFEXt$60qxm®_䰴{$ \a޺)Ȳ-.ދ ;҂*+y(G% \"((]z[sRss͸E3qtDy mnP:z$%*53N#L\ݛaJΖlt_-GzQ} ALØxyUF Gu{!p`$ )\Bj A>gE]hʩUf$TdH$0n0m7,A8klQW{g2+".ߟm>ԙN9BƨhÈxn3ڃ@QfNIǔ}P-޺5V7Rg'5Е8RH,eI-`!FFP# 4;9J#T,Bj|'>㡜(ʫH;ޟ|^?D$ U>v9﫮e9 9/)qbl قu1հơ>6Bܘ BH֝}Rā^Zm A&H-Pb9w56]+5ȗC$V0yuWT#E>5uW{H+'H2>4XΧ彙?듴?6z:-G-WuIGgEŏ\ jṢ8.jͿ?7tyY[kF`$k#pc^P&N RVs򆵶)#.*D!BF@=:PO 'rF$hFVmϥ'my#Hqjb 0;YmKkq3 syR9vn~0{5,րR޽/CX2ݤ((H2VA(hx7>>ܯk]ZSuw'V3XkAu}WZ+qy %=kβ=+xt3,"@aꭹOne 0(8ąEDC4ݓﵞߡ2x~F&0P8m&!JH`,js]3ҜZҰUVMߕxٹ x#]v23&IgPg{~~w:gwoPK9Lun-`ƑvX$ JlpKXoM@ANtfM p"bيaj*ٛp:ۿN@>%\n=595uF^"Q}=q,--C䅩|4ǷZ,GU?࿤mY?cRD@s[jk0;ymZr"ga,J+LЇwgیgpTixlYۃ7cۇr0珮->'Pȷ*a䰴hK \0 䫝p Bf}T9wfvނ)\ ݳHA{|6&^Jn)QDFSa$R!ͼb֎懺iYZ]" f p ,̩᭱H(=k%7ȬHL+j BBBw噄%QZ[ko hkvgW.R!0܎|^giDsH3([Cj:LC6|:(Lr#iX?s<#b}BK\ک>U]i&3q3k]u9ϪZݣ[;>W1wUjmqZiWK9wXآ@8Hr>ֈ3X9 $"soV5jDԦaґ->67S[V N î ~8L׹PN` bqj !}sLHٽ>wRO᱀"PU-ZU$scIv")Ś$+jFsSzF5ŏfq?2<- Um.?k->; B3=BDxO%$L4fPb&W0yɀA5l0 #''皃"ӻo}M䑶9*8! 8𵗼੨L.Ds}{xgfoUNuQK_ IgI% %{ %YyUrj0[a@ 7-w!oMȅ31B Ԗy]Dw6$2y}&:DAh`gQ;=I[;E\ n'?kǓiEBNDZ@&AN~t%€o<`OU 9̙W{@e"RDbh̼4!p!Ppr_,pj-,D)EĀ8n-Cdk?WF@okH"/fk3nO|vB) l-58~kE#D;󬌤a1E" tQ m&}6 !0$F Tu\DS Gw24xuB_ E/gA8|S'>~jZ_\n 5?(Kc &5$xЀDA R ?=ٓXZy-QC}P  .>q;1ei4(\ P'P x~syRSM$>S2Ah23m<Җ4Y9%/p9/D$ɵ `I+mae wImHn%CP&( tS ՛]Z\W vl9fTtcٚG/Q2i)2 epǨG vwC+]δ ~|xkm=% 2%IQ]dt0mfuIלvݾaS^lܪ=-ܿP./?z7^\vkM=Nh\jг^vj~#D}CP8,3AH@5[lR pXSdP{|xoŏ\~5Ǜx;8Q7hqVtq7Ws B p  aX J kbWVǏ+Ml3=s}p[)\r'ODyi(_9[IMnveVALPAܛG3e('Ad -=?r$ }%/J$cs>@O"!h L5O?v]&=(ˊ!B&i&F M㬱6'. pDz?oog4/8~x!n4 5TyЉ 'i ;PWppGqCk+ A>%d d( 18Q!‚L 脵gb_$\~vs?a]u;W.I1C8 Zz'%B=ƝBkxS׫J@Ą]C̐2'11q>[z " \ \/XTƯ #v9n^"! \  pH= ½0,É1>\ -pcT ˹ۃI'^٦|V4sA`e\ KZ6}G.n$̥a*ه)j)- q OW $&r$a g$c^  isLot&yP;81)S8$ J8>+jS \r'݋px3~\t(n&Ab!xHHc[ Pdn P950l#mXlA[gOr@GLh$(Q0pܘE!ׁņ뽗iw1kOEx Oø~s5M"º\ )1Q4go}9 (hKl$)#WC 7.4짫t;P3+wqpQ8"<7+QݕMi.݇{x>F.EqQvzn}LCJ; my4uOBˣ¡,C3ВÞ3mCDNѺ$dF-##{n"Vrr~@ϟUm_2 &o )r: 5txy8|+XHYF"d!2XXxrQrD1z #QCSUC- ?0 π0 ()6S`ԿA[Di=!L-($չt</#M21݋ϯWX=U"Ed ,AHG'΁C8@BX^"ylaICrx&G #`NX[!Qt|xn"z=rY6Ov/^~d"< 9݌Iޤh\cd*!O@cn0Z7V>́dх=$'h(.\JEމ !S@H 8;1bBRZA) ;|и_?ZKۑ.۟Ҙpi'hS)s; i9^qhDy BPicڗ(zM/go@&`@m' p!!E` Go7(w@@V !1  F4Z__7W?||{ړn_ nOfN_y5fyNVKBr}b"v:$`L$'0Ǜ=hDO͙{ Ƅgtߺ2$6_zF H"ܷ% B Bxo;& Fc-H"A NC !eSB{a@KpNn:D_:ֺUl1eA3сA4 S Ci@2B< 3L@@y.<f#`WϬ^(jhͻcߏkkjv %x %*bM{W$L2Z1:\]A@o}V8H-P9`9PF`s/()ʃS 8 q' { (@8B^ssD&/Xvd.qh*Kۡ!1Bl1^|I/҃=]is_2m+ f' )zZց"NZro9>q0 ozH`1J`Ja|s׫8{/lVd՗17f9V񘙊 $ĠNS \]x{>-v`܇|i(?\ &z'w@*[dV@WKꏺ {0/9"%99hܡ @{˙PӒ"JJĒ@Snrg$O~S[o<^gl=bE @[pS HRKڲ@@^&$b[CD(s y9-i}T./ Jpu!# @1M%4I0npUrv$A< `~ZIC ԙ&R_ ({@2>/ܕG@k<L E?z\ZD|q\Wii&V-8FkKse]v90n}OR"l?IV->.({19[odVtwπi -\r4N3[r.[Ո)(ns`W,$܌Of? xgNc À́@S+ >ݲx`lNNrq`w FSf)p8h>O$ &'" "8(y6t'k9I֞QD1O8D[5`]ZEJL-,pP %$aK8/o%LN9OY IM~  '6Os?<='.{IO\C8Ah`T' XC-bj-#<;d_(.*S$)!D8jTg69J?DDl*OBy/g§ XV @/Ҁ7[EF#Glեx__J phLC&BpNE ,-Aӓ[>= >i@FD Veۊj>+! t! eBrUhEwakr}u9,@|O!6vEOuszvo96x$Ӧ lBJ dRb75@-`hf f`9!83!i]۩Uf\@j)!'4]P[!e=x< D$'Poԏn{<~|" >3>>4` cjphb"Dh=,F#%#.?&i:GCLO^5:Ɲm>鶵nI7;6 ma!KAkUyO$Ծ;Nh%ƫ X NƔ`jf|>}x׵=.AƉp`a2 {k "~G@#W oeY?K=؅Jfz?~~5NB9l3 97 x ,4xrkǪzn5( ~r.k: ;u&SgCLHԃ3? ==mmȣ__,OB>UXu!lFCDS"mڤxC kV?^_z[F]dLDˑ[I`=E Oݻ/>ig hA/Wx!4\Hl$ a5Lgom@r.?"xNUuVyE "̳up(_hOO%A; `#k{)9h] 9wFQ -bQD[fO%! BCMn-?=y|;lPuWX-0/Na-xpH{'jnՕm+۔ӯHܵ :>(lo= gBkNF-p>Mć9|p5 ˀBeJ TˑT481u3͌!M!'>}B3*R txr- 5pF8p4^u; J+5{@ &' R|b9'-t9{W*W12wHc=CǯR]<g ̍pN[`/ %D j/aq - fF\V)f%VFֳtJFDH; #77Gk}Vm>a`hXGsJF` ¬Qx1bfJLlF.<~dIJJ%IUSL}A}7kIs ܀rߟ8s!ϧ1ϙ hK7 W`ZAFxg{N$Вa$5 CvcK97ZOaY  3 ܽ(\,%5˃q:}xj\;Bd.FP% e!Og><`0൳=_*ЌPg@6opNm( U<~_˘v5r}|8ޟV7K U_y= Or"yl4S$ND0LI8H  ,Hc`(ԯ6NOjNV=́bfN5pR`5bZ,u7?+G,1/\W4"L "Eͻ2{"rB1yA;Fr@r D4 A622h[kqh5` B>X ,܀)@vc`N1pUu oa v / }#'AL0)CN]9N;=xA- fɵMq'NS;O>j5ЉCGA hpxtE`Ό}[juKW ,k НKmVx2 a; MT[[}|jj"pD'[ _1ӹ \o? v"b$$0QA#{DA`0֕EBQRi0Jϧrdy}^[%@8p_c,ɀ;Be@oO3M59f T띄q|= 3V6)=X1/,rvD4`` '1KNV=O~1afjt/0_-?o<.7ׇq"5몹VXhW2`d <(-ݑ /zZt#p7L8pSy@k}l<.{`mh":BHWfS:,K %1cI8*;{Wr~i$HPe,9s0Dж>@QQKù{>OUI> ixB$q(8WϯwpH)s5My^ӺD| CSN!wr5āJ>8NWL*cW V7柉Dݓk%FU*pXev!]|8w 6mϧdwc9T}z~c㴋R2ra`a0'0/c&"]C?߿}ͷ?<|x^eZ \D\]_^W/n^>yvssl?w5O+JՔyie]EJST[֌"pB " lsN,,Ĩ+s' h"4鷾Gm,$x7@L &  +&wpLĵbqd 4M|?p=(!ŭ_?, Ȁ 2d@ %'dCJ.ĭV;?Oow?|w߿ͷ?>|ݏFx@ \;O"vXaz՛|~W/O}wwon߽}x>>yY__i˛ׯ_<{ݳ770̼tسQq;栕'вAS$Q0,SSdI&Vk zF%Po[`s<p󵃏 `nmAFXV Q6bS ıO {B(r ]FeWpM()/MH@I"3oE4f~}uo}ݟ??k]ͼ/e)b /n|"JX՛/^zٳۯon_v8ҵ`'ߗ0JN0c)E\F[T-2 %60{`(Yb76ow>MLqʰ:[A]'0E 2G!-"p )$U-4Dx!c(yd m9?B3<ó%TxS}A)az?ݿՋw>߽?𰬵>:!f ~}?-DRTJ.0gw׻ӛ}^=~s}}z^Oմ+KI"2(̙3TA D@"0}3KBdr;!  $^AܶTE~ʞ n ) G_9@"`D]&Ȁ !Bf`2P| }9(dN̄irR {BtE}DwTyW ꛑXLAg@V 8EbDŽtxϦXʛn|ow?umkmY˂+_~RJn7__zvWz~wn|3NRm.Ӱn) p'Xܢ 4G,}O6 J9sPZ\2<Rscc@[hǏN*<5{3U--N;چd^n!c + W`U,3gΚRNgT,S&Ɛ3ې!Mϟݫ}ۛo~x>muǗ: >  3 11 Do}~뻗/_>~suj_OeI58"yznmmm]cv7Bh&J3 -T5Q]UNZߗA3-s[t:C(6-2S!dBaGω6`FH0:_^„svs  rW Ng?JyÎ͛6:/gyhQݨt%Z)X c"$DLlgR<8Lǝ ØKΑ)sb"F*gl2$\8 ϧix\<~o|O?}eY/_?%~_)q Gqdt}uz/=۽7%g)u*iGE[ֵb|8Oy>i]f+;dpwD6̈́2~,:zUQ}s5DžF\τBx!k `1$?!"blHZ15zaH@J©Z?ö<0h{.Kfb'sT9=tRb&`6`>؋gLIR)8Ed{8{7ui-5f3)3Alp5P<$03ͬngn6_btY̛M YXbm汭V,.ͳ||_zt$ri8笫^b1?]43;8ΐfIuTf''좝UuΝPnrV!KCݎ]}1ǐUi6H0UID(dRkfm3;ϏWs[=kc.ؔ>U< ` FqLU,8&œ(fh&2P [B TVa+\6 Յir&c+ D@ AHY,y%$%h4*9ιva`aq: 1M2HiT @ Dd .r6;9mNNtgy<|e(ulQ[cȸb3$3NcuX4Y,lQ[k?y3_/wڳꫯ/W'j~Z7g:>1 kCmeC5q&S;T%[5;֮ |Cݸo.?^_p~{ss?"""SյkQ*5r.xqb,, QT9QV\` XEm*ZesвB-Հt)4Lzȏ-ڃ)jD*B $P56EǾ1Ř>n!KȎ< rMa3g+Z͛gg'gE8;9^(HzdƲCk[+&<_̚żiVU]l]]cکu,{^ovy/ /ky{tq\3ZؑYIw.xT"hH1c`Z ^^V[3#YQ;j-D0g# uO܆0c l6Ap10HL̨|% FT=HeQqO@ 4v06n]ooOO'/Nϻ8?OWqTUkf5*\60sʲQ82ܰrp5LPW(ŁT0@ְfm8?:8=ݿ&دlR@MSVRL DϱB7כ~BQrN"wOf"P]9y;?fhݮv;]6;-:7*Z* ?oY[p1sӼƖS@ ffW #b 8s%A&|iN)pX)eA6iUsِJ=8(HOYMV5]qwUW7~{|szqv|rz~jOϖЮ6M[2o] k=*9b6SW.f(ScP*^zv"E`&g1Cp]zQP9,F $dqb1ݺ}YovƾbĬSQ5Y۸ʸp%ǵ v4Z)t air@˓[ClJj:-i"Mú.}kb!F: m#i_Ua"!5!54BdA5#!Ƒ'&<+".RʽnW닋huxq8/Y-32h0WeS$YI,ZzmO)9RJ1݆޽o.vyˉj㚊!F1}y{{>BHƻ͸lBbuU?~||ƒ=G 3.lqj;%3 9n_-愚$RaK R!<.ךgSe@*[!_5 $k(Q@L$8?8Ie/ yQdx( 'RJUq~֛v}utt{}|r}~~iXq~<_ʺ\]նbCLgMPIby)[9=/i )gZwëy1;ض[i8qm*^ŋ__{qQDs.r\C)Edq/} YZX27,R!C ]8س%8UP.~&k:%pbra`: (e4=P|(^Ã0Q'N^ U 2 lؐUea} ?R*gExn诮6VwwW7;9?^-ϖx>o׭3 yѐ%K$+E^fCtQMU97wwˣz}-CחŻ?mNiTӲAZ*=Av0VJ"bΡ8jIMdW)WGNٌQjHaTB,E%rc q"ʃtӽ=4 Z[HSC CM1$3ɓ!>)cT)&@ɔj\@My2Wi%*Ui)矁>cLc?}?~7 z}}{|gώ.WAmuuvf^C+rsKt_zcKc@"[T#D2b{_}w{ץFUߧJ 4?t0 $n㠚洂[9pC ӆoI'Bv0 ;k޹ dzS4@(rǟD?*!e!NB(eBbتJ .ZA8~OkU}inz>?ޟ~ۯl,yFgƒ!C`֠i+,4uwl yW^hۋOfSB*SJ1C>JЧ_6J7YU7r@rkܬl-ʔ>R<"$"Xv&V'@{LQ 1w/BJ Gyj.B*W`zC4*0 !1C*쬚Bi6~[hhix1FCasnHhQ۸WްQRTFR}%]7 .'/hZkѲͷϟ}XNjEM(3 3'"Ds+A~JeEXDhGkPժvmY+ aT." tSۛJo}2? VOޗ?eK0AN!I/wW)"!+X [fH~=k L~Z]9]Ǿ8xU̍9 dVQ+l&s;8()/A_z'nw@( } SRr<8mۯO=z"$WM[٬1mӒNBIUA <9=ߊGS .WTyg[X3gV+Qq/RI3/cBZ2|4$ J0e{Q=qL#$ MD ?]HGks3d.vvr]U[K*M>?p(:jTME3Q+R*&(h$E%BUJbLvޟӻ8? H H{f&T 9/ ӟE)A>,e' VT%Řc9@{w>O+Om4̛x9;igՉݢn*ea51̕1RA# ofۇR?"<ft'j9?y̬uҔ3R;_6~|TQJsпE59}\EVU+"6&A QvVcYĎTg@沒kPe[;%5Ofӽ+yxSgO'O1"6T{VL 9!GrwJQ`I=4d-gk 3II4m>L '^JYfӹGl(`;102lUmaߞ=?;nY+Z۶u=泶9\uTjYc)pl^Nۋo/ߛ~ CIcXk2sEZ)J%WP3RU!Wo>pi F*r_?q~hgVᕥe췋mBen #K@q5:C:1iYPH-5WT'R?B)g4XE8nl7ۓjycru%6$N0m)arIY:c>Z,fY\ * 3[c6 UƚY`*Ѣ뚙+UuP#ϞDUlVuݶuAn݌FDLdJ.A=1,wQǦ b.'~_ `7x3'_Lͷo$ϋL !TY1B")NDyGޗ񑢭>w]_]ޝgק`I3, XjEԊ?TEW@9 Ŕs"ġP-ʶJMbWY7'G__|uowdn$ƴ?CHLִl~::Y-zda$Ml#BlY1R6bf)".A5>ka T;>mjI͊iKY1,\O߳OCP?U:&""P PYMIcNgB;,1w~.]48x}snvwiamjڤLLVȚu >c!t6.=!+gRBǐcH1圢sP@HX69l~x~vn޼o]~-|IR>r>Al]_,*Q2JY`(XD? Y&GAM@4 {dH0Ss޻ZP瘏`,&bEK~uݸ !x?]OǑtDlD8q Xb"A&RLn7n˛QTEiTJ 9~Wmufl!CL%.*yeWGɼon8tDy$",%S1"bTժZj 1t8}uj,Ix[LGDJI302kʓTʄ 3N3~7'9PzPz x0j$꣕òMk"9+c?9+2QC1*|9e(CR~Zڿ׀?yömʰa[&lSUҊ茙ؘ:n;A)fE)yfw{}}͛o}v}95?ɮ9wo^_5-Wmfm5cCKvAfwv\ԋ|{|?ѥ=,4oF48lDj6 u+2UUl؅皋#71 o O@=}tɡXgM~8y:},\eL4s<_n!D Se)z4&+`<Lʒd"3egq㡿pꛏn-{~q˷_.OZ[%X'&bJMZ4u}Wg㳳E\Xk,H*rN~n6Wo߾znv!*_:)}x߱RaYBohIu4jk&cAKQƍ'=cnܲm]z;ǭӜL=DX] $B?BJn2ɹl\{I$~ipe2ƩN y,Shb赌 TXӆ8tjB 8;8gI>X0٬/N|l~o>=;=:]ʱec,a(gAMVj5_T! ÐI%䬻޼~s/߾}zSN/X ŷ6mzy'b6fux"1/U.Ajl)iƞM"9y,3[2`U%g9RƎbF2[.Wjo6<61Ψz\,롏D*KZ~`DbK@K"àZ-I0Le` M!xGB~" !K"Lsx)B=gҨ9߅c'ls'DZ/Q6uU}_?ggٹm[ hmYQ[VV󦮖m,+kg11c٧nׯ?|_~퇛a52a`+muׯ^d{w_gU.;5ÒuujI3gl%b#xuÎbCoe6֨X;,Vf ɑw)% mj.mʆ]d@‘" #sFEeXK!=8=2=b;_ ;WT1hYKV*"!h"ҁX^Ȗ#]7C?t"9}{< Xg7|/w_??jicT-W1v!UAE0c 1l1Џ]ߍ7?\^}_n|'5P<=Sn]otyzz2[.۪n,YMԉ D uӶmKܘvnYWuuE8Wa8zSGOcJ#?V eg͌`#(ŠMn`LZY ågS10S9şEbޑY?$6x_[_h2z&t اSPԀAI~" &*g"wRϲg^7CJ1LDCg g:늻`U.No|{oߞlޞV[g+*sMJ5_4B34æuw7W/?|^}놝 OHZrQDὗۜX_^m/緧'G㓓j-kjfWm:7\eb I gҜ̬ivֵaQ C[j BDZ3K$BH, wLHPBKR` ê) \4Be˩ K 2VW'2OE`MzHpq834 U#ƵI NS6!";kwvzZ^4Ӧimm\r2]]xͳg-^.Z8CL(9o \̖4%)i9Gc7z}ë/㫷o^v~OGf$ЉEb1?q~mij8c%6Ug+aS )!+5j]lۿ~+ZU뺙WVCIQiQc%hsIIl`ƚh٢ PjH$GAxo .\a2`AYtSQщ"d<G?h0&"&%0yVٚ(fݐ?:l䙙+3b>OOگ?[>;{~trtjZ3Uj]mh::?:9=99:_gnW ,c2 qC C:&1!cG]뻫W/?߽z^}mΏɿ/$*B3D{:GL.2ɏFK5Qbg'r^WM,/NOWE55\L5 UmnbF,~̺:Va%@Ysbg2w$RDƒR|`Xyi5Tw@T>~.h!LQRW|{H$qD:mlvîe!Ć+Yo/z~~tq~rzrrtvZcWY4MeYͬNjxٶnѴvYkݬbfCLtOJ'GsPUUIcnw7w7_|^Wo߽aE ~%1 Oe+Uu9+sb&WPf!5JΕul]1t7wڧ Z/qrg_}}/.:>-i;3KSik UsE;wbiUT3nfH zSCUsBH8]_|w//_w߿}ǧ?| L?GX*"bvdYQ_f{rj,Z[xOOB>U9%M)R!Jc!LήtZȚ,.ynyҁI"%Ee Nedgx6<4]t3J.PI{ +Q2xD`J52\k4Eh-q~cB71pkwzWg_=;=:zqljg梵YݬfP*m̲YpX !fLx!MZ%& $d1o[߭ns?/Wo߾|l'Ta2Xn.3^mU1 ZV\ו=89oo~gy;w:梒A ?z!}L}z=.vC?4vmL)m]X띳)<=a$Z˕ӝ$UX05&Ra:4*4%Q S4V'sG+pV9ѮUoGl _U.FWϗ.NONWѳzS[aee6U9c@ʍv )v$9@S_Pl f(ӏ^ TS֜vW7jw/˷oߖKM/r@l }E7_=8;9=Z.m][g*VAj~<vqzZ.s3s8c.SmzqSLCÕVXm67,)9RB"D h.G;o"tzO.e2 +X_T9,\%7MC͑ Ύa+uˮi\m2ƓcLlu3fJ糙Y-hfY[k9m&a0ñ!KaƀADC Ԯi\1Eb}L˩GM0vpssW?|w/߽{w};5RO4ڱgr-09mߟ}~s~|h0a5Ũ*eg+kfMS/j-gf\y ͂:!>]?tw2;[ 5 QjVEzNI(0mB4eǦ\CO.!;y{T'KPrQd*&$'婑"},\/qrsV|)4%UdQs]H1f*Gm`k  HIs>]?ͫxՋ߼~۷WSADij5Y?==iVUY fgeʰm\e*c)TQ62RJ}qf]__\mX۳r>]\2Tbe(lֳE[#1La3+[~`T~)NLZNDH>E9QN<X8pPf8T1C΄vc l;Gծਆ ٬GE+,YYynN$5Zc34pEIT CƘr}ǘ˜bJ9 % V(̌S`˰P;+5`)q vmn\\~_}뛻uqE'dNVտ-<_NNNjqq @s%~'VJ@fAX"bf"c-[c1ԬY>|:]VjJ!j j)+#+g &#rJǴp n0)0DClՐ J5&Mkc4A)aݯw77o__ys{snYo}N/L |>..Ηǧg|Λ9kfQ7At$Y*"[1ؚώ(:D 9iI~țۡv{woW㘣;Hn7w)/y~B/PVKU*e-u\ 9UAӨΨhwLۃ3[w<>\Fj(N?p FR! TlV+R8 Z g0MU2飌 ˀ$8Fsunێݮ~CL'EhnC"uZ%U>qwnnK) w ,?,0&MUs'ghkl Pv۝`*B2JQQEL2~ۮn}y囷^{~OIv]^l/LjJXD5MeJ$/W]ahH`X̳8.TIB~hiInH6mo߼z7_]vЕu,RL\w`ۙSLJ lH(kT 'J2%pmj:RY$~\~po-׉* &a"R2LCD4iԈ.{XoQ[ 2 b\)i@@av׷w/o.߿yՇ/^{j^ocT\4 gs\3TJF՜ĜsYE'BDY?Nl28q"XDiHP }& PUǴ[߆ݻw/xo?\^w},=Y(4o-! ].NSDT5(KjeMhQEŊ])K&d* E!Iq(i 1D# lѠsHe'bXmDk2 B,;;w_߼ywŋ/o_mvaCgd>9? {ITM_ Udj\v㠪#T۾~dI4"3XCKbF9JtcSƔӝQ}Lfe@attIBe8<(+@QA}`,X̚u] RF ֜f$lײ]7׷^|/~>\]mBc {'tFga#4q1(כӟ^1 d^5VZaĆ[sߑC H 1Im\ܭoozaCȨOJLভEdǮ*ّS KJDLu@tgKXbiOw}v[*֒)F#1 (!fX G7uʔ,@PrzAc wHUP9CD8UH!tmU㫷>nFs+?'א_:<SO1asg` c83<|,[$~nlwf]71AG{U>Y9du&kbLU%ee`ap LFj`*j31M>R ID V[$"$*YDI"0]@U !H"RAL lI)n͇Wo޼|nSa~9KOU3@' PU5)e:xMo-LpdT&_(LSJ9CJ),~*o4Pbere̙A IŨf~ͪCWXXBc(ZRE\M3 3Ly3Lf!RvjjDAA*jChVuQ|kv[5ehA9 8Re!o^O/_7o}xHIg>맒+zybE>"=M ~kꞇVgS]2}^$*LFDIVQMPD +Q&![ E,O%{Wk<]ja M9+B`$ꋲ$BND! ʩW4d]Fނ8TwZ256$huYX? {*_}xhe +=yhҧ]UYA&N $H8p%S&+ T3+ z(aC RML>16)$(RcFEb1L A@)w=Pջ>wW]=ư͢Sśm՚rw]뺡 ej2LӟJ~=$~*੉$ S|V%0!V5(⽙FE$L20pIv^e*]~R.C?pk,0 Ʃ؎M-BU/Eo'g:gwͷ`ݼ.\>lCL}N~K0&D4}J,^Dƽ鯆{.g|O5Bi NQ)(dX5Xh6),jzL.|}$TTCTlk͆ȍGü56 mP#>2>Eɍ6:Հ6~T9ar؁R[gtREA;f,y?I <)`-T|!F(I=wģ'KYK]ݬL=W۾u׭/ǔc]JiTS$}FީWuRLL9FLr$M[s4Wj ac@*f0K6܅I3PJ0%gWF}@v @'Ф@NH"@vPd7vH_no1AUߏ'O{|1WFSIηeU^AQb_P8Zr2lEh!!$IEjfs E?Usn3Q4(?CR(AQ@@""Rc2SMj%ؒeIY#8?+65pLpyR! 2|TA gh6 hk[]nͬe z{BL'PO6'NCWYScHQ䢛5-ӪN@3)PELb?l XKd:Bc 30CdیNqЁF6!ufmhyn .qzzm=݅8!o -.NU?PQ4,9甭͊$m,h{ xz,U@@v@RIE gFw $cVi&C[jZb=;˱￿m^nFN?A=$ *ǔb"[Y1[dFHb̀eJY`GLe0+MC;PȅDcJtə-zfMfmg~zja'*OWvaw' qb\QVUHXu,uDXECGQe׃N msu7z#rWc> >CϔO)?!~:D)$Y3HQVj'ppl Te)DT!\C&MzӫG UUߙj\jlYg~~qs{av{7q QBbRQKTqC`3AQ@efP TD+TZ1c !lISCq4Bnv~y\ĝ8A^5'`1Kk݅amD#)K@ @* `^bSqI#`P7ldC??' `.IOהe aL݌R#$MTM(^.+I*k)00! 4DUI($kQ &7!uѧz{? n,b"bU617eA,ciQ9R"&K0DĥOaznaDƸa]rUc9˛ч~w¸y{(p0оh*gmYTMik %BJF&@I+! rWm\ݩ10EU]_lOuq?'>*UaR㉶]6m6*B$RrjDJ 6 &(CpĨj8dx$& "ab?qO9y3ăjC2$oUak(H!PeA%T*&k Y{LБ}L2|?Ivԝ8?T Ue6ۘ1ShjYV0 TD!RM QgYuLd||36'ϭ>GU#עfA5Y*&(yBT4@3G+LEdC\;K8? DQ >hTfN&Ne168&b(Y+XG L//!pU  cL05Qn2j"*cRH2[K )iL֤)h,tC&?$U!&/"uK0βvuδd{0$4ժ5djʢ:$Q"8KB())əAPj>$3TaT%&ՎMvm+Ju%0 -J9[&q , )xO ِa!pjgW2jo 6b}4z]Mr2B(A~J H8+O谱ƞDi"VBb@T$(' Ȩ?!~>у>+ZŶ$a9SZtbOS=iqH)@HZ(D)f4%4(Q>8l4 ]0`ԆDg HxIH .\Dy~UrΣOS+kX"l00J "/ΖDCA` CLW N(Y5`I~ X?݃8|x ~W'U gB>IV;oFEaPA"@(ڬʢH1!!~4Y;[ ؂R4-gA3()@* %,堦rE®qƪjkykvvpuq~Z8|9AOTTrG !&fƱ3AoCLmfƱP|x|Ni;T;6*" Ѥ!eݍxo]yHY)ROC#_RY(j*KBN9Ĉ1c ~88~OB&?ELY)G}U5C‡`r/x/`&̶:@m,T\d8T8/xz=002w!=v>Iyb){_e\dG_?!~ @{ϟ2p_Ϙ>蓉!8=C*C|+.!C8!qC8!qC8!qC8!qC8!qC  ϴIENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/32px.png000066400000000000000000000010211512772601700231020ustar00rootroot00000000000000PNG  IHDR szzIDATxW1V K#Xx K `aiGci؊Gx KX>D7+7o ?$oŊ!m(q350WC1r2AkAD2zK)#LC&L T[ xZ-kU Q5kL $?6XUd DVAguaC 1U+Q6eP EAFv%T(A` }vkKcuoz@Ab!a V)B4%m {NiLmRՙTGSb]ΏIyttoW8#\|F~UЈڌJQфjv(R)L:rQ*_N}]s̎wz"@@<l}eRTa{yR.nKui_aܣ8ȟKI>Hl.@GKFԦGYq1gSvgI e61YqFXY:D0./ATeCeTCڲ|FR@yt /jOۗ zh9a&;hb]U 鈳^-"a)6)w *--!Vʃ$pg80ȧ:,2vRAċ@l=C%BJD,^BU'#tֵ] aN>BB)dsɽ^Ds[9,ȷWa~gt:NM _/kY[ׄB7"Z\ V'm*,^oz__S bк>@dC#*qddJJq nN1&Rdw;G\ !-ZZi?8k`¨|[I $.h@F}*_VI0 << W9MgD kr?Eaåg|o9Ĺ[CHOBW" ن+Rop@9 -8.qV>\ЭmZrAzG30Apt#<VxisCu EځC}b? u.J'pLf6z% ')_] Y"Nd:^u.p.i[pAt0eoϐ*:|[>{>)^4h~Ra?Z9-7}KUK<^~UA;kT~yQeGk*s&{AӤX2!<3yg-$gܰ Vd7o޾|SS' -׹=l.Yu^\ 5`ް\b7TMc"૬̔G1X+1HnÙf[&A.rO+$Gu$ 8/].}S=h C-o.ڷ>@T-`H~5ϞDg_ r$G+)N[@7`_$ͭ>ں,Ч[ #*׋ ~ x®- &{{I pj$Xpe0i43jW_4Zy3+bǻe<[;C}e'‹X}c^9[Ώwdt IG[N`dVMm@⁲5g.2~ud%H}?L!~N7pPbOnhmS'щw%cϗƅ ր߷svꉅճ|'l??\s>(he]_'(=KS/~c<%7>ٻB Yuji[>nQ< %gUr벒s6;b#nDi:|:y&<<$ee7-6qY{uIlƼ۟%!TlS۷oaőǽ/Lb9} 8UWeKk[RUUfyY%]r2 lnE/g77%2zҫ`<٩JW%`#\j2WpPG/?m;($` ^&* YpEG|%!d8Uѯ$*ttqo%t, &X3 :@i0ʪz' P&i6A nfgmՊXay=IENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/Logo.png000066400000000000000000000330571512772601700232240ustar00rootroot00000000000000PNG  IHDR<q5IDATx]XW . .nIbc?5;.bP Q-1ƒ|&hX1;{ggv 8yge̻9s7 npS-)<PP  nG(L:&[ c@+/znpCP8|q<6#9@ [3>GA0npQ+L$)"O\؊k]Isj5AKj;\/X3-Q'=4Jcm?mq9?@PP|㗦 K~Z 8k?0C8PřOo `h6V9]k{zc_͒.]=CyM!]"t0ƯJ%ӿymoe>$I1YHE|J9<+#\ *~`-x(zrW[H3aoUXU+ $lqZ߭[x?glŘI S?H;.FLa"H@-đɤucruɭe. "IxxM}C !^cfc ;zh7lڃR)="'ܣkd[ѧWSAA.·Xjđ M*}C Db> %Ѱ IM'g :7v:~.ea˄7(PEѤ F6 "W8J#> nI$㺎35uZI%$/&$LI;0Z] !V+0Os=0^AOD5~rRYS/}4[o"GL8=zxuM/;RϞ1gC6zeڋ$Sݒtmv-=`ޡ`EF@3'|U)kImIM)*Tի|y^ڻO#8J/$_ 3;nS Yr_1ЯYBs{*z1N j^WVR^"=liY٠c%5@,Lx;8o}~J _!~\)p~n;F-'2:E]v]:LR <EωjD(D,*$*`"$EJL.j@k=&e:ko.x@_zhԫ(yXrV/t<\DSRdٮe뢡—1~5q-lk(h4t+Y)ރ6 iMhbIKn9eu#ˏ Eʼnىq`rpf *^epy;3 AǠ5?EmT4hz@4X(T^բ!— {gݼB9LZmΡ83|]BB #U琎:Y%MŖ&PLr1? ^ؗ$F _g~C{gF:t,j͙ď3 E/rzkm2 $|A`Pu~U5R|-}U ȏ'"*@X -7 +1S?h88Q A*t` M(6iM\bIκԏIH*W>] p,1Zgı}!{;ٕ:H5HTT}.䪀=slȄ2ӟ$⻠Y+>7KꄠoT u Wc0;|20h2}Y0 O*ٔѪG8-Ykgk C8%1#v;JHC6rqIV,0Ou={lhtGIJƾ$:neX98E!a(—8ZN,%cV\ߘӯ\Xɘ>C>>;rw93tTFeF4PcX<- jn=Sz oJ-W뤖AT6S" .2'XH :zN}.\9r3LM5ftv Z6083+(E*W.+3A^::r+ !iUJt3aP{fe&vk `g`g7=rtPN</n.Xlp:$!pBP{{^_h7!TNO?a ;uKK CNPڞ[j'z] -77ą(;"Ⴁod-&jSM,q @y ?gsf( $))JNSH+V"V@C6LiFj\|h"X_4TsPUnr{+ڶ.5m-VF"~خ'%Dߕ[% hf3Fϰ&`&W}NC!Ebh`$V5t311^OlܨtPwɶ-ss"&;̣UHC5Z(_hahQC|H > y?RY fdw,/}OY-MfF 1!8 `I%m]R_KW`BGDx- +GĂᇨ {ԭ?j6av-?kH@RL(~ <G 3i RM"lHV^Hoy~-ѡfeXƊ}0ʁcBzѼo m e=;0LߢTaD,bMaFFmH0yh7a/k.GH ޕG*!cM,R~NzEd4Z8.r}IZ|"<\ZER[E=&:Hb& 8C^)kPn;t3%!oyrViX` Ib =))g:`3i Xր K6:i"դ[Q9uAX4 brBvŏnm9MoXT ĺVZ*ԇ"_G,4h Y'~Y =;CB:@xXMsh` ]:RR&j5ND{+/PA%AA0L6t٫_8ghU+Pav 6cQ'ՓR%EH} *iw/us5P!"VJJfMMb>'(;~)r5ZsVA& *ǒ j^LhETHnVZZF5$?C_E#\;ʑw4Ɏu萱W>VT.tFKX u4+ˠH 1Q]j"UfEhW ]׻= }gm‡Kk91l*TRDADwZVenIi nhlk vI:ǂR-lq  NTAz Q{=F`lqi" sh#sHe4~HTًr & OddUԈ6͡cepJI6H9k{U2H!-FE)҄b.L+Ԋ̕؋k\PH܌*m7d rQRP[ZoQo5N(%RR+ى ~ :}<(, KLhr#A8{~6CcP使EhQRdtLEu5gu}`k55Э82~AuJ%z0tX $'* 'sY4"biZj闝"/d'=oqg[FoTR3` E5PKj"IX K*C/vL|Z}Ļt\Kڗ]D/'[ ͎};$d+#&_5Ǫ(͆LCzfc&H%>lVrlY΁Wt[vfw xfnO #u$٢*@H˴=Mk+P}oT'y3:'u F`ad9>= 5^cr9*..y.ۿeۦMa ]4Q2fK:}K [3ԮHJ`bɷLb1+xKOB yUy2ʹ0kSesZ|UhW. #p(= {tN))aMnz _#BjRW Sɥ:Kdς4/5e1ޒJnq)aǏukI%cgP+""ٝʪ*+?ڼyʁid7SMBnZzGтkKO|xθGRS2qb{s1յ/>Q''9`o/n;6a>m~ mX/`]Ж|ZDL؎[yXal26{Z\IJmrr ꛒM;2rfB bٶ׭oZ>|*,w|,qDZZ&[3L"RRX}Pww-b )`k&ۥέwhZ@C>}+M_3 i`!XVǍ;V:}c* Usy?e 1}yԣM͛y vjh 㽱|=ԈM,d Lkgk3UJH0#LJwsI^)\p^8ل4S;ډ,LdK~YZtB ~e`߫> 䒯a%$@Q'?'=]gwUXdxDb}f?-Fji쬃I)51fR%EEoKCG,{ K`wmc&+UIcFOcDB'TLk`ju@˲5tuKK F洝ZuLNW]1rUG5URZ;ښ9c8Oo-Ǯ}OWoܱj֩}=JDEݻP#c7Ki}ö 2QKyyu?/tSr]IYB!HM\ vS@&'" V.ڜ-bz}g;$CH qvcoǸ CGh'o9*ɒ lr)M)W,i|N<=PkX'&}GZZ3S{P^?dZgT>Y0kwC?t~cgXqd9kfe ǝO|t}. ӊb٣79fê_3e[9s&X I}p3|wi +WBCK: V+JoB}VWyj0ۖي;NU3BvPTw3fdEǾz䉅,?oE?s|~p7}{t'O}}xî3gT/#+Η]ݵk54E[%ӧ/!UJyW(f`(,6N0*}FRIgng%M`+к&*.c n-vnsW~}:v]xu8X/of疅O*;%>"^ۜJ1൳gGH7 AÆ̝>bEf;).vTɽ{9'N ~(.++7BL-\4tZaT-1/YT/SYז;e rR);<:ɺLYm3V>%'ē'- P{dHJ^W]A%Bq݆NJvI>:Y%ӒQi>I0X$`A1"]V*&.*nCǛ=`@ dt c1cm_~?zH@:2H t>S '?E`` >폿^;:r!bϪ޹eƻQ)o$rB7nwHE`o?eRI~ $.;7D$*S!;voy{THLcRQ>>u./~o<ۑaub} Jzᮭ3|Ĝ{Ps#))bbbE*t!%:DQOɗo W#_.ľ~ĦuCŐ{F*9ʫI|$gHJMH?] PTfe]޲, (GHժ1ZF냪h4U("EWPQAT wi$؉߿˽rՎN?3߬8_٪Bciԉ8-`ʥ32 %* ~˵,fO5~SI?ael~9>71]\R(CD*@$أyԨ>?,3_3'83}ow/Y2.o٢)+͜Yӥ9'^9Zk R9D6UVXf};'RRn{[֯O__Yva:s'OsG$Vhi]CҺd^"%% &Zz15uFK?=N*CR&rbt}E31e0_G^ǭ_޹qH(5Ob{LH/N%1<[|gceI޺R5khnQ)mI,A耠,//=s!˛*W~3eba\\rFśޯȲ1#vz](OEr yk,a23k*D {M rBjǏ02Z"FoZU`!d& IMi|9hPݟgPY &-qi9豪~bɌXeLOoxk =NdPcB4= eX<`roG'GX>gi0i}w<1whB 'aŐ:(_K-l 'X19=ŗQG=TIo6qh9(h<':Qo&8:3u,I-|8K]1 R.Q8sE?b!J,(Ģ胭[:r؀Mp e' ^cЌ*چ)|f(bCY( S808P?·va*>* p?F&2fG q5ݺUgWv.J9!J+fgU1s DyL(b{w+T͈(Dh&^E=vR&`Rizdt<- 3'!s& yno۶?+IM麌 z:"o{9&WH{>`pՙou}@I,_εCr%-x(0C!ke{zT@RF*~(W,w063cҩU Ӈȕ_.˛| ÁRiJ\L -4Tz+: !GV#k,3ߥ 6b5x:%I@[J[5QP_Vd]#% B P3q|ކeͷ:'?J$UpP_ 9*awLjthD(ț:ӯ#nd\Fe`Dx'VtoN@鵔 )YMu(k DB :j<<ze!  AX4cE7wũx:bB*?cw=^+22(nȢE(N*^&z-y ^%'x2p)Ṭa")p?B} n$BuIEVx37I8` & rcF#rUf#1%~#{ANT*^Me7= ϕKJBbT\R}K}M&0ȾeWN)QhzMP͋OpLoƯ.,A7v J^Avj>$ᡜXkc\ BBVqoM%ʪSW{7?bdQ,pYR 44 &_!4Z+aM6 N_T*!5D幩ptPW*Mfw;(Eվ{jTB(:;D^[o84Q(\-\QP$BsBɅڨvGi4o;I8 }\d7 /O|QE(*#'gm jSX)V g^h&_O\xv T==,bmF,L歾SaNyXܞedIr6 i>MF(Fh` pz{(6fe*z zݸ4.JcHF) FwN#2Xܞ`b1ejQ}dnHVNtsnK#VQqh‰֍Mb}&㪒=iȶaכEKL;z^qu:4AA-R_m~V:`p {K>}kNzm=+ϲޚq,mʴ9f,@(\sV⥝ͩn8MIAclq{х$7Q.Jr)W1s{Qr9nD&@{*n/N.:'U܄sH&42̺qƍ7nܸqƍ7n_(^zIENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/Main.ico000066400000000000000000001664311512772601700232010ustar00rootroot00000000000000 V00 %٨   ) hPNG  IHDR\rf IDATxiq,Ug6E=ggJ s*3"}2}aVL%k~V7曛_ ж"me9j4(zow"|w| |6p+\/b0͇iT_ևYW[y[nG;p{xw1\/%ǛSw:k)6[fgBޅ:\FMD=wRStjtk<*žR]EFKy DY<0sޒtZR[Od́No°)R`xy,7huQlчǸH$+0\z TU/__lų7틵ěoVloUHw\4l2B(t%6IzJQ۔THч c`+$r1D0 Er~z8_~,12R. kJp@C_q[cx^m}nfi,9VA9B ҟ2 B&Ԡ @BEY&@'X f$fIG*k&'Ğ2Q)TKT1Q3 0K>O|wkmmz1[[Z}yӇ>| W-bŪ{NmvԗmZ7]M^2[&HM Ai2 d)A&,e @pE`&P)`@dYAUxBC(AhueAz" ;d1EPAIed9e98ÊyiN_E7_>sX_Xϵ W2)/jt+oכl_7x3E-gZ @ P*@tG@EV fP/T@C!)///(2: •pێf5JRiP BJdVXMUE!zyBab(fYEmelz5N6 ?Nv֬f}|l㇔+\?ë_{}6u|^xwShM,ܝV:P ECEL{"g~%3Ȁp ŠZ&J+!R(a{@DTVCz0+ĊH"*h4)*P1,L jj(ư7434h֊j)#{욦WG@P(q1O.h0fDFѩHȰ &`4bs /Pt7;p~9ye[@sO R`bXQH*{zae"E!R(T8tQfxAP`#rs SͳhύM>Ua6WuRpm^o҇۶mM.eZRG}Sd# Y ԀR=@p S/F(` ``'r `!$ Ln@ @T`ŀ܁)2at088 LQThHXH4`IR,C+dJˬ3rպ}}#Jk<>ޮߝZW.Ҁ&::EFͨVe W"AOC h^N܏g:xtA§O'Y_q?HHD )O(i+RF6* U o q,`Iv$R 0" Le&FQՃ;R~~b/nR,} ,ڼX3& ePG.Sz%\Bis>}g\tdc,]$@ (c( BՐ.xekaHѐJ !2Y X ¢PJ&1&"^Ej$dԭ~H՛Nq _y<xqyZշgw)LON&Px3_Ǖ 3VR@s^8va!}Hh8+`LD `Ttw'@iH xB"E.p7h[" CdroR fbi.Ah2`3_pߞC5  LYs(, !Qs@Mq1Pm`觯}: w'T0IN[v'r";Ta "4Ty)Fʴ"闛㝈=%ហ( `R!&A#` )!U |ßE+^| صpG1"L4krh.=K5%BUA3>0A !@($&Ah*a`e$3 ȩ!o`S DCwKҝ0Ӂ]J^DR!^k楘;P@`( iӅה#-DGb1,v;oKfJBW_ݣ,ˆvˇW0yt~.[)BHۻf&e_ɧ^Hr g=JcQB"Q#G}<GȬ,;P+"/~_{^a BC MCc ƀ G;e8Rh:M{ xO|Z))C\DMT k}g\ROu@uKMmH`h HP'j Q T0i2{Gl pJAHJ\$¨ b#bɽ>U9Lv.{WA7}209ӝ 0y6hD0Rˀv8PS0lCL@B lcEzଙ86jion~QiY]v7VDvN'p@,ⴁ[bS *y w.o^D^,#.˫^sH|>."#KPwj2qR^j(#\C-5K3#<7L+{"{L@U49fslE"FT*pz!'I;ߞ` J:7A mv)*$ ~y2@r@N"vf8~C ,q!;' `g!@$̶ NTb3;rKAAOHd8@ L4(-p=8H{"| l瑑Y=Z_(DTDiz~:޼nfPۋ߫I@0awF2`Hxz+ LD˽PH \"N.Yb//%eaH;(^ӝ(qq_3@uC+duS?s'tIE zW`[ &E920c1b.aG9x>K9}ǒx\'w|"<n^j-z"Y*O}b-j@+"(@eb6WdQ4$ t&5 0%0 &/Bf@d\(v1Ab!&T /*B\`;h@Ł;؞_.Jeu0Z(0 )lRhORf,N@i*q(f:"4Wi(_Ms~g:jbP߼<_ɺoqcgB/B wO]>,:%d lW&R( Td].$ @r/\w{~ P'TC/ C  (@oc_~^F@A ϲO"Vi4֜rgֳo+.䏝W_>gT|틇~9|e6a=mM:Hh`"PvApEUQ`;-:R+` M¸d8.*-}jxL@K}՝P9.E̊w](P'] dۋcvؕ DՔaT/,r 1E0fwD:j'!P+6|l75Y$1MǬc*(LT DPdz]w|ː=$Ԕt݁] Ao(dTM+e\NsAh h+((@BacXoU M uZ˰\=Z.ܣ |H ,B̃TLVb"h_'s[2ti;RZFËc=ܠV\PKe :2dґtpH*eW1DEP/Qv.< AwR=*' s3@] QXS!d@Y}`gU'h}_-};q.^} D "Bɱgƒ׼G C翶c"MEfnwz75E1S35BRA+ދ IDAT52$sL ܵPr(KH "PǏlκs#=0,S:v>B焝D4"&4e}l9B&*Y5ٞ&Pw(^ĤA"'M◷0|/AFp5l9>= W9iN4-T7fNh(RRTf44w =27z\us{5%jA1@F1#=Rc!XDP op2EVSQ*B{m#a~YD2QxKN+ ,i;7HzطwJ$Qřcń4&j$EPID aӽɏ S 5z$+' !Oƚ# p+@DLkz8Lv 'u$r%s`DymdzoK=ZM"52269瓞{<'^'n1>sDBp(QbN2CIھ[/I& TKe%0E3+x'D@KA!$މܔshl%˓o@{.MuehԍUF7214|-.X=otJNܣt+PHf쪨D`=ȓm%x*<t/?;#}_ ?y'OY`oC+0m,o9l vRPL\ف`})PW!bCyfcYέS >A(Ca)t@5Q$|cgYIWyÖXK  9"v*}M)Qa!jW͇vuY~w>l[_pPXcyIF5q>draVȍtNƞS 8쭹Ojr._U@!TZҚk9|~)KUȃ.3q 쪄#mX\Tu%UIQ|k_>GGJ$TUfFȂHݶm%Z@`*vu19nᏵm-l/Cs'gU=%\V&+7ܪ1I  1q~p;ǣeŝ Z+OsMC=XSV VZX$n' 'Vq^ $$$qEכ@ Bij>AqBsfl Ut,x<Σ Rc5"(JA( ơhMt@<2́T]9埏.rt,Wp_?9pjof(ŭV4Q@R}߉*9<Zk6leȌbV+i%S7r4: U]}v/pvHL.1" #B P1ba)RGZDZ䔪s A8bK9b6#Fab30T2 z9{w5ULD\kZg7C-Zs1z~:5ngLk |pһu,+!f '8DD5};}3 CL%2Hr$hje\T!XbV=U[pI()Q)Gg`jG6yJ{3r/M?|N*TO*0wWw3?7MtVk1ժ3 Yn^wa%)nVP 62!j%x&u΃9%I,ep|p D\zal\fHIk 3r4Jc~/T}_.+8 *hm._~؄~w7o'va>Ⱥ?R * n@NKck ^ 'ݷ* oZw Yb(PkbvbGq%7^nS(#bpM{:}\&wπZoDF1w]Gm x-x>jGWGUzc;7~.W pDKg\VܾzgwG~*Ϲ;ݹ1NK%ľyj*'o͓KsɹgFBEP3Sh x<+>7._21RjY܍z??61ʇՐ 5EYX`*=$sIl0@AUEwe烍-(,XS6s ༏T%*"0 ]&\HDQ;a1{{sH4\[tR5醣-\`p9KJ*;@S1 ZKaq߿n`Y/"Ր_ಾ2  Z v^%yNcqڌ߮>VFAHU` 955!GG!G&_ndfRryF}stnn4y}ևQo,z1 \_0n6CԙzqlVO] u+M)\!\ALA=9b&`fDuZשCX]]Zxp_K:[qo+_v^=]oV*sOLq-TV^Ɲʵmj xSr:@>] $@@t} Tő-3ht9xuG#u6_t)v1_, ůP򶖲6"W|5y++S T4P 憹L@ \ >+V0ђU`P3SLfd9Y>e(v1t)Z-%nx0_Ţ^uales?“4 ڔ83(xV/̈sWkS C?*x%DbeJ[">HI,wT_=q}Ϊ}n_{I_?>.Su1Hh;h8uS`u>q@ő&jV$Txln!*|J+#{@9.l)&t]p.ՓW7?Oy|~<@W$Ӛ;G \ɛ̎XDU *^E*0Es׃p'dG HQ'G6\RvHxUq\vcHb01P@ Mh^=PtFHp&ܪJ !+c3igWR"O*Cׯnabs9_UX::_u.4?vh h2_d#YEGU<A"40bV@PؒeζQ{AHh L * ъB5*kw 0s!o#P7RBaEaM'?y~ .mfGW`г}E;t i> HC,s h" &uEblóճM_Zx)w !tn6'¶C;7a>^ {(L=);,@O3ܚU-?zsjiEW*ri]Cn(tNTXG@` )PjdΜ=ǚ෢6R. q5H)yL18/#wO@S&?t<)-=Gqz8Vl ('p3c:kE, ]bSj5SR\u1͹;<_~_ /T粥jk6 RGzGShJ#R>Ԁ{F^1icIz#BOHa ` X̰B8tO(:BMj{?^yϜ?{#]~ݟBBa%(Cr´rv:D# -gd+k h1p/DHzkj' p"!#CXv~5bHDL,p1Gǂ(ֻmqk\ˉJ.C^m6zG%cqڈ:0R+mBj|p(VԽ@aeBY#Z߼HK_G H TĂQ?U ʎ:B\uw1PQrI P IDATW; >Z#'x<oVz缲nX}G 6ѩ@ "ؿ(΁#% 030gZ@1(pj%A*E__\4\M/C@D)-ƦymqO=1-iƽb3؈?q:@n:ŀPfkna90D%<=Z 'da89(;\r~ľޭ6OwY>˰~ TZ}*+]VOIѲsrZ/3P`4W 1!xs6%T[TALOFrY\g X牔YIkH^ *7+`;s+ۑ`pQBv@Kͩe^82 @Z)ȧ@ /: sG@ ^Quq|xSqoJ_~XFS`0Å,hVXEG?)#3'T][%jx[3O &pj|c ts`Io =dI23;E@kkZ0"uJF楶sY<pVɁB-d7r_ ѱJ@Xn'2/~i p\z(ß>OytӒTY9-9ƅVAPr "KZfCax3/.SO@1`p".L\%u\@AaAgYN93xYnϡ O$^'bq?g!L pɝx r% |1Ӌ}?_DR*'$$B퀛XwB%F}N>lӫkgX_ڙ\`N(`d#{qnY[-nfn)39#LCIB]C Œ`ئVW?;` xYhe*a#<89N/G wu *Nm&6TV*L3<BH]a w>K)?^Rqo$uGprA$.B"PK޹Q/Kvݧ]>yt\*dߠVVJPmCA'| >j W(u 3٤t]?1l v۫[f9\ 5+d0GV #Й{ˎXZ3h+Ƅb~7`ŠP##JEz7:z,&6?e]GXy 6X)H(  ܩ?5f^iI_3= ۶!xiS. 9~\{n^+6>2O{^g6H;3;T}ji?YAjL=.@qB.mxRC%53 jBJ]hEgׅ\~( bIp¦>ejpvQcYuՀ@ЩYǿ' K `V dg#>xt&ԙPctN *ᡸO^mfB y&^Z1/3B~PPY5Yd>(Q;vğ`֨.Syi<S,@l?ڠel;a9 `P2yavm gu1"nfZM;.Ç BfL\kO{B΁A\"Ȉ載|0-oԻg?3oRh pǖ+8-ۿx({chFj`%'E%fO  ^~ D,38h+25: 2- W6-iia!U>7g;9Χ ( a Fsc,)&,6pPpNY1+򑨎Ba޴i>eSSD+Jy?>OˑZ1J090cvXcf%H]F׍ Gu;QM&]y=HeSpZeXyH>61Téqj)'6@<|Tf25\ LP'duM &"vW۴Z@Dj^ c5d^+k^)"(ŖS/rWo[˫/O}si ч綌?Pp4 #fCf5f"~݇wX̜3=S"q >8@E8}JKбxp!yc8<}Tj!: 3x/. =p+@#g[D<?M?.^=^w`S!pPWVfp*M'z8ԖD'N0#k$ta^tNt'̵+h"=H\|84M`Qɹ̕^rCO>VUU,H<^3DC&=٥ݦ. #Y,y1ڱ*E ]B"={긓^z]k=+&a:2R`Qƽ܄@Rs>N}Q ^4Eޒ (Ҡ<bErI{GF9yNiSZuyX5uo=~ݭ.ovЧvo嫗}vrOS诂t"+ <rsxxcՊygo_=;뛛7?q?N43K_bE8H>|q_^\xۛ7O_^_o^vˡE^DڬY)̈hg' ͎$9D!JU*崉iM&$j75`Tc8S1eO 'G]Jnۈ Xɥ2'8#uX<"'YEV ՀwOiBRhޥZc7AWm~n\vwo޼o}8sUhF9;?iՅ &ѝGw)Ѭ:I#5e# u] } ;R"{'h9[ ]_nV>^R2lw=to~݇ǏӜ =}!K9/ !KOnv?'ϟ^v_VŰtR؉ "0GEVPQ# fv1E jFp'"=gF"g"@"!GbocJ0!xG jjOK3:P r5aPQ#69Ԉpyڃ!B5B` .uFśWkGB`J@]td rN L5#> l_9OOmz9ެ~{_^={ݛ~2璋w/_ۖH@!H/_<]xdO\n7֫e?tR 1M JH:|2LUg2WU갦MeBʝW-So$@yDu4,?@@MKڰ!UL]uz$jMG2$@>y@&]g\GY߃C$B}wyq!kMDLA8m1Q⺋jӼ^lnۓ_=헗/޾}ۏpp?, Pϼl.9Sׇ 2 CܬViw^//n_}~{srwy)v!@ .s(Hs5"GX)vl+Y@p/2e SUVHGyP+;aM$!`B$Hh@Am@ 3,B1$v}c@Q9z(ڏOSKV ]y( 614Z+t*~(jw5sc׼hu5BL @OpeM9VXCb!$b(R%rbu®0Ϸ맷=?__xilf[ ODBDBDLD :oeӟ^n_<{{fr]ZêB#{K̥Yk6>)k$L#Li֪sbaw"v(vM݋O=yz VeI )ȣ~x>8jƢW6"Iafpr. u͏:mcVm?9Bl XGu"<ǎ{$Y#⮹c!)T7漙[= \!n >[8dtwss3XNZ %^PrU$rj"iǘmzL4E-`-!@- JIMSo N#"k`b;][/T*)3Alp5P `f6KM9깛UeQyQE YXb-$j_f7Yq\V'~\ֳeյi1H?ksκϪYnjbx2-N~=9Yf/_/Nb=_ԛXl63k:ˆrH&w9-- \!Ʈ Mݡ^ݶWn/>]]ooOYUZu֚|/gbi[μUQ" Qdt+**v +Uȭ8i?*\6-+PP8NO"@GAGF&pA Xa %U!ep\HD]}}?nŘ& IDAT(-1{"%8 r&`[,l5??[֫ӓlmAp#)Z8Ab|D6|&#]߉SZT|" d;o7wwf_8ۜ6tկ6blQ*g[pKYV2jYa}A g.g1 JN4$-U 5l,gfؗ}0"gq(r; hC!|;*(:7mj{uuqDޝ>?%9Ye̊ 4wƛ&%ZAi% *PgM~ Ny3 +RLTH^7OLAӘ. c$, ")0$pRHQ¹dlb%Iq٠YF!%ZL@"|ﳚj~pCv}{Xv뻇vpvZoNd^,](K_eK]&{Trl5&ϜU(ScP*޾"M%o`&F1];yfY沍N,F $2JámΏc Cmst_]?mWf_<xO* - uW{2 =tbj 8ͅE:UmQYIȦYs%OnAGE56lJG;GP7YO|-iLh=A2 *C11'zt@۩FxӣF|ʓ01Xq>Blqlu}rzwsvvrsZnd-gz$TE "l$p.Ӧ2HIJZz}K!~;R >>^wgX+YK.7bƦxk۾C1áݾqdux'> 92u^f'slo@]Lw0vU~9#: 9)H$ :EKi}LbN轡XT!<.ך'iOZlb>Jڀe=I$kJPԦ 9 ^D PS%DUťBﻶ^/w7f\^ճrYWf˳fl55(Kk0i)_^д1 i=Я.:ؖ{Ω[[^B7^/>^}IT/1&98ч{+ @ErL]2R!] 9ؓ tF. NӀ 0*MDN d.]F:`LPg!$|(%Lh*LƉS!/ )e 2 lؐUpLG}d<8f1j.2m~iq~O_f}ZOUr^y.6rYA,Y':x o͗E~0z?m{|~CU?JK'$v˟ `NlF.ǜ}~j9Lɞ P@'U@xRU} JFUއuv{s=??}q9?_-_lgӅ(B[k]f˝3r"?'Av[5&(tHC$‡aڦ= ape,2&k 2\O>on >M{Ohvx}|b}RUB*C~0BKrLR6n[0<0F.9d\E“gB@KnGnw g`} fjU0G ..B"(O*D?! OJ|^^a3$cΪ2A`1QkZ@D{uMun#7`(kf2FYl|TED߉jtȅj{5v߿~1N_ͪugefa@XzOJ窲_SK,"ԷMluT"rZ a甚N`:)i=ٛJo}6nTFRN!AA7IϯEƨh|I 2`I;vW=w@*{4ϒ ?Vw!6Mw}"_nژe"ڈ\,IdT$}ߌз0x,3٦}wbYuʋR ]U,Jr}PէI`|v =U{i1oe+(5nfM+Dǽ|H&೎SzYYA`8iO^ӕ=qL#$ D.xFFGړƎb| {BɊ_8OL|VE|"HN5 0zUڍtVbg1Ɉ,qd~q?AT Ddb霫c9j^Tg''ݞv-7[c`Tը5 Z9 e0zRd"ԜHQG ʴl'sz >?A#<"1 Io 32AdȁϸL?FڑR$ph&} > %p1nZͫuYekY^Y"j,1c3c,$JKW˛nߎЄ p Im֋zY,i],*km4ĈPb~z\a]C!C%ω*ZqP0j3 'p <DN+L'0S@ZlV'a/w ,*q '`JʽSD48DP5Ą m>O@OWz)D1]ooN6O쬪b Qg,4egf8=Yt}hnQ<Ԟ=Wd,󼮫*eefzƶ~}QdXҴ]4 J/vac5Zr3EBYW3BxH{FJ@*E_餏pfϼ0c>RUu! iO7O_eo,f0 3, XjEԊj(C@B5)*+#6]f]E%֛^pd=Ħt#1ZSuU/b>;'a,0"P,FC9h2j ;$L0Q5>h|> =>?ǎE(KbF@;9 cab瑠jZѠYDJ#fA} q"{62Y6w0{կr*_q(% H"efDK,6)8 Hvd>a8vn{&N< PDt04o8ёIjS.391-M&7e~$[1xuc~__13cϔ,gf8]F\#b"8YHG_DFTM'Inܰ0V8?ڇ=5%0A=TO(@$Ҡ*hE^Baߍc?$dJ̆A;؀Háo˫жʿ6@VUc?pWÉuRRrlؐ!6l})N_U/ΖɺwnDD**$ EDU*ָȊaZq '͢yu{H`F`*i>(O\,1q(K 84w/\Jk8j룕qهB$̓ FE$Q"b{0.v!aZzmGư2g벴e6l|iw8c1 _ <%{sppqs]Tg<!-*WEܱsΡY9#;e7'wg׫O݀&8 BJj74#ޏb4"b5ym]8B͊<_|y^NNf|f D1ġnws}wwqqy}OoG|II iFO [$*R0z TWeQج̙-%y)|ś۽x"Q"T,QI|q=cϣ9XDt,2?H߯zKl,g!W}wqS+Q@h'&UP&ށI@G'D+*0*@m>vCO}:xCGAQ*xq$8%ߧgU _־!"WUE~v.?~jsYmagl8sldQ`E(yQb^׋E=2k0H*czWŧІ$o%$aGy1qM77޼ۋOwa;}2 4HM޿{}{Y{l99;Na٬z^esPQ%1p21+sYLqH~h4 cP䭏#Q fy=G6 K0'*m !J"( ;πDE #hoL@@b.Ttt zd %:ར! ;W1Ӵ#"G%E+4iGQA-{0wiokDb=ͫl*_wë^8~(|劬Z"Į2*HhuJ&rpL33kL1ھԵ컦m_ӻ_~/onhDϩ<!pSGv6zd:lͺ,/,Y 8A.,˒0y'PX!hHLraaH(ƴy~H4N->  Rc$#Ҏ\xE%ɝUy5]'k7ưq֙,ˬ.;Z6\v~W㏯~x'89]aYrefdIT朔r@'44a!L:0Ўw7~姟]\^5Mw_2=;Wk墈ba?r^mj-j^ș]fXX[Eb A GΜɊYɺ.YWE&\!/1d#'"Ҭ 0 P[0D"D60D"zkVQ2cHJNmXM !"y>iuq@HQ;?a FEL`v6D>aL<3u]f7erD494桽޽ӛ7?mv ? |>"^6m<({M5F=c&JDHx!kz(O׳٬Y*'ُxz򡨊 !hcCy LthΊͭq I/#' e idg)y&=0:%i|}(ܤ=è48@@gR5"#sE6+5fZLov;x={c "bcLU3~:f^n6g'/7W}YV欴*/,HMe 3/fflڐ adȤ>ҴK>UMhRKnmsn>~y~x뛟~zwqph >p\+^,.,wec ,1y.kcN*4I}8q|hanakB7t>8 ;յ6jˢU,ϊY=8H 㮏F@ 5zK #UX0(%l@ XiJ81-1L^̰X( pf1Сݏ&Kf͐e.Teʾ<{1??;٬׳լ\66J=Y18rƀ S,A+r>7UUy]j&s&!H`cC $ nбщ8 @]( LQEt^Cǡ|~t{{_?~o6/eb j__f]-g3SV#Q2Yv<*|>׋z^ͫK>2&Ph1`F18ڮ_]=8Tf]!lrEUGIN6y 4cZyNOGI$_ 3+y5`s>FX+dl V7/IQ &)H䓔cesZ4aSl f<' c`Y*g*Ŭa(eUً9}5A!gm)11C+*,0 އ C{>bh *^ 3-a!wgjC}?C~pu{˻Opqyss{m?dN֋O?,f-W|Y @#5ę%$$*al({0zCn}vnoןn?~?m&al~hwCllψLFFtu11M2aa<(IyI;4:& dQQ+!Lc9?BV"C% 3lkbUqqSZ3NLL5&s aatMӍM}7v}q.P4jH% &iu1 v C?^}a'$o:gJ0DdNdQڝssRfw$Q2"1ؚ;D 1mw]]էuGﲡSidá>;=z"Yı%,#dFZ1 8rQQ!4+;tEmHLAC]XS$PA]x8g d@7ҏl26fZX) g0 U"飌`D>?ݾo~{͡C׏>!h$(&1!RǪZ"Q?c߷~{w}CB&zS7*gc K\d;qpF0JPn3 [&#PH UԪ$cvi^]}{ӻ7CE]shHdmۄifvfSV"+2Sf T$?Q_ tD@A:Lf)uڠ"ܤ$)A '#Ihk@3Ba-٦(QET\7ziLl@΀%8 T !av{\o?^=~kЍ1E# B&%Cl jIHET~hۮp~:=C>o9;ɰVۇ]xصy7kjhɵ` JhΪXTK~4 ]Ӆv?ç7޽XEUTuo]lY9ɐ7 DA UEM}DB M'\,E"凐 A mj'HpHG0cH0czh;X)JccnMsx{ݧOo]m.xU5&M#S ґԨcccQD~M—`}_}lj Ǭ8Q€ m& PURpލ7ۛǏ7znmb5ԄB[|niƾA$AU\T0`9*2V,rgN)v2 "i JF"@a҉*F0(F ܧ6h$Ş`$p`P5]?3(M$~h ^_ysŻwW׷8NrbύD'rtzjM\ . 4Mm_z#o_wlON]fU95Vf Ĭ,ٻYAPgoMеm闧QDEShmm[vF UI!SH%*VT ejKpaLI;#Lfe@at:dv$4 0)+@^.AmX01y BD 1FCޗ6q$zDfnҘ.  uC)̬fFnVF MgDdy~ܽ~<r.3׺rSρxqXc_^?su65vhЪQIVBॺ!O7ۻ~8SΓ/$/J[aH,,}ֲDze' p H %^F p}EW/9ܱBpf Jt_܀ԅ́8f`r<çN\0MY0ꅈJGÃL?|7o?}{XRNus'Vp*>Cpia*RJiI?=\wA*nWp2vf6MK9a8NaqJ3^c2|@a̾XZ.3պ(;uD^VVe ,6Վ؄i/ WQQC!џF~»TTYC͝Xl.e%Tx"tZ1- + wjia?ݽ~?W_aIy$9">x.'K VqDJnuH @~j"#zKO%Yjkى)un4b5tUM!CiH@b'Dbm]/v(UHT#ZSy)x tIN '/l`vBC4+6#{ H륅Xq,RQKEZr?|7_Oog|HG'<;V037 X-@DԅyT? $n.h PYuZA' ؝z:T֫-Zu\mpT|tpft vLFLK<13Ra/%vׯja >8?yo_?yN>?Y<DR{댍sMzF"]==' (.pS4ɍ[PPNmiwy櫬WvNŏ|T~Y/TYIzDcO_\*Inu^Y,$$m%ny%^|UWyYto}nnS*O'sC9NMgRpvR)rYKgK{ t5`(7:YAd(bc@ӵ:QluOaq%׸\:}}$@=[YKcԓxVe1"fjIfC9lhl{W&ۘ+8c}|xo8_K٭-giks9W:ԗ/ Ukp"BY*A(@Rd`?a!?}eK8kCPiN䲚ΧO I#L TJc}9'ot<3)z]Xu1Ɛsٯ~+t ,ձd1XTV!"2$',BJz V}9CiY rQX%鬁k Fr{?M嵫U) S Rq,BBla㢽@Z;"u_4u/`j NB`'A*dށ̅\!HbkDū#M0;!D9"r*'X$s#8n#?o$^A5l.z7j]RmN7 E6:72 0 qT(P_AOR*sCPF::,($5l컫6IcJfڰ2Z{O Bxꣴz YjbZ1K*CT6O!;d!m3O` +HU\U}wr֋fZdDE+h`%`tf2 hqA3c󒾿?xՙd:_Ͻ5|%ȻFӭZlݻ>Knnjڻ{,,8ֵqܹ W&'OY@w f-UAŪ_!0 ހSJI0$"z`wY%ׇqr)k,N!o˛T3e WPBBhTf@Q ;\ŵ؄jpU»DW$ @LAR0&f ɇ |Ʀ.ˤ8NQi=jJَ8m*e8M-x2{z|d JN !hTPHTU6] 'W`;xH dA6AF@"q"~>8ogbtpRpXglq |ҽzQWQ{SM.uXبϜ-|Y;8{$N.*bI;9hBq^ ~4Q$t q ̶NpS,Td( q;ތC.rF7f} /_틫Q,~nͽ]vڷ@E}{>N\O:%rGoeU4aG麎z2\"j{r l5 )`Pu0r%B,F,OrjAv۹l%؛ey8?<,)?_#-4/O$NKz+*(H԰ML=th@  %HUd]14ʉum"̪>Q%-څ_mKƷӜ=|sYR>:re<ە^+ޞN&UUlFPp3DG "܉   ž F~pZߔj'ɮ_[7h_Ѐ!x_ W5C`vYq/{E^D(+RW^d% K&,n~7ü{[t?ۂ@ÿgYhoܙcG #BARzF$v"5}'u#ƴDGQ#Xr٧RFw>@ÿ) 8d[RUGٹI}.^mDЩPITŃj]˦wu%ӿח=+@Q 3TfwB=6:uk£/\〉]AB냱jM}uަ>KM>.v+–WnvL^/c+~$lĜS$;50/u<*Dtwւ@G &'G uwNyU^M! cS-xGC `X}WABܝ-4Y_,/$pKCrX= yCAF: *A䗁#`Z>vH@ Pd$Q0Ոt8T(׳1u6(?EdN8e p v7hxz] d\!Co:g#ľpT/Ȱ,e i!B`̵@p:54hKJRNxt`*<la-^nbA Z*e1xSU*ʶc$KJY#eLHEj]?T`dfX;4|$pfBp90 벗4Vq=IY%ED@y*0J@GYfGF/*$怐iV& >DA"Wyq"a#T#βs{tЗ` !#rt[2jRN#*ND"|oEVcz["QΎǝLW9k?"#I&L߭٥F Q`$.`qCΕR$+ 6[ ^s-%aqb M@\b9:s7ba3Z/ݶbuTՆ(@yoq2'>/Hf @.%)@'ל,\WnvA1}.4NO0,=?ɂ54|%SPrNV0OǺt}U Ejv`e2/.4\g_/l.f.fΗ2QBKAi.ŗbXrJSZɬ~I7hGb!'?Ԫ)e~ɧiI)䤑t 炿]~ucç':QUc>+Jn \<3-h!== @]r6ϋ@?F PxRL^s 7h}$Z7h>ϋ4>cyy΃'v \~ 4|F! m~CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC  IENDB`(0` 1-  [P Jye= X $~D5 2.E | 96FVkW !}F'D2'O { A ^I\C Z)O JAw__H _44  D8u D__Q2 Y-:ox7ADN  ٷy#,2V". ap2A<"w ,iq5@C[AKQ^=JMO ݽj"JUw"5-vLPbycrwo7ADN '5_BO\Kw"4=+S)e29=Kn5ACZ %*0du}Vhw Aw"8L^G}SV9@57ADN 5:?0̦sooas~s?w"6T__O,'_C 3;?Xhp;EJgg|%HW[C*aqysӛ㍧z`s8Iq5 D%@SXY# kNa}ex#,,9  DQWwԀe{GWc : :879 U$1fuۉێۙۨaty , 3w+BXH. E  pt)A\__H8=  /<[_S x8R__}_1. !jI=[__ba@b-(ROT__H8G`7g4 ? a@G2 (6B+ b5  RP+"6r$$(S B9t i/>.SbBH6 ( H#)Nk# H "/ L#V/wk](\<* M(,|r|3[6 y T4\Pql`o A`0 *#Z' "t- ` -( i #B#{ b /\' 4 .U-t >7n) i J#} / "jB!z RZ' ) 6 . y V( ( F7n( w - 5 , w . & ' / An $~ - r Fj W:Uw Up n Q@2 4 Bw H\6 ?, ! Ms ) 1* w n 5 ?=2 7&{'}. ;X 2q + 9 @<3 $~ "{ , ( r 3 ,#y j2 ,&|5 B!v=B<2 !{ [ Hi H;) 8A6=?, KW) ) J2;,H,!r(L/2 LN. =>  L<s#<<!E  ??? ???O( @ 3f ff  33 _ 3f S ff S7_S f3 S__S f333333S7__7Ssf33sfSS7Ssfff3fs S77S fffs S7__SS 33ff3f33fS7___77 3333f33S77__77S33ff3f3S7__7Sf3ff33fsWkw S77_Ssf333 7_7 333333 7___7S 33S7____7S33S__77_7S3fS77S33S w 7SffS / ?Sf33?/ ?ff ff ff ?w ? w f3 w w ? ? w / ? 33?/ w w / w w / Cw ?ff?/ w / Cw / w / ?w / w ?3?w ?w ?C/ Cw ?C?f3?w / C/ w w Cw C/ w ?33?/ Cw CC/ w ?w ??ff??/ ?w / ??f33f???f3?@_(0 N 9e c4n3;> i$ Zjq^pv IGSG#[kq-bpv7*7O!WX!V򄜥 }sYkq [1\BQgWgndv}cTcl/G=?Yjob,47MZ_EV`qpn7Gh[.IX^PLUY~p2;?' U0 (sg Y w,& H>B.2s lh-; ,V2:(+ cc[5 7. ' 0 , -Z=:M%y' .3 I == 4 @)Ql >)BBK_o//__(  f 3ls_ ~s & SDD Sn asys恘0$$Aٯasysnfٰՙ"*HUD &nfasysunf)$b0@&z nf^o{"J5:Dmnf ':_b_4f.3: ^G&n)fa* )sT&b 3 30 @&o&|&|0 [&|(f50 5 , "v&| G 1Ja(f ???deathkiller-jazz2-native-2a7ccef/Sources/Icons/SmallLogo.png000066400000000000000000000045361512772601700242150ustar00rootroot00000000000000PNG  IHDR,,Z %IDATX՘wpu?݄$$K6!$$, M@8;"Eҋ4H Ϡ D4 6((ˆ Ҥ8s!87ws`=3?wvy}n& J@. ^-I#!rƶsěrxx6<)BSa[鈕:НPj'`-Lm*4u$8YG<쳒K[hdVSc|Dez`'Fe}"׬rH.ꦲ/LsfmL! ,Ce/sV̭ieDT%Ͷ>#WW%DcbRSrέ'ĒpxK[WAJ]f$WmDl-K`eȑ?/i$Ǥ2u uR,CbD"{#ȚJ#KH95.V%)r2Sdj锽Ok#|$!7Y.ic}lOD90JIr>]KrM#OD5-Qx!ED9FDh:C }MN8٤K~=:᦯{sHcD8L dDeT1 DiK dWMFsg1QljN騽G0c 6qgh.DI ?J t~yJ$S0 <ȣ']cdXQ?rX1{\?4hP_(#C?h z=L%3'AA:0!}|(K59bު0A jKSJTd ڏXޅrp} CTRK; 0abWGIUߤk9(ə&FEOT&v/U5Ь&ޫ>IAs|gAES^67]lVCG@=nYX4m8tgzuڢ fm۱% sܘ%z{䶛z'7f9ۛkܾy[:nڽ-Z?] y]kΪCU=R~sA0W &|wCSC'YcU>fѪxt>tݭXCod,0/nǚ}/ulۇvl~S#|[^>0-yy~n rO:Wԧ72jkc*О M V jgEP7ZdƂ3 #]9 䭎LϞgkعvs{u`r 05;`w[hY,>.ԼKCz_޼ݷ^kyے9.(k.ݮgΌFk}}sEۋo|q1Ao ahl|+F'C-۪o놹?Q\x=ߋ3GG3_zͺS 7<0511,551, U"`V7'?ͩ݋wEY6.;u s)WmZ>E˲Lj4Z'7#].'k砸A ︒zu X5qf|{ymkBԼ{6p+ .e{PA&Hq%n#Kǻ9U=Pv_ } .~/?gIw+ ~ 8ԋoF[2w12{(V~v¡D+Gz{!tYآ/`+ ?ZkIENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/SplashScreen.png000066400000000000000000000441311512772601700247110ustar00rootroot00000000000000PNG  IHDRl,~%sRGBgAMA a pHYsodeXIfII*JR(1Zih``paint.net 5.10230R980100(XM ,G.IDATx^ E;}! dBa@ (E?rxrQD/^Q-Ⱦ/ !$ldLfz{z{r*9'3ggxpt~OAy@F d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l-# 4(rw [F4iR|N xxw2:" Y'lHg볻5z  % qz䩗<OpjӾi@KЁW}'ڣXEh-e~:m|_CxO}sJmm{WlS-&XJ )Su=l֩2a/d&lNE:# :̫᷷Cș{Փ;]k'hcu(sZ=~pg~dz{1(d,BDZk󝦵AG<37 |KL6l!l=x֋ Ͻ>cBl->gwޖh4٦s&܎= w!`+#b7+%#=ASVBUۋG +>;> _-ؠEY%ڣYQ ^d&[r ۣ4e,T{BV&ot;KGuyDR= [)UuDˈQ,Sأe씢 ^DgjT{TM*>lekӨ>S;Y8{4ekZIt!Ӝ>:We_> Sk:XeDR7 Wgwv| 4wuamst klN7OzgjۛL%٣)[ؠoO>9 ۃaZ}Sgw%/SLe3pmwj4'ML6*jK!/@Oùeչ8)2 Duhh7VVfJ{lД+)Soz@O+Ӳ꠱-ƦXAccu--t \ [O=F={D7G2DvAsmQ^۠L-e :M5뫂|[JM٣Ug%u|/ L;4d;0X׭OFZc= gΝn˾f0_>gKrk5Cϩv/Mv}A&M۶-Dɟf~_;MgDNh#7 }(Fgyվ5Cub $ZBtB`%q[jED 7 c{T۠uի,$l\x–qNܢ[{Tzy[^u82 R9a&n/A ֶg {/zem_s>M[7ؕyk}h_"yzu{/h8lj2aSn6G8yեֿ=Z;qcnw}ds? 8/YZ_e Dkmq6ZZgY4 sL1h_AYgJ& 򛣁h z7+Vcg9tsP^4mX4hZ4DY%  [6;hy]~s`M^+׹2Oո}=3s0Xk3&kV9" 5jؠM赵-Qy~@$#ʏ^?K:lʧ`Mɡ߸7.mA ֖'9O]9\5p?տgvNvgl(~j }vGNmϢ Fc_߬icf_ч>O[[}U>'Z=hV1% 蛆Ok[g}j+㫿I&Mm`FLYfjeigl5U~eTV&M,kVO V|sWNAޢstx6oZ Zf,7 E^ʘEx–A%YeBDϙ\/s0Xk3?9>ZS)TU9qTۂgV[?YϺW}vDŽs;oӢP2kk`M}Qiο{tPul|}y]ywIb5-ю^@2ȯ}m2C/8My-\ t}Vs8Nm٠TeLk ye}_Z:}}&:έpY<-V.W'}'Nr:2Ugw\ןy: ~ ,?6Y!eCOX3|;+} ,^@,em}j+k,-QJk(cZ֠unJkzau)+V)}H.muE:$)']}\2FcD}5'# =MMMM`B}Md1r[l jmؚ~❚`I0R\_W}c܀eshΞ3.ZCۂz|- 6e68o2.o 646 <[|8ӮXvR{ VojwT> .ZmY:h`mou<k7vkk T;.ˁCsCsN$蚱jMC8>\i G155j&[+%5I{{++{h/X2ڦcrZ۝S=A^/KXBϾsO;Wg>pչs:'Zzotm5݇3O<չ9$Gl~o*Qe2yf'ԱU>WwcYmˢ3Dew?M۾`uesIs{+<aWFl;@_K|z>375֭FGk],QĹkYZlJ7wG%~]g#LOAUb[ݦ-܇T 3Fctd ׮ +V۲JԘ21 ֕2!p\:)AS㩷lSʒvX=Ϛi4Zn+zvg~f/Ghtey٩Kᘾh ~4Mp!Aa0"o6vpa67aQA0OVk`]mTmȴ:xǥ>H[bITUKmA~| 5E=A+-os_[--GЎo(Nޡ=5e[MPs -a06ԟزz[~/].3{2<ϪuѼs:):#{H[?i=)nUufN=lυ2{Ӭ>uw@=5gJ^!?=mի7<M7!o;#/t:eֺIzϾE_Zٯ_~ӹݔV9eFOD{[VUhrgj{gZ;e+y0 }yP}ED{}Z[IqS'3Dʍk'׭ |fu5ը)u)U[u2~mjMNAuҟhUnߴ~:SFlܸ~==*h;uyE2y,c}.گND%!mٚ;txufCK]*nbuc}]h~`֨`]]Klf)l:Z/YmOeƅF'׆u >Y{tV]3̾bAJ鷭zwml cClqV1i sF?rb憚ڬVSUY-x) N~Jx;#Uܟ=6:Ft%7qUGYyobl=6h0.soV:W-lEDˈ0gMVe/zժUA[[Ħw~iG]6wչCwzǝY_S4hm}HYu7\~+ڠ}QëVDnaa`M2+s€Eʽw #yUm^{ג0`_|*:21N 3g2kyo&l7аXxs]ӟ=vvyneIu/ ۖ1CaPp?9h{`x]'1+*#u:c3a&W-z~mi~|Τ0XYxg:6 ФqX)w׫`M*;&M Q>/ix.5 f[zk=Q1{j=Ggss]l{9jGα3buoϞ3\vg?luYEcUzkz^.+Yβ<8g/[hw|ܸͼ|gM_nuVqz/]jmx2RˡAD}s(U77-SMAk>z*icuonz u}٣u궴n7MJ=ErSNG[~In[1֖^떦Uj۴Jթ3"7 LoSzŮlH-彐;%7dvb|}To ZZk-XfMPlG 6͘a2tmCko Mv%P$Aew~vjA, 5=g j_%$jkn;; ~%{QCf'Dq͵QǖZCGDuz:fD֚>l4Zlkzϴwd&kv|K ф}XڰGOMq{/#Uk+B(SGtgvm~ŅVc53KO*\ԯힵD:n][m*AR_glLjINkKﺉjϾ#VRun5(߬PIGk?w{~Psrz_Y&Ֆl]oDODkOolS5&/}iVՙ=t_?{%zi;DmL Æ23etI1fJN>jjTԿNQ[RʑN r~`M?,v_vkci5/ښ0 {rD= @IBGZkBez~ڷD?sӥ%c]DT{+zp!V_grdG|uv)G9}^עHVXêLO|Ae-sD/fnʇd}Wse:+vD'{9tl,L^_ [>z? ˮܞ{Z}`ug RRKt;A:Ԗuaˮ%uzm{LIɸbd)5ھ)*lMFٗ[ZS6zu'6k1bŔ ,Fe 5TqHT|}ndF2:{cjݖOE:˶]9׮8t|NU}>j]е}D_VN=f\_fb˂ n _KTg[n1ZgUt=El7-^d{pAզ( '@ܗHmlJlwl|2BP/n1@7JƥkA8+%Zee.{|i- kVl&׮I`6wִ#޳vp٠L/=f=*})A}Ws3R}}(7v˭}" }(W+}b>iT -[}=2+ },$ 묵=z1XӬ>;ʂ&z *7gO suE`?΋;j݇ܲ ܲY'^2IS/xj"e* vw0\p֘/[sHXtVcbV\;e yJ;OR3|TUitwZwc#O.7u߾Fs/#njo>{wRBN: :̣ jb[m_Q%}KD},5+h+-~e*Y =*^tI z]Kﺢ׿A!郞zͪ`soNy,QZ֌Xm$;Ug1i5PNjlSqes4m7MuӺ~*UInB1Z(~uM5tw{Kw jݖj֬1?5?&kEcS]4D} [ WBRP:aK\qءTIyڗ9~ty3R>뫇_-Fy) ټv6^YWgñzV Kon3ϴKW|\|4pܹa&s|)4מk~B@}C)$ _ ۣY=PWO[ Z][ u/I&V>in8[;+auYߺDyR>}/ް}?}%oӓ]i]Ycz]TAnOF7zv}KZ|yka ZXjH.et#݄HFN|T97eigpPY<{>o'Zg]y˴;kin"[Y{ZOSzYe9/==wuk6=_u:~&zB]=F_gk'ϋPޮevzN唲Dl!ɀ[kPILiZ??XiBj*Ouj{WYetWr:%vuh YW~~t1Ӿ:8"fhPPdgzƮ P^;}}h+mglÇ>V[v-oj У=SL?|Ӎ5ϒey`]mKДr}Pc]{2:MQ8[B{>%@@l|E۷UnÖftɾQj;XQJic@bc~V5ܾr:ax[Ɣoj79ޖ7cΩimn| s d˝ړvi%`1sfPAQs͏ck9A$Cmf^e^a6s=؜LOUr57͟ܒuݍ>Vso6W%7b{ᅠA}x=J|_F26z 1#mJ?5 ־5.L%AvOw%82m6l2?̹ɓm?y^9/z}竘Ń% ]{[M Ve.pY9VYQ]A3c7gle3mG~=Nk[S[cuZ34#W;E.TO;7)?qGYtYvvY实ouٝ_?p:W&͍S@1ә*C6&k7z֞ΦZFjk7qnmwauN|c6AYϸ:Pӱ9sM_dY}Y}gp[w59=]jYf~,&:K5)w(+O+=T+s%Lc.jmS ju:;ҝr?&~Ͼnnm_?du*]M~K; u;gD೹s|;eW_muGe>QsGe[7SսK]yt\jGuej+}oW46U]g 4[cټjmi;X[l27cO՟ڵk\Slڷ9p{|,>RrnWz֬Ze%}ͤ5cT~ ٠U&@O?#%coA&D#Roc=q*Wi kV,Λ ]]AS_WkfL 爠: *lkjk6mve nem> Mi:u#beLL4lu>eAmز K@LbgWSϼ_]ژ:ڸ۩1=5*#Idʔkjڴ{ýYK/-}8帝 Np>e |Խ5qi>wQdn$y~27|OsC oG^}a #7M57#!6+ղej_~MqKiQ[{|7p] L.~oIu>\qApԄ &XSMAv~Y;xEb{bҸF3VfY-瞶?rKPkU+m>ƮX>͍nc1 WGUj3(iH(1rmwvJE6 N3^3ѣ]L9ۮ=F雩Kߍ8>jS>gsk;G}4߁i`|^dLdVX2v=HgJc]U?*{6V>׊GV[9q5 5X?o-⏸ummLp7jmqlMʜVDپW1ѩsN{۹_>ISh Q_>{-u\_Z7>VK٣2QmAdLi#71b?Go׶l,ļd!Zdk'gb5C#[3=omm\]hNY?)KK;׭6OfBKW[x)]2uu=jҙζ+zHxRII̶Bk5o}r]9}^8:f|}h\KgΝX}g֕vciD1/=*X{C QFg~hɘomtiZo{};\S//M5Mg?7(+P[?\[irr4(d իĦlycU e;o,_gޣ0-Iu;|b=9וH; 6؈pqYXl_bIsM,nֹtu!hSr771z/\XOa{Ndy8>:2Og\lzo!K]ŗ}l>OP/:{+V#g}d|Ym m)OblIWjYm17|ҷmC[n JvQrƹ8y'L[ 3F8U[z.瞸tLٚr׶ʼm*[Y۾n 6_{9q.m<_%k l,^ R=Zۣ}S:}sN~ִIۺ?=BenpspZ7Sۂ*R[I5 80|y K3vsnn.f_:y=2Y7y/_/YX'V=}Z}ꩪ~9skPTa\>z%zƴ%/Y~['^M튕>\>J#mi:~:k8yÏ8{*R[wkLg..[95VV_%}ZOY_c:+!ey_~^ۦo*3w;wkXƜGym% ӧuQ -QĹBBZoi :>*̯CA:Rk-Xh|]o/sJ-F'tq׊\}j12R{*tm ISMi@`l[1㜲L:N[i6 vػu엔/z.oZf׆Hvdjgݮ+_6w̶d_o)[蜦ltu֣a:Gڪ3% Boc*X~}p :vDo}sY@&юk5-Pe\-٦޸-n!,%<iK1b&_x.ٯ֙ L+ej}n'oĖ27kW+Zl=D\cqvN9Yf[ܴՖB)rn|> ZMߒ ֆKwiܺ{ݦ&^rA[2sgɠ~G؎|6Ocr K t衇sfO2R7"sHkwq1]h:Mq߷.J׈3'8[K|:+d!eb|M7scu3jm[n d?{q{?%!G)[Ai߄uXT[;das}|3R}sk%G675OPت2g~[Vwo}d%m9mqDuFgpi9Wj$ڊZ3mi?y Q&ZO(ɑ,T+)/NsP㎳ZC׷?pӟZ3kZWǿ[믷ZomZTv:Kd gI{KO:_gՖJuqP7ohI?ccKPԱr>ЧS5L[ʆǥ׃7S|kXzR㠮T=f[ώ>v}\PXg&]XU aw%ǘɌBsOQ[+G, /֚6mzn󚬉)in g;iZhQA+M[~ ܩF6̖_:d k^3dhe6?9E9 L [m=?X}>jg/j/_[W֓?BחT榜dq(z9nU6bVfo~Tf&ոݨVK2o8V&DsM\%^M pe~hJp_՟ωNikDk}OZ]%ur/]jz˴hD&pM}sq`&zmu[GT hF 9A?:ec»}=tɯrg&'GiKu*SU@Le=~|6ΈLTHLP<-MYVm˼'ⵀu=z_Cojk=usmx&Uǧk}c:ybR-Tyݖ9BmU^߾U%ݮ?u(D3~\h|3Foxcۖh'l}tti'wg-efv.4ǐX礓 YyN;ΚT\.MevǰΌmbVWk2Cmf{¬;d1s纶LmۺFV㲃VkO2m%d"b^0pdbԵ1/>*сm!ߠVяֵ%CZt&Ɋg-m.#%j_kK49>7_^aymW^zպ*; /:;z7t1k.ꔭ'UZjIMb̙-?x( ND/|e39c_pzlM7`)5(Sݦu{20Ai( Uv*CZ7lXSl]{jGY2&Kڬ?D/S+Ֆ }2o GŹVc=We^,:u|9{?(]=~؏u>}Y<֐^dɫ;tџ U<)kIY`Z;rLky'' SIy\T[eZ%fyO >F{][=73C=2>q=q:ѩDXveSf%Ǟaԓ ]C}MV&LNUϵgϻe{\kyzy*˒%|o@`fh][m.-K@ӳ_re.LjAyMyd%֒X~܀~~GTV#>xɉ}wwSm}q;W墄SNnu%y:.c;zA7 5f#8k5_渫L_%'oxn~ʌ ><7tHXG$مs3OJ'5tFשG2S~~Ď4=GʵQ` o\-v|R^Ԕפ؈hQh˘,'BSWMeN(ܖG^k[8KOq̙3"?x]s]*t<Ϋf^x4tw~ٿh.hfeKWsj;H}meg*;LgMS Nw{e27D5Fu=U&dO^T|O }햪SaV|}6lB$6SOW_J]?)KQ]T6J=ŜG]FoOYMxm1jo>Սjb7κoS5o?X O8ţ?)H#Joן)S٣%W@}}K2c_jU^ר,f=0F1:&es>kK+뷖8x֧pǧMۜvt¶y=aL3& oP7+'Et]2wnc57-^ ^K? $R!3st_} 5y{ ob{UX˜+K>>Bi!_lPX$ɵ"|=} }w7Kk ^bΣ.I~JL}aŲ=o°.yaC\KqKor2 Od a\1VȔ/MH[ӦѮӴL}@SN-N[-TZ.3yks(8sZw>noݝ h}zBimZ|;KBt[㜜wCpU.^&`0`^]h?^/slgc;1"ݧ=芭уj/VxT -uc_[gW}Ͻص?z>StwUڋ'l}bmWsU T 췊~PCGG?IHJC[qlo/2[kζjgiWm[o}z?K'lۇ i{4=$lX2,QVH.MG0pྑSU$bUz+~K;+Vf9(uqu5ٵ}NMgږӟض>ʀ2^e߭KEE:b1_ۖt% )i۩ar{JUj=[{}ۻZ"`>L֧@ Y] l=o {Ra׀9[;--)\ض9s,WЋd6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2@!`8l d6Cq2MFAIENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/StoreLogo.png000066400000000000000000000051311512772601700242310ustar00rootroot00000000000000PNG  IHDR22? IDATh pUƿ鞞Mȅ!& 9 r("ʩ!Ir 0Q9Tb8U"r c!˩r Vt$UV fUk}?  O6*&ƴNPI& L,DF;`0;ݰB!xD ݈K5b+nD.Hot wVr HCWbN+ŁMh{.J~$,"|ަho9y԰PS)wQ)tOZ<ʂXC( nB!S<_D{4mi!Cm\ բV-mKky`[x-N2N%?@rIaGfv3Wψ=\(SIiVk*3ZR fS.K\L_Nu]i0Ҙ *0,=QCS.O0ʋqN\Kjq ٔ(by9%YQ4wүgGS~géΉ!dRڂʬ(LOF: Q:C̈mT3ZQCyt(tOכ:H[iID?:[5#\YB¬a.):9w&1 M~MfѺ"|LҮ a~c"16Iiua>hi&K&heC5BNKh.S`v^CB%|`qϽDeL(45lkW`^WF z@oa-6@N-hI!`MB[ ZԈ -cPMl@X„ ?:l gJ8 *T\ˏ22LdO 8 KflN*3`h)O uC}{[qe]+pe(?8>A&\0T R H0GG[b tpc+p /bafyb#BD^\z^CLp_ xm7Ɖ(vF[]-:2rTp%Ĩ;Nկ+fI/.hm y W|q5me`gjwq|H4bswA`J͜ &c[?+)@Dִ  D#;iY ĖhpI3Մ "P !OLmVe&x`>_IiG 'jc;8?G;%v'ڂ3|q gҺѥsYsj>ظU5&漠(7]Lyj9i\΄5+~Pgo]iUچnVfKtd/'W>#MXӓ_Fige WsOL{rDX~QU>fmggUKΎuܔ6T` 3Y`e xx!sn= z ͪ(_Zr|Zk벺]ݹy马NGl9*2l+LzTs+@RO T9f\6 "l V߿iyxwOݱ^nˈR7mz7ԫMC{Y𣦩v;#A> >V'9 6EO.inK!~;7\#=} )~h߁X^RPvlze6NșV"r'XVHvpE80E"뭪e$U]4Zyy3Wʵ Juv$-- ;JNVˋYwc֞Ǐ`jzj06࿺;,s gl+x@v_{_~??^ŏ6/;jC%Izy.nys{_4d7 [fgխ0"ml=WO_;/Tb;vX\ו^է8qhZΙ>Dhs| (Ȳ h1S> v/M7;:7W\d ,.>fÛ4lHYSZ8UjGٯ*͟1"Qqe't'< ܟFكYR8_V}cSiܧO!FLji~EF3?hȸZꭊ1|\pM A{p $G, r4U83.z8=ΐm>dWࠖ(౮#x qQeX)wR"y&le`y,.3 \P!L,hnBV3x ăS]⛁)6TqC@"PlƩ `q n'<{85gmqX*&5EϟUn\fh%J(&fC{a7 h-QvLf9Xan IENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/Window.ico000066400000000000000000001664311512772601700235640ustar00rootroot0000000000000000 %V  % 6 h.@ D(0` 1-  [P Jxd= X $~D52.E |96FVkW !}E'D2&O { A ^I\C Z(O J@w__H _44  D8u D__Q2 Y-:ox7ADN  ٷx#,2V". `p2A<"w ,iq5@C[AKQ^=JMO ݽi"IUw"5-vLPbycrvn7ADN '5_BN\Jw"4=+S)e29=Kn5ACZ %*0dt|Vgw Aw"8L^G}SV8@57ADN 5:?0̦sooas~s?w"6T__O,'_C 3;?Xhp;EJgg|%HW[C*aqysӛ㍧z`r8Iq5 D%@SXY# kNa}dx#,,9  DQWwԀdzGWb : :879U#1ftۉێۘۧaty , 3w+BXH. E  pt)A\__H8=  /<[_S x8R__}_1. !jI=[__ba@b-(ROT__H8G`7g4 ? a@G2 (6B+ b5  RO+"6r$$(S B9t i/>.RbBH5 ( H#)Nk# H !/ L"V/wk](\<* M(,|r|3[6 y T4[Oql`o @`0 *"Z' "t- ` -( i "B#{ b /\' 4 .U,t >7n) i J#} / "jB!z RZ' ) 6 . y V( ( F7n( w - 5 , w . & ' / An $~ - r Fj W:Uw Up n Q@2 4 Bw H\6 ?, ! Ls ) 1* w n 5 ?=2 7&{'}. ;X 2q + 9 @<3 $~ "{ , ( r 3 ,#y j1 +&|5 B!v=B<2 !{ [ Hi H;) 8A5=?, KW) ) J2;,H,!r(L/2 LN. =>  L;s#<;!E  ??? ???O( @ A |6786532.-*(&szuu 9 j f JUY}]wase 3 POSW[z_ 33333S1JMQUY{^ 3sfPJ|DHKOSQ 3fs Pr?yBFJM~E fs S1juA|DHj< 3f33fS5T f33S75T 3f3S1yhyhyh 33fsWkw Sz}33 7_7 333333 7___7S 33S7____7S33S__77_7S3fS77S33S w 7SffS / ?Sf33?/ ?ff ff ff ?w ? w f3 w w ? ? w / ? 33?/ w w / w w / Cw ?ff?/ w / Cw / w / ?w / w ?3?w ?w ?C/ Cw ?C?f3?w / C/ w w Cw C/ w ?33?/ Cw CC/ w ?w ??ff??/ ?w / ??f33f???f3@_(0 Z ;8;KL^b_]{xv{xt|xu~yvzx|x|y~{snnfkl~UUU kl IlzTYz_Zkq-aos7*7N!k}MRXƜsYjq [ko}EIOvdv~cTcl/kyq?yCHlMX`EV_qon7Gk3;?.2s kh-; ,V29(c[5 7. ' 0 , -Z:L%y' .3 I =< 4 @(Ql >)@@_o//__(  f &Xz^tc{^asys恗ٛQV}\Qnfٰՙ"*GٛJOT~Easysunf){CHMj< nf^n{&r~4f.3: ^G&n)fa) )sT%b 3 30 @&o&|&|0 [&|(f50 5 , "v&| F 1Ja(f ?PNG  IHDR\rf IDATxiq,Ug6E=ggJ s*3"}2}aVL%k~V7曛_ ж"me9j4(zow"|w| |6p+\/b0͇iT_ևYW[y[nG;p{xw1\/%ǛSw:k)6[fgBޅ:\FMD=wRStjtk<*žR]EFKy DY<0sޒtZR[Od́No°)R`xy,7huQlчǸH$+0\z TU/__lų7틵ěoVloUHw\4l2B(t%6IzJQ۔THч c`+$r1D0 Er~z8_~,12R. kJp@C_q[cx^m}nfi,9VA9B ҟ2 B&Ԡ @BEY&@'X f$fIG*k&'Ğ2Q)TKT1Q3 0K>O|wkmmz1[[Z}yӇ>| W-bŪ{NmvԗmZ7]M^2[&HM Ai2 d)A&,e @pE`&P)`@dYAUxBC(AhueAz" ;d1EPAIed9e98ÊyiN_E7_>sX_Xϵ W2)/jt+oכl_7x3E-gZ @ P*@tG@EV fP/T@C!)///(2: •pێf5JRiP BJdVXMUE!zyBab(fYEmelz5N6 ?Nv֬f}|l㇔+\?ë_{}6u|^xwShM,ܝV:P ECEL{"g~%3Ȁp ŠZ&J+!R(a{@DTVCz0+ĊH"*h4)*P1,L jj(ư7434h֊j)#{욦WG@P(q1O.h0fDFѩHȰ &`4bs /Pt7;p~9ye[@sO R`bXQH*{zae"E!R(T8tQfxAP`#rs SͳhύM>Ua6WuRpm^o҇۶mM.eZRG}Sd# Y ԀR=@p S/F(` ``'r `!$ Ln@ @T`ŀ܁)2at088 LQThHXH4`IR,C+dJˬ3rպ}}#Jk<>ޮߝZW.Ҁ&::EFͨVe W"AOC h^N܏g:xtA§O'Y_q?HHD )O(i+RF6* U o q,`Iv$R 0" Le&FQՃ;R~~b/nR,} ,ڼX3& ePG.Sz%\Bis>}g\tdc,]$@ (c( BՐ.xekaHѐJ !2Y X ¢PJ&1&"^Ej$dԭ~H՛Nq _y<xqyZշgw)LON&Px3_Ǖ 3VR@s^8va!}Hh8+`LD `Ttw'@iH xB"E.p7h[" CdroR fbi.Ah2`3_pߞC5  LYs(, !Qs@Mq1Pm`觯}: w'T0IN[v'r";Ta "4Ty)Fʴ"闛㝈=%ហ( `R!&A#` )!U |ßE+^| صpG1"L4krh.=K5%BUA3>0A !@($&Ah*a`e$3 ȩ!o`S DCwKҝ0Ӂ]J^DR!^k楘;P@`( iӅה#-DGb1,v;oKfJBW_ݣ,ˆvˇW0yt~.[)BHۻf&e_ɧ^Hr g=JcQB"Q#G}<GȬ,;P+"/~_{^a BC MCc ƀ G;e8Rh:M{ xO|Z))C\DMT k}g\ROu@uKMmH`h HP'j Q T0i2{Gl pJAHJ\$¨ b#bɽ>U9Lv.{WA7}209ӝ 0y6hD0Rˀv8PS0lCL@B lcEzଙ86jion~QiY]v7VDvN'p@,ⴁ[bS *y w.o^D^,#.˫^sH|>."#KPwj2qR^j(#\C-5K3#<7L+{"{L@U49fslE"FT*pz!'I;ߞ` J:7A mv)*$ ~y2@r@N"vf8~C ,q!;' `g!@$̶ NTb3;rKAAOHd8@ L4(-p=8H{"| l瑑Y=Z_(DTDiz~:޼nfPۋ߫I@0awF2`Hxz+ LD˽PH \"N.Yb//%eaH;(^ӝ(qq_3@uC+duS?s'tIE zW`[ &E920c1b.aG9x>K9}ǒx\'w|"<n^j-z"Y*O}b-j@+"(@eb6WdQ4$ t&5 0%0 &/Bf@d\(v1Ab!&T /*B\`;h@Ł;؞_.Jeu0Z(0 )lRhORf,N@i*q(f:"4Wi(_Ms~g:jbP߼<_ɺoqcgB/B wO]>,:%d lW&R( Td].$ @r/\w{~ P'TC/ C  (@oc_~^F@A ϲO"Vi4֜rgֳo+.䏝W_>gT|틇~9|e6a=mM:Hh`"PvApEUQ`;-:R+` M¸d8.*-}jxL@K}՝P9.E̊w](P'] dۋcvؕ DՔaT/,r 1E0fwD:j'!P+6|l75Y$1MǬc*(LT DPdz]w|ː=$Ԕt݁] Ao(dTM+e\NsAh h+((@BacXoU M uZ˰\=Z.ܣ |H ,B̃TLVb"h_'s[2ti;RZFËc=ܠV\PKe :2dґtpH*eW1DEP/Qv.< AwR=*' s3@] QXS!d@Y}`gU'h}_-};q.^} D "Bɱgƒ׼G C翶c"MEfnwz75E1S35BRA+ދ IDAT52$sL ܵPr(KH "PǏlκs#=0,S:v>B焝D4"&4e}l9B&*Y5ٞ&Pw(^ĤA"'M◷0|/AFp5l9>= W9iN4-T7fNh(RRTf44w =27z\us{5%jA1@F1#=Rc!XDP op2EVSQ*B{m#a~YD2QxKN+ ,i;7HzطwJ$Qřcń4&j$EPID aӽɏ S 5z$+' !Oƚ# p+@DLkz8Lv 'u$r%s`DymdzoK=ZM"52269瓞{<'^'n1>sDBp(QbN2CIھ[/I& TKe%0E3+x'D@KA!$މܔshl%˓o@{.MuehԍUF7214|-.X=otJNܣt+PHf쪨D`=ȓm%x*<t/?;#}_ ?y'OY`oC+0m,o9l vRPL\ف`})PW!bCyfcYέS >A(Ca)t@5Q$|cgYIWyÖXK  9"v*}M)Qa!jW͇vuY~w>l[_pPXcyIF5q>draVȍtNƞS 8쭹Ojr._U@!TZҚk9|~)KUȃ.3q 쪄#mX\Tu%UIQ|k_>GGJ$TUfFȂHݶm%Z@`*vu19nᏵm-l/Cs'gU=%\V&+7ܪ1I  1q~p;ǣeŝ Z+OsMC=XSV VZX$n' 'Vq^ $$$qEכ@ Bij>AqBsfl Ut,x<Σ Rc5"(JA( ơhMt@<2́T]9埏.rt,Wp_?9pjof(ŭV4Q@R}߉*9<Zk6leȌbV+i%S7r4: U]}v/pvHL.1" #B P1ba)RGZDZ䔪s A8bK9b6#Fab30T2 z9{w5ULD\kZg7C-Zs1z~:5ngLk |pһu,+!f '8DD5};}3 CL%2Hr$hje\T!XbV=U[pI()Q)Gg`jG6yJ{3r/M?|N*TO*0wWw3?7MtVk1ժ3 Yn^wa%)nVP 62!j%x&u΃9%I,ep|p D\zal\fHIk 3r4Jc~/T}_.+8 *hm._~؄~w7o'va>Ⱥ?R * n@NKck ^ 'ݷ* oZw Yb(PkbvbGq%7^nS(#bpM{:}\&wπZoDF1w]Gm x-x>jGWGUzc;7~.W pDKg\VܾzgwG~*Ϲ;ݹ1NK%ľyj*'o͓KsɹgFBEP3Sh x<+>7._21RjY܍z??61ʇՐ 5EYX`*=$sIl0@AUEwe烍-(,XS6s ༏T%*"0 ]&\HDQ;a1{{sH4\[tR5醣-\`p9KJ*;@S1 ZKaq߿n`Y/"Ր_ಾ2  Z v^%yNcqڌ߮>VFAHU` 955!GG!G&_ndfRryF}stnn4y}ևQo,z1 \_0n6CԙzqlVO] u+M)\!\ALA=9b&`fDuZשCX]]Zxp_K:[qo+_v^=]oV*sOLq-TV^Ɲʵmj xSr:@>] $@@t} Tő-3ht9xuG#u6_t)v1_, ůP򶖲6"W|5y++S T4P 憹L@ \ >+V0ђU`P3SLfd9Y>e(v1t)Z-%nx0_Ţ^uales?“4 ڔ83(xV/̈sWkS C?*x%DbeJ[">HI,wT_=q}Ϊ}n_{I_?>.Su1Hh;h8uS`u>q@ő&jV$Txln!*|J+#{@9.l)&t]p.ՓW7?Oy|~<@W$Ӛ;G \ɛ̎XDU *^E*0Es׃p'dG HQ'G6\RvHxUq\vcHb01P@ Mh^=PtFHp&ܪJ !+c3igWR"O*Cׯnabs9_UX::_u.4?vh h2_d#YEGU<A"40bV@PؒeζQ{AHh L * ъB5*kw 0s!o#P7RBaEaM'?y~ .mfGW`г}E;t i> HC,s h" &uEblóճM_Zx)w !tn6'¶C;7a>^ {(L=);,@O3ܚU-?zsjiEW*ri]Cn(tNTXG@` )PjdΜ=ǚ෢6R. q5H)yL18/#wO@S&?t<)-=Gqz8Vl ('p3c:kE, ]bSj5SR\u1͹;<_~_ /T粥jk6 RGzGShJ#R>Ԁ{F^1icIz#BOHa ` X̰B8tO(:BMj{?^yϜ?{#]~ݟBBa%(Cr´rv:D# -gd+k h1p/DHzkj' p"!#CXv~5bHDL,p1Gǂ(ֻmqk\ˉJ.C^m6zG%cqڈ:0R+mBj|p(VԽ@aeBY#Z߼HK_G H TĂQ?U ʎ:B\uw1PQrI P IDATW; >Z#'x<oVz缲nX}G 6ѩ@ "ؿ(΁#% 030gZ@1(pj%A*E__\4\M/C@D)-ƦymqO=1-iƽb3؈?q:@n:ŀPfkna90D%<=Z 'da89(;\r~ľޭ6OwY>˰~ TZ}*+]VOIѲsrZ/3P`4W 1!xs6%T[TALOFrY\g X牔YIkH^ *7+`;s+ۑ`pQBv@Kͩe^82 @Z)ȧ@ /: sG@ ^Quq|xSqoJ_~XFS`0Å,hVXEG?)#3'T][%jx[3O &pj|c ts`Io =dI23;E@kkZ0"uJF楶sY<pVɁB-d7r_ ѱJ@Xn'2/~i p\z(ß>OytӒTY9-9ƅVAPr "KZfCax3/.SO@1`p".L\%u\@AaAgYN93xYnϡ O$^'bq?g!L pɝx r% |1Ӌ}?_DR*'$$B퀛XwB%F}N>lӫkgX_ڙ\`N(`d#{qnY[-nfn)39#LCIB]C Œ`ئVW?;` xYhe*a#<89N/G wu *Nm&6TV*L3<BH]a w>K)?^Rqo$uGprA$.B"PK޹Q/Kvݧ]>yt\*dߠVVJPmCA'| >j W(u 3٤t]?1l v۫[f9\ 5+d0GV #Й{ˎXZ3h+Ƅb~7`ŠP##JEz7:z,&6?e]GXy 6X)H(  ܩ?5f^iI_3= ۶!xiS. 9~\{n^+6>2O{^g6H;3;T}ji?YAjL=.@qB.mxRC%53 jBJ]hEgׅ\~( bIp¦>ejpvQcYuՀ@ЩYǿ' K `V dg#>xt&ԙPctN *ᡸO^mfB y&^Z1/3B~PPY5Yd>(Q;vğ`֨.Syi<S,@l?ڠel;a9 `P2yavm gu1"nfZM;.Ç BfL\kO{B΁A\"Ȉ載|0-oԻg?3oRh pǖ+8-ۿx({chFj`%'E%fO  ^~ D,38h+25: 2- W6-iia!U>7g;9Χ ( a Fsc,)&,6pPpNY1+򑨎Ba޴i>eSSD+Jy?>OˑZ1J090cvXcf%H]F׍ Gu;QM&]y=HeSpZeXyH>61Téqj)'6@<|Tf25\ LP'duM &"vW۴Z@Dj^ c5d^+k^)"(ŖS/rWo[˫/O}si ч綌?Pp4 #fCf5f"~݇wX̜3=S"q >8@E8}JKбxp!yc8<}Tj!: 3x/. =p+@#g[D<?M?.^=^w`S!pPWVfp*M'z8ԖD'N0#k$ta^tNt'̵+h"=H\|84M`Qɹ̕^rCO>VUU,H<^3DC&=٥ݦ. #Y,y1ڱ*E ]B"={긓^z]k=+&a:2R`Qƽ܄@Rs>N}Q ^4Eޒ (Ҡ<bErI{GF9yNiSZuyX5uo=~ݭ.ovЧvo嫗}vrOS诂t"+ <rsxxcՊygo_=;뛛7?q?N43K_bE8H>|q_^\xۛ7O_^_o^vˡE^DڬY)̈hg' ͎$9D!JU*崉iM&$j75`Tc8S1eO 'G]Jnۈ Xɥ2'8#uX<"'YEV ՀwOiBRhޥZc7AWm~n\vwo޼o}8sUhF9;?iՅ &ѝGw)Ѭ:I#5e# u] } ;R"{'h9[ ]_nV>^R2lw=to~݇ǏӜ =}!K9/ !KOnv?'ϟ^v_VŰtR؉ "0GEVPQ# fv1E jFp'"=gF"g"@"!GbocJ0!xG jjOK3:P r5aPQ#69Ԉpyڃ!B5B` .uFśWkGB`J@]td rN L5#> l_9OOmz9ެ~{_^={ݛ~2璋w/_ۖH@!H/_<]xdO\n7֫e?tR 1M JH:|2LUg2WU갦MeBʝW-So$@yDu4,?@@MKڰ!UL]uz$jMG2$@>y@&]g\GY߃C$B}wyq!kMDLA8m1Q⺋jӼ^lnۓ_=헗/޾}ۏpp?, Pϼl.9Sׇ 2 CܬViw^//n_}~{srwy)v!@ .s(Hs5"GX)vl+Y@p/2e SUVHGyP+;aM$!`B$Hh@Am@ 3,B1$v}c@Q9z(ڏOSKV ]y( 614Z+t*~(jw5sc׼hu5BL @OpeM9VXCb!$b(R%rbu®0Ϸ맷=?__xilf[ ODBDBDLD :oeӟ^n_<{{fr]ZêB#{K̥Yk6>)k$L#Li֪sbaw"v(vM݋O=yz VeI )ȣ~x>8jƢW6"Iafpr. u͏:mcVm?9Bl XGu"<ǎ{$Y#⮹c!)T7漙[= \!n >[8dtwss3XNZ %^PrU$rj"iǘmzL4E-`-!@- JIMSo N#"k`b;][/T*)3Alp5P `f6KM9깛UeQyQE YXb-$j_f7Yq\V'~\ֳeյi1H?ksκϪYnjbx2-N~=9Yf/_/Nb=_ԛXl63k:ˆrH&w9-- \!Ʈ Mݡ^ݶWn/>]]ooOYUZu֚|/gbi[μUQ" Qdt+**v +Uȭ8i?*\6-+PP8NO"@GAGF&pA Xa %U!ep\HD]}}?nŘ& IDAT(-1{"%8 r&`[,l5??[֫ӓlmAp#)Z8Ab|D6|&#]߉SZT|" d;o7wwf_8ۜ6tկ6blQ*g[pKYV2jYa}A g.g1 JN4$-U 5l,gfؗ}0"gq(r; hC!|;*(:7mj{uuqDޝ>?%9Ye̊ 4wƛ&%ZAi% *PgM~ Ny3 +RLTH^7OLAӘ. c$, ")0$pRHQ¹dlb%Iq٠YF!%ZL@"|ﳚj~pCv}{Xv뻇vpvZoNd^,](K_eK]&{Trl5&ϜU(ScP*޾"M%o`&F1];yfY沍N,F $2JámΏc Cmst_]?mWf_<xO* - uW{2 =tbj 8ͅE:UmQYIȦYs%OnAGE56lJG;GP7YO|-iLh=A2 *C11'zt@۩FxӣF|ʓ01Xq>Blqlu}rzwsvvrsZnd-gz$TE "l$p.Ӧ2HIJZz}K!~;R >>^wgX+YK.7bƦxk۾C1áݾqdux'> 92u^f'slo@]Lw0vU~9#: 9)H$ :EKi}LbN轡XT!<.ך'iOZlb>Jڀe=I$kJPԦ 9 ^D PS%DUťBﻶ^/w7f\^ճrYWf˳fl55(Kk0i)_^д1 i=Я.:ؖ{Ω[[^B7^/>^}IT/1&98ч{+ @ErL]2R!] 9ؓ tF. NӀ 0*MDN d.]F:`LPg!$|(%Lh*LƉS!/ )e 2 lؐUpLG}d<8f1j.2m~iq~O_f}ZOUr^y.6rYA,Y':x o͗E~0z?m{|~CU?JK'$v˟ `NlF.ǜ}~j9Lɞ P@'U@xRU} JFUއuv{s=??}q9?_-_lgӅ(B[k]f˝3r"?'Av[5&(tHC$‡aڦ= ape,2&k 2\O>on >M{Ohvx}|b}RUB*C~0BKrLR6n[0<0F.9d\E“gB@KnGnw g`} fjU0G ..B"(O*D?! OJ|^^a3$cΪ2A`1QkZ@D{uMun#7`(kf2FYl|TED߉jtȅj{5v߿~1N_ͪugefa@XzOJ窲_SK,"ԷMluT"rZ a甚N`:)i=ٛJo}6nTFRN!AA7IϯEƨh|I 2`I;vW=w@*{4ϒ ?Vw!6Mw}"_nژe"ڈ\,IdT$}ߌз0x,3٦}wbYuʋR ]U,Jr}PէI`|v =U{i1oe+(5nfM+Dǽ|H&೎SzYYA`8iO^ӕ=qL#$ D.xFFGړƎb| {BɊ_8OL|VE|"HN5 0zUڍtVbg1Ɉ,qd~q?AT Ddb霫c9j^Tg''ݞv-7[c`Tը5 Z9 e0zRd"ԜHQG ʴl'sz >?A#<"1 Io 32AdȁϸL?FڑR$ph&} > %p1nZͫuYekY^Y"j,1c3c,$JKW˛nߎЄ p Im֋zY,i],*km4ĈPb~z\a]C!C%ω*ZqP0j3 'p <DN+L'0S@ZlV'a/w ,*q '`JʽSD48DP5Ą m>O@OWz)D1]ooN6O쬪b Qg,4egf8=Yt}hnQ<Ԟ=Wd,󼮫*eefzƶ~}QdXҴ]4 J/vac5Zr3EBYW3BxH{FJ@*E_餏pfϼ0c>RUu! iO7O_eo,f0 3, XjEԊj(C@B5)*+#6]f]E%֛^pd=Ħt#1ZSuU/b>;'a,0"P,FC9h2j ;$L0Q5>h|> =>?ǎE(KbF@;9 cab瑠jZѠYDJ#fA} q"{62Y6w0{կr*_q(% H"efDK,6)8 Hvd>a8vn{&N< PDt04o8ёIjS.391-M&7e~$[1xuc~__13cϔ,gf8]F\#b"8YHG_DFTM'Inܰ0V8?ڇ=5%0A=TO(@$Ҡ*hE^Baߍc?$dJ̆A;؀Háo˫жʿ6@VUc?pWÉuRRrlؐ!6l})N_U/ΖɺwnDD**$ EDU*ָȊaZq '͢yu{H`F`*i>(O\,1q(K 84w/\Jk8j룕qهB$̓ FE$Q"b{0.v!aZzmGư2g벴e6l|iw8c1 _ <%{sppqs]Tg<!-*WEܱsΡY9#;e7'wg׫O݀&8 BJj74#ޏb4"b5ym]8B͊<_|y^NNf|f D1ġnws}wwqqy}OoG|II iFO [$*R0z TWeQج̙-%y)|ś۽x"Q"T,QI|q=cϣ9XDt,2?H߯zKl,g!W}wqS+Q@h'&UP&ށI@G'D+*0*@m>vCO}:xCGAQ*xq$8%ߧgU _־!"WUE~v.?~jsYmagl8sldQ`E(yQb^׋E=2k0H*czWŧІ$o%$aGy1qM77޼ۋOwa;}2 4HM޿{}{Y{l99;Na٬z^esPQ%1p21+sYLqH~h4 cP䭏#Q fy=G6 K0'*m !J"( ;πDE #hoL@@b.Ttt zd %:ར! ;W1Ӵ#"G%E+4iGQA-{0wiokDb=ͫl*_wë^8~(|劬Z"Į2*HhuJ&rpL33kL1ھԵ컦m_ӻ_~/onhDϩ<!pSGv6zd:lͺ,/,Y 8A.,˒0y'PX!hHLraaH(ƴy~H4N->  Rc$#Ҏ\xE%ɝUy5]'k7ưq֙,ˬ.;Z6\v~W㏯~x'89]aYrefdIT朔r@'44a!L:0Ўw7~姟]\^5Mw_2=;Wk墈ba?r^mj-j^ș]fXX[Eb A GΜɊYɺ.YWE&\!/1d#'"Ҭ 0 P[0D"D60D"zkVQ2cHJNmXM !"y>iuq@HQ;?a FEL`v6D>aL<3u]f7erD494桽޽ӛ7?mv ? |>"^6m<({M5F=c&JDHx!kz(O׳٬Y*'ُxz򡨊 !hcCy LthΊͭq I/#' e idg)y&=0:%i|}(ܤ=è48@@gR5"#sE6+5fZLov;x={c "bcLU3~:f^n6g'/7W}YV欴*/,HMe 3/fflڐ adȤ>ҴK>UMhRKnmsn>~y~x뛟~zwqph >p\+^,.,wec ,1y.kcN*4I}8q|hanakB7t>8 ;յ6jˢU,ϊY=8H 㮏F@ 5zK #UX0(%l@ XiJ81-1L^̰X( pf1Сݏ&Kf͐e.Teʾ<{1??;٬׳լ\66J=Y18rƀ S,A+r>7UUy]j&s&!H`cC $ nбщ8 @]( LQEt^Cǡ|~t{{_?~o6/eb j__f]-g3SV#Q2Yv<*|>׋z^ͫK>2&Ph1`F18ڮ_]=8Tf]!lrEUGIN6y 4cZyNOGI$_ 3+y5`s>FX+dl V7/IQ &)H䓔cesZ4aSl f<' c`Y*g*Ŭa(eUً9}5A!gm)11C+*,0 އ C{>bh *^ 3-a!wgjC}?C~pu{˻Opqyss{m?dN֋O?,f-W|Y @#5ę%$$*al({0zCn}vnoןn?~?m&al~hwCllψLFFtu11M2aa<(IyI;4:& dQQ+!Lc9?BV"C% 3lkbUqqSZ3NLL5&s aatMӍM}7v}q.P4jH% &iu1 v C?^}a'$o:gJ0DdNdQڝssRfw$Q2"1ؚ;D 1mw]]էuGﲡSidá>;=z"Yı%,#dFZ1 8rQQ!4+;tEmHLAC]XS$PA]x8g d@7ҏl26fZX) g0 U"飌`D>?ݾo~{͡C׏>!h$(&1!RǪZ"Q?c߷~{w}CB&zS7*gc K\d;qpF0JPn3 [&#PH UԪ$cvi^]}{ӻ7CE]shHdmۄifvfSV"+2Sf T$?Q_ tD@A:Lf)uڠ"ܤ$)A '#Ihk@3Ba-٦(QET\7ziLl@΀%8 T !av{\o?^=~kЍ1E# B&%Cl jIHET~hۮp~:=C>o9;ɰVۇ]xصy7kjhɵ` JhΪXTK~4 ]Ӆv?ç7޽XEUTuo]lY9ɐ7 DA UEM}DB M'\,E"凐 A mj'HpHG0cH0czh;X)JccnMsx{ݧOo]m.xU5&M#S ґԨcccQD~M—`}_}lj Ǭ8Q€ m& PURpލ7ۛǏ7znmb5ԄB[|niƾA$AU\T0`9*2V,rgN)v2 "i JF"@a҉*F0(F ܧ6h$Ş`$p`P5]?3(M$~h ^_ysŻwW׷8NrbύD'rtzjM\ . 4Mm_z#o_wlON]fU95Vf Ĭ,ٻYAPgoMеm闧QDEShmm[vF UI!SH%*VT ejKpaLI;#Lfe@at:dv$4 0)+@^.AmX01y BD 1FCޗ6q$zDfnҘ.  uC)̬fFnVF MgDdy~ܽ~<r.3׺rSρxqXc_^?su65vhЪQIVBॺ!O7ۻ~8SΓ/$/J[aH,,}ֲDze' p H %^F p}EW/9ܱBpf Jt_܀ԅ́8f`r<çN\0MY0ꅈJGÃL?|7o?}{XRNus'Vp*>Cpia*RJiI?=\wA*nWp2vf6MK9a8NaqJ3^c2|@a̾XZ.3պ(;uD^VVe ,6Վ؄i/ WQQC!џF~»TTYC͝Xl.e%Tx"tZ1- + wjia?ݽ~?W_aIy$9">x.'K VqDJnuH @~j"#zKO%Yjkى)un4b5tUM!CiH@b'Dbm]/v(UHT#ZSy)x tIN '/l`vBC4+6#{ H륅Xq,RQKEZr?|7_Oog|HG'<;V037 X-@DԅyT? $n.h PYuZA' ؝z:T֫-Zu\mpT|tpft vLFLK<13Ra/%vׯja >8?yo_?yN>?Y<DR{댍sMzF"]==' (.pS4ɍ[PPNmiwy櫬WvNŏ|T~Y/TYIzDcO_\*Inu^Y,$$m%ny%^|UWyYto}nnS*O'sC9NMgRpvR)rYKgK{ t5`(7:YAd(bc@ӵ:QluOaq%׸\:}}$@=[YKcԓxVe1"fjIfC9lhl{W&ۘ+8c}|xo8_K٭-giks9W:ԗ/ Ukp"BY*A(@Rd`?a!?}eK8kCPiN䲚ΧO I#L TJc}9'ot<3)z]Xu1Ɛsٯ~+t ,ձd1XTV!"2$',BJz V}9CiY rQX%鬁k Fr{?M嵫U) S Rq,BBla㢽@Z;"u_4u/`j NB`'A*dށ̅\!HbkDū#M0;!D9"r*'X$s#8n#?o$^A5l.z7j]RmN7 E6:72 0 qT(P_AOR*sCPF::,($5l컫6IcJfڰ2Z{O Bxꣴz YjbZ1K*CT6O!;d!m3O` +HU\U}wr֋fZdDE+h`%`tf2 hqA3c󒾿?xՙd:_Ͻ5|%ȻFӭZlݻ>Knnjڻ{,,8ֵqܹ W&'OY@w f-UAŪ_!0 ހSJI0$"z`wY%ׇqr)k,N!o˛T3e WPBBhTf@Q ;\ŵ؄jpU»DW$ @LAR0&f ɇ |Ʀ.ˤ8NQi=jJَ8m*e8M-x2{z|d JN !hTPHTU6] 'W`;xH dA6AF@"q"~>8ogbtpRpXglq |ҽzQWQ{SM.uXبϜ-|Y;8{$N.*bI;9hBq^ ~4Q$t q ̶NpS,Td( q;ތC.rF7f} /_틫Q,~nͽ]vڷ@E}{>N\O:%rGoeU4aG麎z2\"j{r l5 )`Pu0r%B,F,OrjAv۹l%؛ey8?<,)?_#-4/O$NKz+*(H԰ML=th@  %HUd]14ʉum"̪>Q%-څ_mKƷӜ=|sYR>:re<ە^+ޞN&UUlFPp3DG "܉   ž F~pZߔj'ɮ_[7h_Ѐ!x_ W5C`vYq/{E^D(+RW^d% K&,n~7ü{[t?ۂ@ÿgYhoܙcG #BARzF$v"5}'u#ƴDGQ#Xr٧RFw>@ÿ) 8d[RUGٹI}.^mDЩPITŃj]˦wu%ӿח=+@Q 3TfwB=6:uk£/\〉]AB냱jM}uަ>KM>.v+–WnvL^/c+~$lĜS$;50/u<*Dtwւ@G &'G uwNyU^M! cS-xGC `X}WABܝ-4Y_,/$pKCrX= yCAF: *A䗁#`Z>vH@ Pd$Q0Ոt8T(׳1u6(?EdN8e p v7hxz] d\!Co:g#ľpT/Ȱ,e i!B`̵@p:54hKJRNxt`*<la-^nbA Z*e1xSU*ʶc$KJY#eLHEj]?T`dfX;4|$pfBp90 벗4Vq=IY%ED@y*0J@GYfGF/*$怐iV& >DA"Wyq"a#T#βs{tЗ` !#rt[2jRN#*ND"|oEVcz["QΎǝLW9k?"#I&L߭٥F Q`$.`qCΕR$+ 6[ ^s-%aqb M@\b9:s7ba3Z/ݶbuTՆ(@yoq2'>/Hf @.%)@'ל,\WnvA1}.4NO0,=?ɂ54|%SPrNV0OǺt}U Ejv`e2/.4\g_/l.f.fΗ2QBKAi.ŗbXrJSZɬ~I7hGb!'?Ԫ)e~ɧiI)䤑t 炿]~ucç':QUc>+Jn \<3-h!== @]r6ϋ@?F PxRL^s 7h}$Z7h>ϋ4>cyy΃'v \~ 4|F! m~CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC  IENDB`deathkiller-jazz2-native-2a7ccef/Sources/Icons/WindowImGui.ico000066400000000000000000001664311512772601700245170ustar00rootroot0000000000000000 %V  % 6 h.@ D(0` 1-  [P Jxd= X $~D52.E |96FVkW !}E'D2&O { A ^I\C Z(O J@w__H _44  D8u D__Q2 Y-:ox7ADN  ٷx#,2V". `p2A<"w ,iq5@C[AKQ^=JMO ݽi"IUw"5-vLPbycrvn7ADN '5_BN\Jw"4=+S)e29=Kn5ACZ %*0dt|Vgw Aw"8L^G}SV8@57ADN 5:?0̦sooas~s?w"6T__O,'_C 3;?Xhp;EJgg|%HW[C*aqysӛ㍧z`r8Iq5 D%@SXY# kNa}dx#,,9  DQWwԀdzGWb : :879U#1ftۉێۘۧaty , 3w+BXH. E  pt)A\__H8=  /<[_S x8R__}_1. !jI=[__ba@b-(ROT__H8G`7g4 ? a@G2 (6B+ b5  RO+"6r$$(S B9t i/>.RbBH5 ( H#)Nk# H !/ L"V/wk](\<* M(,|r|3[6 y T4[Oql`o @`0 *"Z' "t- ` -( i "B#{ b /\' 4 .U,t >7n) i J#} / "jB!z RZ' ) 6 . y V( ( F7n( w - 5 , w . & ' / An $~ - r Fj W:Uw Up n Q@2 4 Bw H\6 ?, ! Ls ) 1* w n 5 ?=2 7&{'}. ;X 2q + 9 @<3 $~ "{ , ( r 3 ,#y j1 +&|5 B!v=B<2 !{ [ Hi H;) 8A5=?, KW) ) J2;,H,!r(L/2 LN. =>  L;s#<;!E  ??? ???O( @ A |6786532.-*(&fTOOqliqliqliqliqliqliqliqliqliqliqliqliqliqlihda3 qliqlif qliqlif Sqliqli3 Sqliqli33333S7qliqli3sfSSqliqli3fs Sqli              qlifs S7qli#!""""""""!"""""""""!""#"#!""#""""""#""""!qli3f33fS7_qli$%%%%$%$%$%$%%$%$$$%%$%$%$$%$$$%$$%%$%%%$%qlif33S77_Zl}qliqliqliqliqliqliqliqliqliqliqliqliqliqlilee3f3S7__7Sf3ff33fsWkw S77_Ssf333 7_7 333333 7___7S 33S7____7S33S__77_7S3fS77S33S w 7SffS / ?Sf33?/ ?ff ff ff ?w ? w f3 w w ? ? w / ? 33?/ w w / w w / Cw ?ff?/ w / Cw / w / ?w / w ?3?w ?w ?C/ Cw ?C?f3?w / C/ w w Cw C/ w ?33?/ Cw CC/ w ?w ??ff??/ ?w / ??f33f???f3@_(0 Z ;8;qliqliqliqliqliqliqliqliqliqliqli qliqlin3;> qliqli Iqliqli[kq-bpv7*7O!qliqlisYkq [qli! ! ! !!! !!!!!!!!!!!!!!!!!!!qlidv}cTcl/qli%%$%%%$%%%%%%%%%%%%%%%%%%%%%%%qli,47MZ_EV`qpn7Gqli((((((((((((((((((((((((((((((qlip2;?' U0 (sg Y w,& H>B.2s lh-; ,V2:(+ cc[5 7. ' 0 , -Z=:M%y' .3 I == 4 @)Ql >)@@_o//__(  fpljnhfupmupmupmupmupmupmumjtpmpjgtnj &upmupmasys恗upmupmnfٰՙ"*Gupm !!! ! ! ! ! ! ! !!!! upmasysunf)upm%%%%%&&%%%&&%%%%&&&%%&%%upm nf^n{xtp((((((((((((((((((((((((umj&LX{nmlupmupmupmupmupmupmpnktpm4f.3: ^G&n)fa) )sT%b 3 30 @&o&|&|0 [&|(f50 5 , "v&| F 1Ja(f ?PNG  IHDR\rf IDATxiq,Ug6E=ggJ s*3"}2}aVL%k~V7曛_ ж"me9j4(zow"|w| |6p+\/b0͇iT_ևYW[y[nG;p{xw1\/%ǛSw:k)6[fgBޅ:\FMD=wRStjtk<*žR]EFKy DY<0sޒtZR[Od́No°)R`xy,7huQlчǸH$+0\z TU/__lų7틵ěoVloUHw\4l2B(t%6IzJQ۔THч c`+$r1D0 Er~z8_~,12R. kJp@C_q[cx^m}nfi,9VA9B ҟ2 B&Ԡ @BEY&@'X f$fIG*k&'Ğ2Q)TKT1Q3 0K>O|wkmmz1[[Z}yӇ>| W-bŪ{NmvԗmZ7]M^2[&HM Ai2 d)A&,e @pE`&P)`@dYAUxBC(AhueAz" ;d1EPAIed9e98ÊyiN_E7_>sX_Xϵ W2)/jt+oכl_7x3E-gZ @ P*@tG@EV fP/T@C!)///(2: •pێf5JRiP BJdVXMUE!zyBab(fYEmelz5N6 ?Nv֬f}|l㇔+\?ë_{}6u|^xwShM,ܝV:P ECEL{"g~%3Ȁp ŠZ&J+!R(a{@DTVCz0+ĊH"*h4)*P1,L jj(ư7434h֊j)#{욦WG@P(q1O.h0fDFѩHȰ &`4bs /Pt7;p~9ye[@sO R`bXQH*{zae"E!R(T8tQfxAP`#rs SͳhύM>Ua6WuRpm^o҇۶mM.eZRG}Sd# Y ԀR=@p S/F(` ``'r `!$ Ln@ @T`ŀ܁)2at088 LQThHXH4`IR,C+dJˬ3rպ}}#Jk<>ޮߝZW.Ҁ&::EFͨVe W"AOC h^N܏g:xtA§O'Y_q?HHD )O(i+RF6* U o q,`Iv$R 0" Le&FQՃ;R~~b/nR,} ,ڼX3& ePG.Sz%\Bis>}g\tdc,]$@ (c( BՐ.xekaHѐJ !2Y X ¢PJ&1&"^Ej$dԭ~H՛Nq _y<xqyZշgw)LON&Px3_Ǖ 3VR@s^8va!}Hh8+`LD `Ttw'@iH xB"E.p7h[" CdroR fbi.Ah2`3_pߞC5  LYs(, !Qs@Mq1Pm`觯}: w'T0IN[v'r";Ta "4Ty)Fʴ"闛㝈=%ហ( `R!&A#` )!U |ßE+^| صpG1"L4krh.=K5%BUA3>0A !@($&Ah*a`e$3 ȩ!o`S DCwKҝ0Ӂ]J^DR!^k楘;P@`( iӅה#-DGb1,v;oKfJBW_ݣ,ˆvˇW0yt~.[)BHۻf&e_ɧ^Hr g=JcQB"Q#G}<GȬ,;P+"/~_{^a BC MCc ƀ G;e8Rh:M{ xO|Z))C\DMT k}g\ROu@uKMmH`h HP'j Q T0i2{Gl pJAHJ\$¨ b#bɽ>U9Lv.{WA7}209ӝ 0y6hD0Rˀv8PS0lCL@B lcEzଙ86jion~QiY]v7VDvN'p@,ⴁ[bS *y w.o^D^,#.˫^sH|>."#KPwj2qR^j(#\C-5K3#<7L+{"{L@U49fslE"FT*pz!'I;ߞ` J:7A mv)*$ ~y2@r@N"vf8~C ,q!;' `g!@$̶ NTb3;rKAAOHd8@ L4(-p=8H{"| l瑑Y=Z_(DTDiz~:޼nfPۋ߫I@0awF2`Hxz+ LD˽PH \"N.Yb//%eaH;(^ӝ(qq_3@uC+duS?s'tIE zW`[ &E920c1b.aG9x>K9}ǒx\'w|"<n^j-z"Y*O}b-j@+"(@eb6WdQ4$ t&5 0%0 &/Bf@d\(v1Ab!&T /*B\`;h@Ł;؞_.Jeu0Z(0 )lRhORf,N@i*q(f:"4Wi(_Ms~g:jbP߼<_ɺoqcgB/B wO]>,:%d lW&R( Td].$ @r/\w{~ P'TC/ C  (@oc_~^F@A ϲO"Vi4֜rgֳo+.䏝W_>gT|틇~9|e6a=mM:Hh`"PvApEUQ`;-:R+` M¸d8.*-}jxL@K}՝P9.E̊w](P'] dۋcvؕ DՔaT/,r 1E0fwD:j'!P+6|l75Y$1MǬc*(LT DPdz]w|ː=$Ԕt݁] Ao(dTM+e\NsAh h+((@BacXoU M uZ˰\=Z.ܣ |H ,B̃TLVb"h_'s[2ti;RZFËc=ܠV\PKe :2dґtpH*eW1DEP/Qv.< AwR=*' s3@] QXS!d@Y}`gU'h}_-};q.^} D "Bɱgƒ׼G C翶c"MEfnwz75E1S35BRA+ދ IDAT52$sL ܵPr(KH "PǏlκs#=0,S:v>B焝D4"&4e}l9B&*Y5ٞ&Pw(^ĤA"'M◷0|/AFp5l9>= W9iN4-T7fNh(RRTf44w =27z\us{5%jA1@F1#=Rc!XDP op2EVSQ*B{m#a~YD2QxKN+ ,i;7HzطwJ$Qřcń4&j$EPID aӽɏ S 5z$+' !Oƚ# p+@DLkz8Lv 'u$r%s`DymdzoK=ZM"52269瓞{<'^'n1>sDBp(QbN2CIھ[/I& TKe%0E3+x'D@KA!$މܔshl%˓o@{.MuehԍUF7214|-.X=otJNܣt+PHf쪨D`=ȓm%x*<t/?;#}_ ?y'OY`oC+0m,o9l vRPL\ف`})PW!bCyfcYέS >A(Ca)t@5Q$|cgYIWyÖXK  9"v*}M)Qa!jW͇vuY~w>l[_pPXcyIF5q>draVȍtNƞS 8쭹Ojr._U@!TZҚk9|~)KUȃ.3q 쪄#mX\Tu%UIQ|k_>GGJ$TUfFȂHݶm%Z@`*vu19nᏵm-l/Cs'gU=%\V&+7ܪ1I  1q~p;ǣeŝ Z+OsMC=XSV VZX$n' 'Vq^ $$$qEכ@ Bij>AqBsfl Ut,x<Σ Rc5"(JA( ơhMt@<2́T]9埏.rt,Wp_?9pjof(ŭV4Q@R}߉*9<Zk6leȌbV+i%S7r4: U]}v/pvHL.1" #B P1ba)RGZDZ䔪s A8bK9b6#Fab30T2 z9{w5ULD\kZg7C-Zs1z~:5ngLk |pһu,+!f '8DD5};}3 CL%2Hr$hje\T!XbV=U[pI()Q)Gg`jG6yJ{3r/M?|N*TO*0wWw3?7MtVk1ժ3 Yn^wa%)nVP 62!j%x&u΃9%I,ep|p D\zal\fHIk 3r4Jc~/T}_.+8 *hm._~؄~w7o'va>Ⱥ?R * n@NKck ^ 'ݷ* oZw Yb(PkbvbGq%7^nS(#bpM{:}\&wπZoDF1w]Gm x-x>jGWGUzc;7~.W pDKg\VܾzgwG~*Ϲ;ݹ1NK%ľyj*'o͓KsɹgFBEP3Sh x<+>7._21RjY܍z??61ʇՐ 5EYX`*=$sIl0@AUEwe烍-(,XS6s ༏T%*"0 ]&\HDQ;a1{{sH4\[tR5醣-\`p9KJ*;@S1 ZKaq߿n`Y/"Ր_ಾ2  Z v^%yNcqڌ߮>VFAHU` 955!GG!G&_ndfRryF}stnn4y}ևQo,z1 \_0n6CԙzqlVO] u+M)\!\ALA=9b&`fDuZשCX]]Zxp_K:[qo+_v^=]oV*sOLq-TV^Ɲʵmj xSr:@>] $@@t} Tő-3ht9xuG#u6_t)v1_, ůP򶖲6"W|5y++S T4P 憹L@ \ >+V0ђU`P3SLfd9Y>e(v1t)Z-%nx0_Ţ^uales?“4 ڔ83(xV/̈sWkS C?*x%DbeJ[">HI,wT_=q}Ϊ}n_{I_?>.Su1Hh;h8uS`u>q@ő&jV$Txln!*|J+#{@9.l)&t]p.ՓW7?Oy|~<@W$Ӛ;G \ɛ̎XDU *^E*0Es׃p'dG HQ'G6\RvHxUq\vcHb01P@ Mh^=PtFHp&ܪJ !+c3igWR"O*Cׯnabs9_UX::_u.4?vh h2_d#YEGU<A"40bV@PؒeζQ{AHh L * ъB5*kw 0s!o#P7RBaEaM'?y~ .mfGW`г}E;t i> HC,s h" &uEblóճM_Zx)w !tn6'¶C;7a>^ {(L=);,@O3ܚU-?zsjiEW*ri]Cn(tNTXG@` )PjdΜ=ǚ෢6R. q5H)yL18/#wO@S&?t<)-=Gqz8Vl ('p3c:kE, ]bSj5SR\u1͹;<_~_ /T粥jk6 RGzGShJ#R>Ԁ{F^1icIz#BOHa ` X̰B8tO(:BMj{?^yϜ?{#]~ݟBBa%(Cr´rv:D# -gd+k h1p/DHzkj' p"!#CXv~5bHDL,p1Gǂ(ֻmqk\ˉJ.C^m6zG%cqڈ:0R+mBj|p(VԽ@aeBY#Z߼HK_G H TĂQ?U ʎ:B\uw1PQrI P IDATW; >Z#'x<oVz缲nX}G 6ѩ@ "ؿ(΁#% 030gZ@1(pj%A*E__\4\M/C@D)-ƦymqO=1-iƽb3؈?q:@n:ŀPfkna90D%<=Z 'da89(;\r~ľޭ6OwY>˰~ TZ}*+]VOIѲsrZ/3P`4W 1!xs6%T[TALOFrY\g X牔YIkH^ *7+`;s+ۑ`pQBv@Kͩe^82 @Z)ȧ@ /: sG@ ^Quq|xSqoJ_~XFS`0Å,hVXEG?)#3'T][%jx[3O &pj|c ts`Io =dI23;E@kkZ0"uJF楶sY<pVɁB-d7r_ ѱJ@Xn'2/~i p\z(ß>OytӒTY9-9ƅVAPr "KZfCax3/.SO@1`p".L\%u\@AaAgYN93xYnϡ O$^'bq?g!L pɝx r% |1Ӌ}?_DR*'$$B퀛XwB%F}N>lӫkgX_ڙ\`N(`d#{qnY[-nfn)39#LCIB]C Œ`ئVW?;` xYhe*a#<89N/G wu *Nm&6TV*L3<BH]a w>K)?^Rqo$uGprA$.B"PK޹Q/Kvݧ]>yt\*dߠVVJPmCA'| >j W(u 3٤t]?1l v۫[f9\ 5+d0GV #Й{ˎXZ3h+Ƅb~7`ŠP##JEz7:z,&6?e]GXy 6X)H(  ܩ?5f^iI_3= ۶!xiS. 9~\{n^+6>2O{^g6H;3;T}ji?YAjL=.@qB.mxRC%53 jBJ]hEgׅ\~( bIp¦>ejpvQcYuՀ@ЩYǿ' K `V dg#>xt&ԙPctN *ᡸO^mfB y&^Z1/3B~PPY5Yd>(Q;vğ`֨.Syi<S,@l?ڠel;a9 `P2yavm gu1"nfZM;.Ç BfL\kO{B΁A\"Ȉ載|0-oԻg?3oRh pǖ+8-ۿx({chFj`%'E%fO  ^~ D,38h+25: 2- W6-iia!U>7g;9Χ ( a Fsc,)&,6pPpNY1+򑨎Ba޴i>eSSD+Jy?>OˑZ1J090cvXcf%H]F׍ Gu;QM&]y=HeSpZeXyH>61Téqj)'6@<|Tf25\ LP'duM &"vW۴Z@Dj^ c5d^+k^)"(ŖS/rWo[˫/O}si ч綌?Pp4 #fCf5f"~݇wX̜3=S"q >8@E8}JKбxp!yc8<}Tj!: 3x/. =p+@#g[D<?M?.^=^w`S!pPWVfp*M'z8ԖD'N0#k$ta^tNt'̵+h"=H\|84M`Qɹ̕^rCO>VUU,H<^3DC&=٥ݦ. #Y,y1ڱ*E ]B"={긓^z]k=+&a:2R`Qƽ܄@Rs>N}Q ^4Eޒ (Ҡ<bErI{GF9yNiSZuyX5uo=~ݭ.ovЧvo嫗}vrOS诂t"+ <rsxxcՊygo_=;뛛7?q?N43K_bE8H>|q_^\xۛ7O_^_o^vˡE^DڬY)̈hg' ͎$9D!JU*崉iM&$j75`Tc8S1eO 'G]Jnۈ Xɥ2'8#uX<"'YEV ՀwOiBRhޥZc7AWm~n\vwo޼o}8sUhF9;?iՅ &ѝGw)Ѭ:I#5e# u] } ;R"{'h9[ ]_nV>^R2lw=to~݇ǏӜ =}!K9/ !KOnv?'ϟ^v_VŰtR؉ "0GEVPQ# fv1E jFp'"=gF"g"@"!GbocJ0!xG jjOK3:P r5aPQ#69Ԉpyڃ!B5B` .uFśWkGB`J@]td rN L5#> l_9OOmz9ެ~{_^={ݛ~2璋w/_ۖH@!H/_<]xdO\n7֫e?tR 1M JH:|2LUg2WU갦MeBʝW-So$@yDu4,?@@MKڰ!UL]uz$jMG2$@>y@&]g\GY߃C$B}wyq!kMDLA8m1Q⺋jӼ^lnۓ_=헗/޾}ۏpp?, Pϼl.9Sׇ 2 CܬViw^//n_}~{srwy)v!@ .s(Hs5"GX)vl+Y@p/2e SUVHGyP+;aM$!`B$Hh@Am@ 3,B1$v}c@Q9z(ڏOSKV ]y( 614Z+t*~(jw5sc׼hu5BL @OpeM9VXCb!$b(R%rbu®0Ϸ맷=?__xilf[ ODBDBDLD :oeӟ^n_<{{fr]ZêB#{K̥Yk6>)k$L#Li֪sbaw"v(vM݋O=yz VeI )ȣ~x>8jƢW6"Iafpr. u͏:mcVm?9Bl XGu"<ǎ{$Y#⮹c!)T7漙[= \!n >[8dtwss3XNZ %^PrU$rj"iǘmzL4E-`-!@- JIMSo N#"k`b;][/T*)3Alp5P `f6KM9깛UeQyQE YXb-$j_f7Yq\V'~\ֳeյi1H?ksκϪYnjbx2-N~=9Yf/_/Nb=_ԛXl63k:ˆrH&w9-- \!Ʈ Mݡ^ݶWn/>]]ooOYUZu֚|/gbi[μUQ" Qdt+**v +Uȭ8i?*\6-+PP8NO"@GAGF&pA Xa %U!ep\HD]}}?nŘ& IDAT(-1{"%8 r&`[,l5??[֫ӓlmAp#)Z8Ab|D6|&#]߉SZT|" d;o7wwf_8ۜ6tկ6blQ*g[pKYV2jYa}A g.g1 JN4$-U 5l,gfؗ}0"gq(r; hC!|;*(:7mj{uuqDޝ>?%9Ye̊ 4wƛ&%ZAi% *PgM~ Ny3 +RLTH^7OLAӘ. c$, ")0$pRHQ¹dlb%Iq٠YF!%ZL@"|ﳚj~pCv}{Xv뻇vpvZoNd^,](K_eK]&{Trl5&ϜU(ScP*޾"M%o`&F1];yfY沍N,F $2JámΏc Cmst_]?mWf_<xO* - uW{2 =tbj 8ͅE:UmQYIȦYs%OnAGE56lJG;GP7YO|-iLh=A2 *C11'zt@۩FxӣF|ʓ01Xq>Blqlu}rzwsvvrsZnd-gz$TE "l$p.Ӧ2HIJZz}K!~;R >>^wgX+YK.7bƦxk۾C1áݾqdux'> 92u^f'slo@]Lw0vU~9#: 9)H$ :EKi}LbN轡XT!<.ך'iOZlb>Jڀe=I$kJPԦ 9 ^D PS%DUťBﻶ^/w7f\^ճrYWf˳fl55(Kk0i)_^д1 i=Я.:ؖ{Ω[[^B7^/>^}IT/1&98ч{+ @ErL]2R!] 9ؓ tF. NӀ 0*MDN d.]F:`LPg!$|(%Lh*LƉS!/ )e 2 lؐUpLG}d<8f1j.2m~iq~O_f}ZOUr^y.6rYA,Y':x o͗E~0z?m{|~CU?JK'$v˟ `NlF.ǜ}~j9Lɞ P@'U@xRU} JFUއuv{s=??}q9?_-_lgӅ(B[k]f˝3r"?'Av[5&(tHC$‡aڦ= ape,2&k 2\O>on >M{Ohvx}|b}RUB*C~0BKrLR6n[0<0F.9d\E“gB@KnGnw g`} fjU0G ..B"(O*D?! OJ|^^a3$cΪ2A`1QkZ@D{uMun#7`(kf2FYl|TED߉jtȅj{5v߿~1N_ͪugefa@XzOJ窲_SK,"ԷMluT"rZ a甚N`:)i=ٛJo}6nTFRN!AA7IϯEƨh|I 2`I;vW=w@*{4ϒ ?Vw!6Mw}"_nژe"ڈ\,IdT$}ߌз0x,3٦}wbYuʋR ]U,Jr}PէI`|v =U{i1oe+(5nfM+Dǽ|H&೎SzYYA`8iO^ӕ=qL#$ D.xFFGړƎb| {BɊ_8OL|VE|"HN5 0zUڍtVbg1Ɉ,qd~q?AT Ddb霫c9j^Tg''ݞv-7[c`Tը5 Z9 e0zRd"ԜHQG ʴl'sz >?A#<"1 Io 32AdȁϸL?FڑR$ph&} > %p1nZͫuYekY^Y"j,1c3c,$JKW˛nߎЄ p Im֋zY,i],*km4ĈPb~z\a]C!C%ω*ZqP0j3 'p <DN+L'0S@ZlV'a/w ,*q '`JʽSD48DP5Ą m>O@OWz)D1]ooN6O쬪b Qg,4egf8=Yt}hnQ<Ԟ=Wd,󼮫*eefzƶ~}QdXҴ]4 J/vac5Zr3EBYW3BxH{FJ@*E_餏pfϼ0c>RUu! iO7O_eo,f0 3, XjEԊj(C@B5)*+#6]f]E%֛^pd=Ħt#1ZSuU/b>;'a,0"P,FC9h2j ;$L0Q5>h|> =>?ǎE(KbF@;9 cab瑠jZѠYDJ#fA} q"{62Y6w0{կr*_q(% H"efDK,6)8 Hvd>a8vn{&N< PDt04o8ёIjS.391-M&7e~$[1xuc~__13cϔ,gf8]F\#b"8YHG_DFTM'Inܰ0V8?ڇ=5%0A=TO(@$Ҡ*hE^Baߍc?$dJ̆A;؀Háo˫жʿ6@VUc?pWÉuRRrlؐ!6l})N_U/ΖɺwnDD**$ EDU*ָȊaZq '͢yu{H`F`*i>(O\,1q(K 84w/\Jk8j룕qهB$̓ FE$Q"b{0.v!aZzmGư2g벴e6l|iw8c1 _ <%{sppqs]Tg<!-*WEܱsΡY9#;e7'wg׫O݀&8 BJj74#ޏb4"b5ym]8B͊<_|y^NNf|f D1ġnws}wwqqy}OoG|II iFO [$*R0z TWeQج̙-%y)|ś۽x"Q"T,QI|q=cϣ9XDt,2?H߯zKl,g!W}wqS+Q@h'&UP&ށI@G'D+*0*@m>vCO}:xCGAQ*xq$8%ߧgU _־!"WUE~v.?~jsYmagl8sldQ`E(yQb^׋E=2k0H*czWŧІ$o%$aGy1qM77޼ۋOwa;}2 4HM޿{}{Y{l99;Na٬z^esPQ%1p21+sYLqH~h4 cP䭏#Q fy=G6 K0'*m !J"( ;πDE #hoL@@b.Ttt zd %:ར! ;W1Ӵ#"G%E+4iGQA-{0wiokDb=ͫl*_wë^8~(|劬Z"Į2*HhuJ&rpL33kL1ھԵ컦m_ӻ_~/onhDϩ<!pSGv6zd:lͺ,/,Y 8A.,˒0y'PX!hHLraaH(ƴy~H4N->  Rc$#Ҏ\xE%ɝUy5]'k7ưq֙,ˬ.;Z6\v~W㏯~x'89]aYrefdIT朔r@'44a!L:0Ўw7~姟]\^5Mw_2=;Wk墈ba?r^mj-j^ș]fXX[Eb A GΜɊYɺ.YWE&\!/1d#'"Ҭ 0 P[0D"D60D"zkVQ2cHJNmXM !"y>iuq@HQ;?a FEL`v6D>aL<3u]f7erD494桽޽ӛ7?mv ? |>"^6m<({M5F=c&JDHx!kz(O׳٬Y*'ُxz򡨊 !hcCy LthΊͭq I/#' e idg)y&=0:%i|}(ܤ=è48@@gR5"#sE6+5fZLov;x={c "bcLU3~:f^n6g'/7W}YV欴*/,HMe 3/fflڐ adȤ>ҴK>UMhRKnmsn>~y~x뛟~zwqph >p\+^,.,wec ,1y.kcN*4I}8q|hanakB7t>8 ;յ6jˢU,ϊY=8H 㮏F@ 5zK #UX0(%l@ XiJ81-1L^̰X( pf1Сݏ&Kf͐e.Teʾ<{1??;٬׳լ\66J=Y18rƀ S,A+r>7UUy]j&s&!H`cC $ nбщ8 @]( LQEt^Cǡ|~t{{_?~o6/eb j__f]-g3SV#Q2Yv<*|>׋z^ͫK>2&Ph1`F18ڮ_]=8Tf]!lrEUGIN6y 4cZyNOGI$_ 3+y5`s>FX+dl V7/IQ &)H䓔cesZ4aSl f<' c`Y*g*Ŭa(eUً9}5A!gm)11C+*,0 އ C{>bh *^ 3-a!wgjC}?C~pu{˻Opqyss{m?dN֋O?,f-W|Y @#5ę%$$*al({0zCn}vnoןn?~?m&al~hwCllψLFFtu11M2aa<(IyI;4:& dQQ+!Lc9?BV"C% 3lkbUqqSZ3NLL5&s aatMӍM}7v}q.P4jH% &iu1 v C?^}a'$o:gJ0DdNdQڝssRfw$Q2"1ؚ;D 1mw]]էuGﲡSidá>;=z"Yı%,#dFZ1 8rQQ!4+;tEmHLAC]XS$PA]x8g d@7ҏl26fZX) g0 U"飌`D>?ݾo~{͡C׏>!h$(&1!RǪZ"Q?c߷~{w}CB&zS7*gc K\d;qpF0JPn3 [&#PH UԪ$cvi^]}{ӻ7CE]shHdmۄifvfSV"+2Sf T$?Q_ tD@A:Lf)uڠ"ܤ$)A '#Ihk@3Ba-٦(QET\7ziLl@΀%8 T !av{\o?^=~kЍ1E# B&%Cl jIHET~hۮp~:=C>o9;ɰVۇ]xصy7kjhɵ` JhΪXTK~4 ]Ӆv?ç7޽XEUTuo]lY9ɐ7 DA UEM}DB M'\,E"凐 A mj'HpHG0cH0czh;X)JccnMsx{ݧOo]m.xU5&M#S ґԨcccQD~M—`}_}lj Ǭ8Q€ m& PURpލ7ۛǏ7znmb5ԄB[|niƾA$AU\T0`9*2V,rgN)v2 "i JF"@a҉*F0(F ܧ6h$Ş`$p`P5]?3(M$~h ^_ysŻwW׷8NrbύD'rtzjM\ . 4Mm_z#o_wlON]fU95Vf Ĭ,ٻYAPgoMеm闧QDEShmm[vF UI!SH%*VT ejKpaLI;#Lfe@at:dv$4 0)+@^.AmX01y BD 1FCޗ6q$zDfnҘ.  uC)̬fFnVF MgDdy~ܽ~<r.3׺rSρxqXc_^?su65vhЪQIVBॺ!O7ۻ~8SΓ/$/J[aH,,}ֲDze' p H %^F p}EW/9ܱBpf Jt_܀ԅ́8f`r<çN\0MY0ꅈJGÃL?|7o?}{XRNus'Vp*>Cpia*RJiI?=\wA*nWp2vf6MK9a8NaqJ3^c2|@a̾XZ.3պ(;uD^VVe ,6Վ؄i/ WQQC!џF~»TTYC͝Xl.e%Tx"tZ1- + wjia?ݽ~?W_aIy$9">x.'K VqDJnuH @~j"#zKO%Yjkى)un4b5tUM!CiH@b'Dbm]/v(UHT#ZSy)x tIN '/l`vBC4+6#{ H륅Xq,RQKEZr?|7_Oog|HG'<;V037 X-@DԅyT? $n.h PYuZA' ؝z:T֫-Zu\mpT|tpft vLFLK<13Ra/%vׯja >8?yo_?yN>?Y<DR{댍sMzF"]==' (.pS4ɍ[PPNmiwy櫬WvNŏ|T~Y/TYIzDcO_\*Inu^Y,$$m%ny%^|UWyYto}nnS*O'sC9NMgRpvR)rYKgK{ t5`(7:YAd(bc@ӵ:QluOaq%׸\:}}$@=[YKcԓxVe1"fjIfC9lhl{W&ۘ+8c}|xo8_K٭-giks9W:ԗ/ Ukp"BY*A(@Rd`?a!?}eK8kCPiN䲚ΧO I#L TJc}9'ot<3)z]Xu1Ɛsٯ~+t ,ձd1XTV!"2$',BJz V}9CiY rQX%鬁k Fr{?M嵫U) S Rq,BBla㢽@Z;"u_4u/`j NB`'A*dށ̅\!HbkDū#M0;!D9"r*'X$s#8n#?o$^A5l.z7j]RmN7 E6:72 0 qT(P_AOR*sCPF::,($5l컫6IcJfڰ2Z{O Bxꣴz YjbZ1K*CT6O!;d!m3O` +HU\U}wr֋fZdDE+h`%`tf2 hqA3c󒾿?xՙd:_Ͻ5|%ȻFӭZlݻ>Knnjڻ{,,8ֵqܹ W&'OY@w f-UAŪ_!0 ހSJI0$"z`wY%ׇqr)k,N!o˛T3e WPBBhTf@Q ;\ŵ؄jpU»DW$ @LAR0&f ɇ |Ʀ.ˤ8NQi=jJَ8m*e8M-x2{z|d JN !hTPHTU6] 'W`;xH dA6AF@"q"~>8ogbtpRpXglq |ҽzQWQ{SM.uXبϜ-|Y;8{$N.*bI;9hBq^ ~4Q$t q ̶NpS,Td( q;ތC.rF7f} /_틫Q,~nͽ]vڷ@E}{>N\O:%rGoeU4aG麎z2\"j{r l5 )`Pu0r%B,F,OrjAv۹l%؛ey8?<,)?_#-4/O$NKz+*(H԰ML=th@  %HUd]14ʉum"̪>Q%-څ_mKƷӜ=|sYR>:re<ە^+ޞN&UUlFPp3DG "܉   ž F~pZߔj'ɮ_[7h_Ѐ!x_ W5C`vYq/{E^D(+RW^d% K&,n~7ü{[t?ۂ@ÿgYhoܙcG #BARzF$v"5}'u#ƴDGQ#Xr٧RFw>@ÿ) 8d[RUGٹI}.^mDЩPITŃj]˦wu%ӿח=+@Q 3TfwB=6:uk£/\〉]AB냱jM}uަ>KM>.v+–WnvL^/c+~$lĜS$;50/u<*Dtwւ@G &'G uwNyU^M! cS-xGC `X}WABܝ-4Y_,/$pKCrX= yCAF: *A䗁#`Z>vH@ Pd$Q0Ոt8T(׳1u6(?EdN8e p v7hxz] d\!Co:g#ľpT/Ȱ,e i!B`̵@p:54hKJRNxt`*<la-^nbA Z*e1xSU*ʶc$KJY#eLHEj]?T`dfX;4|$pfBp90 벗4Vq=IY%ED@y*0J@GYfGF/*$怐iV& >DA"Wyq"a#T#βs{tЗ` !#rt[2jRN#*ND"|oEVcz["QΎǝLW9k?"#I&L߭٥F Q`$.`qCΕR$+ 6[ ^s-%aqb M@\b9:s7ba3Z/ݶbuTՆ(@yoq2'>/Hf @.%)@'ל,\WnvA1}.4NO0,=?ɂ54|%SPrNV0OǺt}U Ejv`e2/.4\g_/l.f.fΗ2QBKAi.ŗbXrJSZɬ~I7hGb!'?Ԫ)e~ɧiI)䤑t 炿]~ucç':QUc>+Jn \<3-h!== @]r6ϋ@?F PxRL^s 7h}$Z7h>ϋ4>cyy΃'v \~ 4|F! m~CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC  IENDB`deathkiller-jazz2-native-2a7ccef/Sources/Info.plist.in000066400000000000000000000025421512772601700231130ustar00rootroot00000000000000 CFBundleDevelopmentRegion English CFBundleDocumentTypes CFBundleTypeRole Viewer LSHandlerRank Alternate LSItemContentTypes public.data CFBundleExecutable @CPACK_BUNDLE_NAME@ CFBundleIconFile @CPACK_BUNDLE_NAME@.icns CFBundleIdentifier @NCINE_REVERSE_DNS@ CFBundleInfoDictionaryVersion 6.0 CFBundleName @CPACK_BUNDLE_NAME@ CFBundlePackageType APPL CFBundleShortVersionString @NCINE_VERSION@ CFBundleSignature ???? CFBundleVersion CSResourcesFileMapped LSApplicationCategoryType public.app-category.games NSHumanReadableCopyright © 2016-@NCINE_BUILD_YEAR@ @CPACK_PACKAGE_VENDOR@ NSHighResolutionCapable deathkiller-jazz2-native-2a7ccef/Sources/Jazz2.vcxproj000066400000000000000000001774151512772601700231670ustar00rootroot00000000000000 Debug ARM64EC Debug Win32 Release ARM64EC Release Win32 Debug x64 Release x64 16.0 Win32Proj {7D39862C-AB77-4BE2-9DED-D330BD162623} Jazz2 10.0 Application true v142 Unicode Application false v142 true Unicode Application true v142 Unicode Application true v143 Unicode Application false v142 true Unicode Application false v143 true Unicode $(SolutionDir)\..\Sdílené soubory Death™ Common\Native\ $(SolutionDir)\..\Death™ Common\Native\ $(SolutionDir)\Sources\Shared\ $(ProjectDir) true Jazz2 $(SolutionDir)x86\$(Configuration)\ false Jazz2 $(SolutionDir)x86\$(Configuration)\ true Jazz2 $(SolutionDir)x64\$(Configuration)\ true Jazz2 false Jazz2 $(SolutionDir)x64\$(Configuration)\ false Jazz2 Level3 true _HAS_EXCEPTIONS=0;DEATH_DEBUG;DEATH_TRACE;WITH_GLEW;WITH_GLFW;WITH_AUDIO;WITH_THREADS;WITH_VORBIS;WITH_VORBIS_DYNAMIC;WITH_OPENMPT;WITH_ZLIB;WITH_BACKWARD;WIN32;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h MultiThreadedDebug AdvancedVectorExtensions2 Fast stdcpp17 $(ExtensionLibraryPath);$(ProjectDir)..\Libs\Includes;%(AdditionalIncludeDirectories) true Windows true false Level3 true true true _HAS_EXCEPTIONS=0;DEATH_TRACE;DEATH_TRACE_ASYNC;DEATH_TRACE_VERBOSE_IO;WITH_GLEW;WITH_GLFW;WITH_AUDIO;WITH_THREADS;WITH_VORBIS;WITH_VORBIS_DYNAMIC;WITH_OPENMPT;WITH_ZLIB;WITH_BACKWARD;WITH_ANGELSCRIPT;WIN32;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h MultiThreaded NoExtensions Fast stdcpp17 $(ExtensionLibraryPath);$(ProjectDir)..\Libs\Includes;%(AdditionalIncludeDirectories) true Windows false true true false false Level3 true _HAS_EXCEPTIONS=0;DEATH_DEBUG;DEATH_TRACE;DEATH_TRACE_ASYNC;DEATH_TRACE_VERBOSE_IO;WITH_GLEW;__WITH_GLFW;WITH_SDL;WITH_AUDIO;WITH_THREADS;WITH_VORBIS;WITH_VORBIS_DYNAMIC;WITH_OPENMPT;WITH_ZLIB;WITH_BACKWARD;WITH_ANGELSCRIPT;WITH_MULTIPLAYER;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h MultiThreadedDebug AdvancedVectorExtensions2 Fast stdcpp17 $(ExtensionLibraryPath);$(ProjectDir)..\Libs\Includes;%(AdditionalIncludeDirectories) true Windows true false Level3 true _HAS_EXCEPTIONS=0;DEATH_DEBUG;DEATH_TRACE;DEATH_TRACE_ASYNC;DEATH_TRACE_VERBOSE_IO;WITH_GLEW;WITH_GLFW;WITH_AUDIO;WITH_THREADS;WITH_VORBIS;WITH_VORBIS_DYNAMIC;WITH_OPENMPT;WITH_ZLIB;WITH_BACKWARD;WITH_ANGELSCRIPT;WITH_MULTIPLAYER;_DEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h MultiThreadedDebug Fast stdcpp17 $(ExtensionLibraryPath);$(ProjectDir)..\Libs\Includes;%(AdditionalIncludeDirectories) true Windows true false Level3 true true true _HAS_EXCEPTIONS=0;DEATH_TRACE;DEATH_TRACE_ASYNC;DEATH_TRACE_VERBOSE_IO;WITH_GLEW;WITH_GLFW;WITH_AUDIO;WITH_THREADS;WITH_VORBIS;WITH_VORBIS_DYNAMIC;WITH_OPENMPT;WITH_ZLIB;WITH_BACKWARD;__WITH_ANGELSCRIPT;WITH_MULTIPLAYER;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h MultiThreaded AdvancedVectorExtensions2 Fast stdcpp17 $(ExtensionLibraryPath);$(ProjectDir)..\Libs\Includes;%(AdditionalIncludeDirectories) true Windows false true true false Level3 true true true _HAS_EXCEPTIONS=0;DEATH_TRACE;DEATH_TRACE_ASYNC;DEATH_TRACE_VERBOSE_IO;WITH_GLEW;WITH_GLFW;WITH_AUDIO;WITH_THREADS;WITH_VORBIS;WITH_VORBIS_DYNAMIC;WITH_OPENMPT;WITH_ZLIB;WITH_BACKWARD;__WITH_ANGELSCRIPT;WITH_MULTIPLAYER;NDEBUG;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true NotUsing pch.h MultiThreaded Fast stdcpp17 $(ExtensionLibraryPath);$(ProjectDir)..\Libs\Includes;%(AdditionalIncludeDirectories) true Windows false true true false deathkiller-jazz2-native-2a7ccef/Sources/Jazz2.vcxproj.filters000066400000000000000000003473221512772601700246320ustar00rootroot00000000000000 {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd {66aba08b-58e3-46ab-b8fc-d41aed34380c} {7609aa2a-09c4-45c3-97b4-07fa31c68172} {19840dcc-d21f-4e95-9c2a-109404387642} {d0ce4523-b7b4-45ed-9184-c39300bbef9b} {1b08075b-21c1-4f64-a742-4ecbe22879fb} {1251ca81-4f8c-41dd-b37d-03cd22b356c8} {d2beec57-7003-42d7-b22f-b563df9d803c} {7015d501-0fbc-49f0-be08-b8d6099fd9a9} {4b5dd7f9-75e1-41de-8421-50358d03dfe9} {a959c99b-4745-4a20-8185-280a49bac222} {89144cf1-d95c-46b1-9196-16537338f431} {15fe9e4f-88e7-4418-bd1e-185e04df0ff6} {f9dcdbac-3727-4080-8c43-3d70cbf7c09e} {8f3b583f-dafa-476b-a09b-8f8bf916fd9f} {14cf239c-a04a-45e5-9b98-73de29aff458} {5f2adbaa-a641-44b8-8b21-fb52eb85eddc} {a9240185-7aca-468b-87ea-256d73bf2a24} {14077be7-3ef9-4d98-a3a1-bf9f6e6ace47} {23fadb05-bb1f-486a-8b67-41bf625ac6e9} {aecd7014-0204-4625-b064-09c13d53de12} {975de453-6bd0-4316-8406-ea0286fa86a0} {2eb75e32-c50f-468c-920c-784a0845fe83} {519c39a5-6f2f-4c30-9b2c-2e1ce566a1d2} {3dd90479-fc1e-496b-8981-a22af66f242f} {08f2605c-9261-4ad2-bb0e-e762c9234663} {fc925b25-6fe6-4878-b90c-381875ebafce} {24b4b922-2cbb-4ed8-bbf0-a13a9a21e4e7} {542d7d67-d898-43c8-b9c0-e8c01b1c4619} {dc902cfb-8e5b-4ac1-a585-7ae3b6b74b5c} {4b80debe-5716-4bd0-a661-34670989abdf} {89332f4f-8dd1-479e-a4cd-803ca0686e13} {646e268b-5901-4372-936f-88d60fc4c19f} {d48c8ef6-f2aa-4271-8969-2390b111249d} {6f48b4c9-dc8c-43cd-af13-0236fcd9130d} {9e5b0b19-ff74-4133-9a57-6a38648c2280} {f14c807a-cda1-4d6b-8b0b-7b7b74a9f565} {4d146257-6633-4254-9029-55f805e74db6} {57513633-b81a-446d-af22-8e3f73cf39f2} {eb7c0048-96a1-4540-9063-690a14a526cc} {ba8465dc-da58-4362-b4fe-1c831c184563} {5de8bce6-a2aa-4513-886c-4bdcb532a14e} {a65d3a71-4070-4f0b-84d8-d8be78337c28} {633274d8-a360-48ab-9529-d01e4b9cd4ae} {28b29c21-7d11-474a-bbf2-a494645dd07e} {672eb0e5-0833-4fa0-b5be-661143817a60} {21785585-fbef-4c0c-b464-f205ee8076fd} {90e78042-998d-48ca-acc9-5bab601629ce} {0bf9b192-885e-4de8-a0a3-b8b346fa6b8e} {e4b4004b-eae4-4a74-853e-4d4318310b74} {e6c68c6e-edbd-4024-90a5-9cfb436da67f} {66756cef-10ce-4ae3-ade6-87422c5ddb2e} {5e949f82-18ce-4137-97f1-bbcc7d2285c9} {8e8a53f2-8324-4eaa-ab44-82141d569c85} {0fd6dab6-c92b-45ba-a620-98604d239e84} {3cffbbe9-dfa3-4952-9d63-803931646ca8} {2617eb82-0e20-47d7-9288-35f78e88f537} {5a47b260-7c5c-4464-a7ac-e9a6fbe91a23} {91ccf582-924e-4b60-813c-738218e484f2} {bcee5bd1-18ac-41d1-823a-18478dcf8523} {9f55d519-1d20-4371-8846-4414bdb959cc} {dfcbe8c2-0d95-4ba7-b5b7-60a9b2cc028c} {195cb373-35dc-405c-a3c6-8c10516ecb04} {416ca28f-5529-413b-b6b9-1f5c7cdf2b0d} {f0d88d86-bb58-4877-b356-365d04655da0} {5a410bc7-e012-4efc-a75b-41bf6b0e9771} {a72d645b-36b7-485e-b3f6-5ec77cbd0ee2} {1043539e-37bb-46ed-a0ec-cf31f13ad9cb} {a9afcf08-b35b-492f-9351-be1edd35d7bb} {89a193bb-8bee-41fe-9308-44e95e8ff9bc} {a0c711bb-1873-48f2-b0ce-164cf83305bf} {7de2dc9b-0c39-47b3-991e-9a71db70648a} {e87f0049-c3b9-47e3-98f4-c5a7f7817396} {fbd151b2-7efb-45b9-a98b-582cf95d2a3b} {f91f3d2c-18d4-4f04-acf1-f649b1cc0406} {9fd51680-3a83-42c4-94aa-b0cd99d88bce} {1507ea40-2384-499d-b295-afe88c90b994} {e213d1ee-1e55-4994-b2c1-5e13336c39f6} {68e92d28-11e9-47df-a1f0-7501edb86af8} {dfcad49c-23cc-4162-960a-23fd6033b0c0} {fb17beb5-a46a-47c8-a04e-af8c49fc7eb4} {a03646cd-077d-46ce-84d2-8d0023de9b50} {28c08749-4861-4751-a607-1c2511ec63f0} {2fdc883a-97b8-463f-a9d4-accf54c50825} {ed3ea44b-0e5e-4811-b0a3-a1303a3f4fa6} Header Files Header Files Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Input Header Files\nCine\Input Header Files\nCine\Input Header Files\nCine\Input Header Files\nCine\Input Header Files\nCine\Graphics Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Graphics\GL Header Files\nCine\Input Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Graphics Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Audio Header Files\nCine\Threading Header Files\nCine\Threading Header Files\nCine\Threading Header Files\nCine\Threading Header Files\nCine\Threading Header Files\nCine\Graphics Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Graphics Header Files\nCine\Primitives Header Files\nCine\Primitives Header Files\nCine\Primitives Header Files\nCine\Primitives Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Base Header Files\nCine\Primitives Header Files\nCine\Primitives Header Files\nCine\Primitives Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2\Actors Header Files\Jazz2\Events Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2\Events Header Files\Jazz2 Header Files\Jazz2 Header Files\nCine\Primitives Header Files\Jazz2\Tiles Header Files\Jazz2\Tiles Header Files\Jazz2\Actors Header Files\nCine\Base Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Collisions Header Files\Jazz2\Collisions Header Files\Jazz2\Actors Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Weapons Header Files\Shared Header Files\nCine Header Files\nCine Header Files\nCine Header Files\nCine Header Files\nCine Header Files\nCine Header Files\nCine Header Files\Shared Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\Jazz2\Compatibility Header Files\nCine\Base Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Collectibles Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\Jazz2 Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\nCine\Audio Header Files\nCine\Audio Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Environment Header Files\Jazz2 Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Environment Header Files\nCine\Base\ParallelHashMap Header Files\nCine\Base\ParallelHashMap Header Files\nCine\Base\ParallelHashMap Header Files\nCine\Base\ParallelHashMap Header Files\nCine\Base\ParallelHashMap Header Files\nCine\Base\ParallelHashMap Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Lighting Header Files\Jazz2\Actors\Lighting Header Files\Jazz2\Actors\Lighting Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Enemies Header Files\nCine Header Files\nCine Header Files\nCine\Primitives Header Files\Shared Header Files\Jazz2\UI Header Files\Jazz2\UI Header Files\Jazz2\UI Header Files\Jazz2\UI Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Solid Header Files\Jazz2 Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\Compatibility Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Environment Header Files\Jazz2\UI\Menu Header Files\Jazz2\Actors Header Files\Jazz2 Header Files\Jazz2\UI\Menu Header Files\Jazz2 Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\UI Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\Scripting Header Files\Jazz2\Scripting Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Solid Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Scripting Header Files\Jazz2\Scripting Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Collectibles Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Scripting Header Files\Jazz2\Scripting Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Enemies Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\Actors\Enemies\Bosses Header Files\Jazz2\UI\Menu Header Files\Jazz2\Actors\Environment Header Files\Jazz2\Actors\Enemies\Bosses Header Files\nCine\Backends Header Files\nCine\Backends Header Files\nCine\Backends Header Files\nCine\Backends Header Files\nCine\Backends Header Files\nCine\Backends Header Files\nCine\Backends Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\Actors\Environment Header Files\Jazz2\UI Header Files\Jazz2\Actors\Environment Header Files\nCine Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2 Header Files\Shared Header Files\Shared Header Files\Shared Header Files\Shared Header Files\Shared Header Files\Shared Header Files\Jazz2\Scripting Header Files\Jazz2\Scripting Header Files\Shared Header Files\Shared Header Files\Shared\Containers Header Files\nCine\Graphics Header Files\Jazz2\UI\Menu Header Files\Jazz2\Compatibility Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Weapons Header Files\Jazz2\Actors\Weapons Header Files\Shared\IO Header Files\Shared\IO Header Files\Shared\IO Header Files\Shared\IO Header Files\Shared\IO Header Files\Shared Header Files\Jazz2\Scripting Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Jazz2\Multiplayer Header Files\Jazz2\Multiplayer Header Files\Jazz2\Multiplayer Header Files\Jazz2\Multiplayer Header Files\Jazz2\Multiplayer Header Files\Jazz2\Tiles Header Files\Jazz2 Header Files\Jazz2\Multiplayer\Backends Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2 Header Files\Jazz2\Tiles Header Files\Jazz2\Tiles Header Files\Jazz2\UI\Menu Header Files\Jazz2\Multiplayer Header Files\Shared\Base Header Files\Jazz2\Multiplayer Header Files\Shared\Containers Header Files\nCine\Graphics Header Files\nCine\Graphics Header Files\nCine\Input Header Files\nCine\Backends Header Files\nCine\Backends Header Files\nCine\Graphics Header Files\Jazz2\Scripting Header Files\Jazz2\Scripting Header Files\Jazz2 Header Files\Jazz2\UI Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Jazz2\Multiplayer Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Shared\Base Header Files\Jazz2\Actors\Multiplayer Header Files\Jazz2\Actors\Multiplayer Header Files\Jazz2\Actors\Multiplayer Header Files\Jazz2\Actors\Multiplayer Header Files\Jazz2\Actors\Multiplayer Header Files\Jazz2\UI\Menu Header Files\Jazz2\UI\Menu Header Files\Jazz2\Multiplayer Header Files\Jazz2\UI\Menu Header Files\nCine\Base\pdqsort Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Shared\IO Header Files\Shared\IO Header Files\Shared\Base Header Files\Jazz2\UI Header Files\Jazz2 Header Files\Shared\Threading\Implementation Header Files\Shared\Threading Header Files\Shared\Core Header Files\Shared\Core Header Files\Shared\Core Header Files\Shared\Threading Header Files\Jazz2 Header Files\Shared\Containers Header Files\Jazz2 Header Files\Jazz2\UI\Menu Header Files\Shared\IO Header Files\Shared\IO Header Files\Shared\IO\Compression Header Files\Shared\IO\Compression Header Files\Shared\IO\Compression Header Files\Jazz2\UI Header Files\nCine Header Files Header Files\Jazz2\Rendering Header Files\Jazz2\Rendering Header Files\Jazz2\Rendering Header Files\Jazz2\Rendering Header Files\Jazz2\Rendering Header Files\Shared\Base Header Files\Jazz2 Header Files\Jazz2\Input Header Files\Jazz2\Input Header Files\Jazz2\Input Header Files\Jazz2\Input Header Files\Jazz2\UI\Multiplayer Header Files\Jazz2\UI\Multiplayer Header Files\Jazz2 Header Files\Jazz2\Multiplayer Header Files\Jazz2\Multiplayer Header Files\Jazz2\UI\Menu Header Files\nCine\Primitives Header Files\Shared\IO Header Files\Shared\Containers Header Files\Shared\Containers Header Files\Jazz2\UI\Multiplayer Header Files\Jazz2\Multiplayer Header Files\nCine\Threading Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\jsoncpp Header Files\Jazz2\Actors\Multiplayer Header Files\Shared\Base Header Files\Shared\Cryptography Source Files Source Files\nCine Source Files\nCine Source Files\nCine Source Files\nCine Source Files\Jazz2 Source Files\Jazz2\Actors Source Files\Jazz2\Events Source Files\Jazz2 Source Files\Jazz2\Events Source Files\Jazz2\Tiles Source Files\Jazz2\Tiles Source Files\Jazz2\Actors Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Collisions Source Files\Jazz2\Collisions Source Files\Jazz2\Actors Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Weapons Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Base Source Files\nCine\Base Source Files\nCine\Base Source Files\nCine\Primitives Source Files\nCine\Primitives Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Graphics\GL Source Files\nCine\Audio Source Files\nCine\Audio Source Files\nCine\Graphics Source Files\nCine\Input Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Base Source Files\nCine\Base Source Files\nCine\Base Source Files\nCine\Base Source Files\nCine\Threading Source Files\nCine\Threading Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\nCine\Input Source Files\nCine\Threading Source Files\Shared Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Compatibility Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Collectibles Source Files\nCine\Graphics Source Files\nCine\Base Source Files\nCine\Graphics Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\nCine\Audio Source Files\nCine\Audio Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Shared\Containers Source Files\Shared\Containers Source Files\Shared\Containers Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Lighting Source Files\Jazz2\Actors\Lighting Source Files\Jazz2\Actors\Lighting Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Enemies Source Files\Shared Source Files\Jazz2\UI Source Files\Jazz2\UI Source Files\Jazz2\UI Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Solid Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Environment Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors Source Files\Jazz2 Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\UI Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\Scripting Source Files\Jazz2\Scripting Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Solid Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Scripting Source Files\Jazz2\Scripting Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Collectibles Source Files\Jazz2\Actors\Environment Source Files\nCine\Base Source Files\Jazz2\Scripting Source Files\Jazz2\Scripting Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Enemies Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\Actors\Enemies\Bosses Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors\Environment Source Files\Jazz2\Actors\Enemies\Bosses Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Backends Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors\Environment Source Files\Jazz2\UI Source Files\Jazz2\Actors\Environment Source Files\nCine Source Files\Jazz2\UI\Menu Source Files\Shared Source Files\Jazz2\Scripting Source Files\Jazz2\Scripting Source Files\nCine\Graphics Source Files\Jazz2\UI\Menu Source Files\Jazz2\Compatibility Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Weapons Source Files\Jazz2\Actors\Weapons Source Files\Shared\IO Source Files\Shared\IO Source Files\Shared\IO Source Files\Shared\IO Source Files\Jazz2\Scripting Source Files\Shared\Containers Source Files\Jazz2\Multiplayer Source Files\Jazz2\Multiplayer Source Files\Shared\IO Source Files\Jazz2 Source Files\Jazz2\Multiplayer Source Files\nCine\Input Source Files\nCine\Backends Source Files\nCine\Backends Source Files\nCine\Graphics Source Files\nCine\Graphics Source Files\Jazz2\Scripting Source Files\Jazz2\Scripting Source Files\Jazz2\UI Source Files\Shared\Containers Source Files\Jazz2\Multiplayer Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\Actors\Multiplayer Source Files\Jazz2\Actors\Multiplayer Source Files\Jazz2\Actors\Multiplayer Source Files\Jazz2\Actors\Multiplayer Source Files\Jazz2\Actors\Multiplayer Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Jazz2\UI\Menu Source Files\Shared\IO Source Files\Jazz2\UI Source Files\Shared\Threading\Implementation Source Files\Shared\Core Source Files\Jazz2\UI\Menu Source Files\Shared\IO Source Files\Shared\IO Source Files\Shared\IO\Compression Source Files\Shared\IO\Compression Source Files\Shared\IO\Compression Source Files\Jazz2\UI Source Files\Jazz2\Rendering Source Files\Jazz2\Rendering Source Files\Jazz2\Rendering Source Files\Jazz2\Rendering Source Files\Jazz2\Rendering Source Files\nCine\Threading Source Files\Jazz2\Input Source Files\Jazz2\Input Source Files\Jazz2\Input Source Files\Jazz2\UI\Multiplayer Source Files\Jazz2\UI\Multiplayer Source Files\Jazz2 Source Files\Jazz2\Multiplayer Source Files\Jazz2\UI\Menu Source Files\nCine\Primitives Source Files\Shared\IO Source Files\Jazz2\UI\Multiplayer Source Files\jsoncpp Source Files\jsoncpp Source Files\jsoncpp Source Files\Jazz2\Actors\Multiplayer Source Files\Shared\Base Source Files\Shared\Cryptography Source Files\Jazz2\UI\Menu Resource Files Resource Files Resource Files\Icons Resource Files\Icons deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/000077500000000000000000000000001512772601700215335ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/000077500000000000000000000000001512772601700227665ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/ActorBase.cpp000066400000000000000000001524771512772601700253550ustar00rootroot00000000000000#include "ActorBase.h" #include "../ContentResolver.h" #include "../ILevelHandler.h" #include "../PreferencesCache.h" #include "../Events/EventMap.h" #include "../Tiles/TileMap.h" #include "../Collisions/DynamicTreeBroadPhase.h" #include "Explosion.h" #include "Player.h" #include "Solid/Pole.h" #include "Solid/PushableBox.h" #include "Weapons/ShieldFireShot.h" #include "Weapons/FreezerShot.h" #include "Weapons/ToasterShot.h" #include "Weapons/Thunderbolt.h" #if !defined(WITH_COROUTINES) # pragma message("WITH_COROUTINES is not defined, building without asynchronous loading support") #endif #include "../../nCine/tracy.h" #include "../../nCine/Primitives/Matrix4x4.h" #include "../../nCine/Base/Random.h" #include "../../nCine/Base/FrameTimer.h" using namespace Jazz2::Tiles; using namespace nCine; namespace Jazz2::Actors { ActorBase::ActorBase() : _state(ActorState::None), _levelHandler(nullptr), _internalForceY(0.0f), _elasticity(0.0f), _friction(1.5f), _unstuckCooldown(0.0f), _frozenTimeLeft(0.0f), _maxHealth(1), _health(1), _spawnFrames(0.0f), _metadata(nullptr), _renderer(this), _currentAnimation(nullptr), _currentTransition(nullptr), _currentTransitionCancellable(false), _collisionProxyID(Collisions::NullNode) { } ActorBase::~ActorBase() { } bool ActorBase::IsFacingLeft() const { return GetState(ActorState::IsFacingLeft); } void ActorBase::SetFacingLeft(bool value) { if (IsFacingLeft() == value) { return; } SetState(ActorState::IsFacingLeft, value); _renderer.setFlippedX(value); // Recalculate hotspot GraphicResource* res = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); if (res != nullptr) { _renderer.Hotspot.X = static_cast(IsFacingLeft() ? (res->Base->FrameDimensions.X - res->Base->Hotspot.X) : res->Base->Hotspot.X); _renderer.setAbsAnchorPoint(_renderer.Hotspot.X, _renderer.Hotspot.Y); } } void ActorBase::SetParent(SceneNode* parent) { _renderer.setParent(parent); } Task ActorBase::OnActivated(const ActorActivationDetails& details) { _state |= details.State | ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation; _levelHandler = details.LevelHandler; _pos = Vector2f((float)details.Pos.X, (float)details.Pos.Y); _originTile = Vector2i((std::int32_t)details.Pos.X / 32, (std::int32_t)details.Pos.Y / 32); _spawnFrames = _levelHandler->GetElapsedFrames(); std::uint16_t layer = (std::uint16_t)details.Pos.Z; _renderer.setLayer(layer); bool success = async_await OnActivatedAsync(details); _renderer.setPosition(std::round(_pos.X), std::round(_pos.Y)); OnUpdateHitbox(); _state |= ActorState::Initialized; async_return success; } Task ActorBase::OnActivatedAsync(const ActorActivationDetails& details) { // This should be overridden and return true async_return false; } bool ActorBase::OnTileDeactivated() { return true; } void ActorBase::OnAttach(ActorBase* parent) { // Can be overridden } void ActorBase::OnDetach(ActorBase* parent) { // Can be overridden } void ActorBase::OnUpdate(float timeMult) { TileCollisionParams params = { TileDestructType::None, _speed.Y >= 0.0f }; TryStandardMovement(timeMult, params); OnUpdateHitbox(); UpdateFrozenState(timeMult); } void ActorBase::OnUpdateHitbox() { if (_metadata != nullptr) { UpdateHitbox(_metadata->BoundingBox.X, _metadata->BoundingBox.Y); } } bool ActorBase::OnDraw(RenderQueue& renderQueue) { // Override and return true to suppress default rendering return false; } void ActorBase::OnHealthChanged(ActorBase* collider) { // Can be overridden } bool ActorBase::OnPerish(ActorBase* collider) { if (GetState(ActorState::IsCreatedFromEventMap)) { auto events = _levelHandler->EventMap(); if (events != nullptr) { events->Deactivate(_originTile.X, _originTile.Y); events->StoreTileEvent(_originTile.X, _originTile.Y, EventType::Empty); } } _state |= ActorState::IsDestroyed | ActorState::SkipPerPixelCollisions; return true; } void ActorBase::OnHitFloor(float timeMult) { // Called from inside the position update code when the object hits floor // and was falling earlier. Objects should override this if they need to // (e.g. the Player class playing a sound). } void ActorBase::OnHitCeiling(float timeMult) { // Called from inside the position update code when the object hits ceiling. // Objects should override this if they need to. } void ActorBase::OnHitWall(float timeMult) { // Called from inside the position update code when the object hits a wall. // Objects should override this if they need to. } bool ActorBase::OnHandleCollision(std::shared_ptr other) { if (GetState(ActorState::CanBeFrozen)) { HandleFrozenStateChange(other.get()); } return false; } bool ActorBase::CanCauseDamage(ActorBase* collider) { return false; } void ActorBase::OnTriggeredEvent(EventType eventType, std::uint8_t* eventParams) { // Can be overridden } void ActorBase::TryStandardMovement(float timeMult, TileCollisionParams& params) { ZoneScoped; if (_unstuckCooldown > 0.0f) { _unstuckCooldown -= timeMult; } float currentGravity; float currentElasticity = _elasticity; if ((_state & ActorState::ApplyGravitation) == ActorState::ApplyGravitation) { currentGravity = _levelHandler->GetGravity(); if (_pos.Y >= _levelHandler->GetWaterLevel()) { currentGravity *= 0.5f; currentElasticity *= 0.7f; } } else { currentGravity = 0.0f; } float accelY = (_internalForceY + _externalForce.Y) * timeMult; _speed.X = std::clamp(_speed.X, -16.0f, 16.0f); _speed.Y = std::clamp(_speed.Y + accelY, -16.0f, 16.0f); float effectiveSpeedX, effectiveSpeedY; if (_frozenTimeLeft > 0.0f) { effectiveSpeedX = std::clamp(_externalForce.X * timeMult, -16.0f, 16.0f); effectiveSpeedY = ((_state & ActorState::ApplyGravitation) == ActorState::ApplyGravitation ? (_speed.Y + 0.5f * accelY) : std::clamp(((currentGravity * 2.0f) + _internalForceY) * timeMult, -16.0f, 16.0f)); } else { effectiveSpeedX = _speed.X + _externalForce.X * timeMult; effectiveSpeedY = _speed.Y + 0.5f * accelY; } effectiveSpeedX *= timeMult; effectiveSpeedY *= timeMult; if (std::abs(effectiveSpeedX) > 0.0f || std::abs(effectiveSpeedY) > 0.0f) { if (GetState(ActorState::CanJump | ActorState::ApplyGravitation)) { // All ground-bound movement is handled here. In the basic case, the actor // moves horizontally, but it can also logically move up or down if it is // moving across a slope. In here, angles between about 45 degrees down // to 45 degrees up are attempted with some intervals to attempt to keep // the actor attached to the slope in question. // Always try values a bit over the 45 degree incline; subpixel coordinates // may mean the actor actually needs to move a pixel up or down even though // the speed wouldn't warrant that large of a change. // Not doing this will cause hiccups with uphill slopes in particular. // Beach tileset also has some spots where two properly set up adjacent // tiles have a 2px jump, so adapt to that. bool success = false; float maxYDiff = std::max(3.0f, std::abs(effectiveSpeedX) + 2.5f); for (float yDiff = maxYDiff + effectiveSpeedY; yDiff >= -maxYDiff + effectiveSpeedY; yDiff -= CollisionCheckStep) { if (MoveInstantly(Vector2f(effectiveSpeedX, yDiff), MoveType::Relative, params)) { success = true; break; } } // Also try to move horizontally as far as possible float xDiff = std::abs(effectiveSpeedX); float maxXDiff = -xDiff; if (!success) { std::int32_t sign = (effectiveSpeedX > 0.0f ? 1 : -1); for (; xDiff >= maxXDiff; xDiff -= CollisionCheckStep) { if (MoveInstantly(Vector2f(xDiff * sign, 0.0f), MoveType::Relative, params)) { success = true; break; } } bool moved = false; if (!success && _unstuckCooldown <= 0.0f) { AABBf aabb = AABBInner; float t = aabb.B - 14.0f; if (aabb.T < t) { aabb.T = t; } TileCollisionParams params2 = { TileDestructType::None, true }; if (!_levelHandler->IsPositionEmpty(this, aabb, params2)) { for (float yDiff = -2.0f; yDiff >= -12.0f; yDiff -= 2.0f) { if (MoveInstantly(Vector2f(0.0f, yDiff), MoveType::Relative, params)) { moved = true; _unstuckCooldown = 60.0f; break; } } if (!moved) { for (float yDiff = 2.0f; yDiff <= 14.0f; yDiff += 2.0f) { if (MoveInstantly(Vector2f(0.0f, yDiff), MoveType::Relative, params)) { moved = true; _unstuckCooldown = 60.0f; break; } } } } } if (!moved) { // If no angle worked in the previous step, the actor is facing a wall if (xDiff > CollisionCheckStep || (xDiff > 0.0f && currentElasticity > 0.0f)) { _speed.X = -(currentElasticity * _speed.X); _externalForce.X = 0.0f; } OnHitWall(timeMult); } } } else { // Airborne movement is handled here // First, attempt to move directly based on the current speed values if (!MoveInstantly(Vector2f(effectiveSpeedX, effectiveSpeedY), MoveType::Relative, params)) { // First, attempt to move horizontally as much as possible float maxDiff = std::abs(effectiveSpeedX); float xDiff = maxDiff; for (; xDiff > std::numeric_limits::epsilon(); xDiff -= CollisionCheckStep) { if (MoveInstantly(Vector2f(std::copysign(xDiff, effectiveSpeedX), 0.0f), MoveType::Relative, params)) { break; } } // Then, try the same vertically bool hitCalled = false; maxDiff = std::abs(effectiveSpeedY); float yDiff = maxDiff; for (; yDiff > std::numeric_limits::epsilon(); yDiff -= CollisionCheckStep) { float yDiffSigned = std::copysign(yDiff, effectiveSpeedY); if (MoveInstantly(Vector2f(0.0f, yDiffSigned), MoveType::Relative, params) || // Add horizontal tolerance MoveInstantly(Vector2f(yDiff * 0.2f, yDiffSigned), MoveType::Relative, params) || MoveInstantly(Vector2f(yDiff * -0.2f, yDiffSigned), MoveType::Relative, params)) { break; } } if (effectiveSpeedY < 0.0f) { if (-yDiff > effectiveSpeedY) { // Reset speed and also internal force, mainly because of player jump _speed.Y = 0.0f; if (_internalForceY < 0.0f) { _internalForceY = 0.0f; } OnHitCeiling(timeMult); hitCalled = true; } } else if (effectiveSpeedY > 0.0f && yDiff < effectiveSpeedY && currentGravity <= 0.0f) { // If there is no gravity and actor is touching floor, the callback wouldn't be called otherwise OnHitFloor(timeMult); hitCalled = true; } // If the actor didn't move all the way horizontally, it hit a wall (or was already touching it) if (xDiff < std::abs(effectiveSpeedX) * 0.3f) { if (xDiff > 0.0f && currentElasticity > 0.0f) { _speed.X = -(currentElasticity * _speed.X); _externalForce.X = 0.0f; } // Don't call OnHitWall() if OnHitFloor() or OnHitCeiling() was called this step if (!hitCalled) { OnHitWall(timeMult); } } } } } // Reduce all forces if they are present if (std::abs(_externalForce.X) > 0.0f) { if (_externalForce.X > 0.0f) { _externalForce.X = std::max(_externalForce.X - _friction * timeMult, 0.0f); } else { _externalForce.X = std::min(_externalForce.X + _friction * timeMult, 0.0f); } } // Handle gravity and collision with floor if (currentGravity > 0.0f) { if (_speed.Y >= 0.0f) { // Actor is going down AABBf aabb = AABBInner; aabb.B += CollisionCheckStep; if (_levelHandler->IsPositionEmpty(this, aabb, params)) { // There is still some space below - only apply gravity SetState(ActorState::CanJump, false); _speed.Y += currentGravity * timeMult; } else { // Actor is on the floor OnHitFloor(timeMult); if (currentElasticity != 0.0f) { SetState(ActorState::CanJump, false); _speed.Y = -(currentElasticity * effectiveSpeedY / timeMult); } else { SetState(ActorState::CanJump, true); _speed.Y = 0.0f; } } } else { // Actor is going up - only apply gravity SetState(ActorState::CanJump, false); _speed.Y += currentGravity * timeMult; } _externalForce.Y = std::min(_externalForce.Y + currentGravity * 0.33f * timeMult, 0.0f); _internalForceY = std::min(_internalForceY + currentGravity * 0.33f * timeMult, 0.0f); } } void ActorBase::UpdateHitbox(std::int32_t w, std::int32_t h) { if (_currentAnimation == nullptr) { return; } auto& base = _currentAnimation->Base; if (base->Coldspot != Vector2i(ContentResolver::InvalidValue, ContentResolver::InvalidValue)) { AABBInner = AABBf( _pos.X - base->Hotspot.X + base->Coldspot.X - (w / 2), _pos.Y - base->Hotspot.Y + base->Coldspot.Y - h, _pos.X - base->Hotspot.X + base->Coldspot.X + (w / 2), _pos.Y - base->Hotspot.Y + base->Coldspot.Y ); } else { // Collision base set to the bottom of the sprite. // This is probably still not the correct way to do it, but at least it works for now. AABBInner = AABBf( _pos.X - (w / 2), _pos.Y - base->Hotspot.Y + base->FrameDimensions.Y - h, _pos.X + (w / 2), _pos.Y - base->Hotspot.Y + base->FrameDimensions.Y ); } } void ActorBase::CreateParticleDebrisOnPerish(ActorBase* collider) { ParticleDebrisEffect effect; if (runtime_cast(collider) || runtime_cast(collider)) { effect = ParticleDebrisEffect::Fire; } else if (runtime_cast(collider)) { effect = ParticleDebrisEffect::Lightning; } else if (_pos.Y > _levelHandler->GetWaterLevel()) { effect = ParticleDebrisEffect::StandardInWater; } else if (_frozenTimeLeft > 0.0f) { effect = ParticleDebrisEffect::Frozen; } else { effect = ParticleDebrisEffect::Standard; } Vector2f speed; if (runtime_cast(collider) || runtime_cast(collider)) { speed = Vector2f(0.0f, -1.0f); } else if (runtime_cast(collider)) { speed = _pos - collider->_pos; } else if (collider != nullptr) { speed = collider->_speed;; } CreateParticleDebrisOnPerish(effect, speed); } void ActorBase::CreateParticleDebrisOnPerish(ParticleDebrisEffect effect, Vector2f speed) { _levelHandler->HandleCreateParticleDebrisOnPerish(this, effect, speed); auto tilemap = _levelHandler->TileMap(); if (tilemap == nullptr) { return; } GraphicResource* res = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); Texture* texture = res->Base->TextureDiffuse.get(); if (texture == nullptr) { return; } float x = _pos.X - res->Base->Hotspot.X; float y = _pos.Y - res->Base->Hotspot.Y; if (effect == ParticleDebrisEffect::Fire) { constexpr std::int32_t DebrisSize = 3; Vector2i texSize = texture->GetSize(); for (std::int32_t fy = 0; fy < res->Base->FrameDimensions.Y; fy += DebrisSize + 1) { for (std::int32_t fx = 0; fx < res->Base->FrameDimensions.X; fx += DebrisSize + 1) { float currentSize = DebrisSize * Random().FastFloat(0.8f, 1.1f); Tiles::TileMap::DestructibleDebris debris{}; debris.Pos = Vector2f(x + (IsFacingLeft() ? res->Base->FrameDimensions.X - fx : fx), y + fy); debris.Depth = _renderer.layer(); debris.Size = Vector2f(currentSize, currentSize); debris.Speed = Vector2f(((fx - res->Base->FrameDimensions.X / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(0.5f, 2.0f) / res->Base->FrameDimensions.X, Random().FastFloat(0.0f, 0.2f)); debris.Acceleration = Vector2(0.0f, 0.06f); debris.Scale = 1.0f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.001f; debris.Time = 320.0f; debris.TexScaleX = (currentSize / float(texSize.X)); debris.TexBiasX = (((float)(_renderer.CurrentFrame % res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.X) + ((float)fx / float(texSize.X))); debris.TexScaleY = (currentSize / float(texSize.Y)); debris.TexBiasY = (((float)(_renderer.CurrentFrame / res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.Y) + ((float)fy / float(texSize.Y))); debris.DiffuseTexture = texture; debris.Flags = Tiles::TileMap::DebrisFlags::Bounce; tilemap->CreateDebris(debris); } } return; } if (effect == ParticleDebrisEffect::Lightning) { constexpr std::int32_t DebrisSize = 3; Vector2i texSize = texture->GetSize(); for (std::int32_t fy = 0; fy < res->Base->FrameDimensions.Y; fy += DebrisSize + 1) { for (std::int32_t fx = 0; fx < res->Base->FrameDimensions.X; fx += DebrisSize + 1) { float currentSize = DebrisSize * Random().FastFloat(0.4f, 1.1f); Tiles::TileMap::DestructibleDebris debris{}; debris.Pos = Vector2f(x + (IsFacingLeft() ? res->Base->FrameDimensions.X - fx : fx), y + fy); debris.Depth = _renderer.layer(); debris.Size = Vector2f(currentSize, currentSize); debris.Speed = Vector2f(((fx - res->Base->FrameDimensions.X / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(2.0f, 4.0f) / res->Base->FrameDimensions.X, ((fy - res->Base->FrameDimensions.Y / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(2.0f, 4.0f) / res->Base->FrameDimensions.Y); debris.Acceleration = Vector2f::Zero; debris.Scale = 1.0f; debris.ScaleSpeed = -0.004f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.004f; debris.Time = Random().FastFloat(10.0f, 50.0f); debris.TexScaleX = (currentSize / float(texSize.X)); debris.TexBiasX = (((float)(_renderer.CurrentFrame % res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.X) + ((float)fx / float(texSize.X))); debris.TexScaleY = (currentSize / float(texSize.Y)); debris.TexBiasY = (((float)(_renderer.CurrentFrame / res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.Y) + ((float)fy / float(texSize.Y))); debris.DiffuseTexture = texture; debris.Flags = Tiles::TileMap::DebrisFlags::Disappear; tilemap->CreateDebris(debris); } } return; } if (effect == ParticleDebrisEffect::Dissolve) { constexpr int DebrisSize = 2; Vector2i texSize = texture->GetSize(); float x = _pos.X - res->Base->Hotspot.X; float y = _pos.Y - res->Base->Hotspot.Y; for (std::int32_t fy = 0; fy < res->Base->FrameDimensions.Y; fy += DebrisSize + 1) { for (std::int32_t fx = 0; fx < res->Base->FrameDimensions.X; fx += DebrisSize + 1) { float currentSize = DebrisSize * Random().FastFloat(0.4f, 1.1f); Tiles::TileMap::DestructibleDebris debris = { }; debris.Pos = Vector2f(x + (IsFacingLeft() ? res->Base->FrameDimensions.X - fx : fx), y + fy); debris.Depth = _renderer.layer(); debris.Size = Vector2f(currentSize, currentSize); debris.Speed = Vector2f(((fx - res->Base->FrameDimensions.X / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(2.0f, 5.0f) / res->Base->FrameDimensions.X, ((fy - res->Base->FrameDimensions.Y / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(2.0f, 5.0f) / res->Base->FrameDimensions.Y); debris.Acceleration = Vector2f::Zero; debris.Scale = 1.2f; debris.ScaleSpeed = -0.004f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.01f; debris.Time = 280.0f; debris.TexScaleX = (currentSize / float(texSize.X)); debris.TexBiasX = (((float)(_renderer.CurrentFrame % res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.X) + ((float)fx / float(texSize.X))); debris.TexScaleY = (currentSize / float(texSize.Y)); debris.TexBiasY = (((float)(_renderer.CurrentFrame / res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.Y) + ((float)fy / float(texSize.Y))); debris.DiffuseTexture = texture; debris.Flags = Tiles::TileMap::DebrisFlags::Disappear; tilemap->CreateDebris(debris); } } return; } if (effect == ParticleDebrisEffect::StandardInWater) { constexpr std::int32_t DebrisSize = 3; Vector2i texSize = texture->GetSize(); for (std::int32_t fy = 0; fy < res->Base->FrameDimensions.Y; fy += DebrisSize + 1) { for (int fx = 0; fx < res->Base->FrameDimensions.X; fx += DebrisSize + 1) { float currentSize = DebrisSize * Random().FastFloat(0.2f, 1.1f); Tiles::TileMap::DestructibleDebris debris{}; debris.Pos = Vector2f(x + (IsFacingLeft() ? res->Base->FrameDimensions.X - fx : fx), y + fy); debris.Depth = _renderer.layer(); debris.Size = Vector2f(currentSize, currentSize); debris.Speed = Vector2f(((fx - res->Base->FrameDimensions.X / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(1.0f, 3.0f) / res->Base->FrameDimensions.X, ((fy - res->Base->FrameDimensions.Y / 2) + Random().FastFloat(-2.0f, 2.0f)) * (IsFacingLeft() ? -1.0f : 1.0f) * Random().FastFloat(1.0f, 3.0f) / res->Base->FrameDimensions.Y); debris.Acceleration = Vector2f::Zero; debris.Scale = 1.0f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.004f; debris.Time = Random().FastFloat(300.0f, 340.0f);; debris.TexScaleX = (currentSize / float(texSize.X)); debris.TexBiasX = (((float)(_renderer.CurrentFrame % res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.X) + ((float)fx / float(texSize.X))); debris.TexScaleY = (currentSize / float(texSize.Y)); debris.TexBiasY = (((float)(_renderer.CurrentFrame / res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.Y) + ((float)fy / float(texSize.Y))); debris.DiffuseTexture = texture; debris.Flags = Tiles::TileMap::DebrisFlags::Disappear; tilemap->CreateDebris(debris); } } return; } if (effect == ParticleDebrisEffect::Frozen) { for (std::int32_t i = 0; i < 20; i++) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 10), Explosion::Type::IceShrapnel); } _levelHandler->PlayCommonSfx("IceBreak"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); return; } float length = speed.Length(); Vector2f force = (length > 0.0f ? (speed / length * 1.4f) : Vector2f::Zero); tilemap->CreateParticleDebris(res, Vector3f(_pos.X, _pos.Y, (float)_renderer.layer()), force, _renderer.CurrentFrame, IsFacingLeft()); } void ActorBase::CreateSpriteDebris(AnimState state, std::int32_t count) { _levelHandler->HandleCreateSpriteDebris(this, state, count); auto* tilemap = _levelHandler->TileMap(); if (tilemap != nullptr && _metadata != nullptr) { auto* res = _metadata->FindAnimation(state); if (res != nullptr) { tilemap->CreateSpriteDebris(res, Vector3f(_pos.X, _pos.Y, (float)_renderer.layer()), count); } } } float ActorBase::GetIceShrapnelScale() const { return 1.0f; } std::shared_ptr ActorBase::PlaySfx(StringView identifier, float gain, float pitch) { auto it = _metadata->Sounds.find(String::nullTerminatedView(identifier)); if (it != _metadata->Sounds.end()) { AudioBuffer* buffer; if (!it->second.Buffers.empty()) { std::int32_t idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (std::int32_t)it->second.Buffers.size()) : 0); buffer = &it->second.Buffers[idx]->Buffer; } else { buffer = nullptr; } return _levelHandler->PlaySfx(this, identifier, buffer, Vector3f(_pos.X, _pos.Y, 0.0f), false, gain, pitch); } return nullptr; } bool ActorBase::SetAnimation(AnimState state, bool skipAnimation) { if (_metadata == nullptr) { LOGE("No metadata loaded"); return false; } if (_currentAnimation != nullptr && _currentAnimation->State == state) { return true; } auto* anim = _metadata->FindAnimation(state); if (anim == nullptr) { //LOGE("No animation found for state 0x{:.8x}", state); return false; } if (_currentTransition == nullptr || _currentTransitionCancellable) { if (_currentTransition != nullptr) { _currentTransition = nullptr; if (_currentTransitionCallback) { auto oldCallback = std::move(_currentTransitionCallback); _currentTransitionCallback = nullptr; oldCallback(); } } _currentAnimation = anim; RefreshAnimation(skipAnimation); } else { // It will be set after active transition _currentAnimation = anim; } return true; } bool ActorBase::SetTransition(AnimState state, bool cancellable, Function&& callback) { auto* anim = _metadata->FindAnimation(state); if (anim == nullptr) { if (callback) { callback(); } return false; } if (_currentTransitionCallback) { auto oldCallback = std::move(_currentTransitionCallback); _currentTransitionCallback = nullptr; oldCallback(); } _currentTransition = anim; _currentTransitionCancellable = cancellable; _currentTransitionCallback = std::move(callback); RefreshAnimation(); return true; } void ActorBase::CancelTransition() { if (_currentTransition != nullptr && _currentTransitionCancellable) { if (_currentTransitionCallback) { auto oldCallback = std::move(_currentTransitionCallback); _currentTransitionCallback = nullptr; oldCallback(); } _currentTransition = nullptr; RefreshAnimation(); } } void ActorBase::ForceCancelTransition() { if (_currentTransition != nullptr) { _currentTransition = nullptr; _currentTransitionCancellable = true; _currentTransitionCallback = nullptr; RefreshAnimation(); } } void ActorBase::OnAnimationStarted() { // Can be overriden } void ActorBase::OnAnimationFinished() { if (_currentTransition != nullptr) { _currentTransition = nullptr; RefreshAnimation(); if (_currentTransitionCallback) { auto oldCallback = std::move(_currentTransitionCallback); _currentTransitionCallback = nullptr; oldCallback(); } } } void ActorBase::OnPacketReceived(MemoryStream& packet) { LOGD("Packet wasn't handled by derived class"); } void ActorBase::SendPacket(ArrayView data) { _levelHandler->SendPacket(this, data); } bool ActorBase::IsCollidingWith(ActorBase* other) { bool perPixel1 = (_state & ActorState::SkipPerPixelCollisions) != ActorState::SkipPerPixelCollisions; bool perPixel2 = (other->_state & ActorState::SkipPerPixelCollisions) != ActorState::SkipPerPixelCollisions; if ((perPixel1 && std::abs(_renderer.rotation()) > 0.1f) || (perPixel2 && std::abs(other->_renderer.rotation()) > 0.1f)) { if (!perPixel1 && std::abs(other->_renderer.rotation()) > 0.1f) { return other->IsCollidingWithAngled(AABBInner); } else if (!perPixel2 && std::abs(_renderer.rotation()) > 0.1f) { return IsCollidingWithAngled(other->AABBInner); } return IsCollidingWithAngled(other); } GraphicResource* res1 = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); GraphicResource* res2 = (other->_currentTransition != nullptr ? other->_currentTransition : other->_currentAnimation); if (res1 == nullptr || res2 == nullptr) { if (res1 != nullptr) { return IsCollidingWith(other->AABBInner); } if (res2 != nullptr) { return other->IsCollidingWith(AABBInner); } return false; } Vector2i& hotspot1 = res1->Base->Hotspot; Vector2i& hotspot2 = res2->Base->Hotspot; Vector2i& size1 = res1->Base->FrameDimensions; Vector2i& size2 = res2->Base->FrameDimensions; AABBf aabb1, aabb2; if (!perPixel1) { aabb1 = AABBInner; } else if (GetState(ActorState::IsFacingLeft)) { aabb1 = AABBf(_pos.X + hotspot1.X - size1.X, _pos.Y - hotspot1.Y, (float)size1.X, (float)size1.Y); aabb1.B += aabb1.T; aabb1.R += aabb1.L; } else { aabb1 = AABBf(_pos.X - hotspot1.X, _pos.Y - hotspot1.Y, (float)size1.X, (float)size1.Y); aabb1.B += aabb1.T; aabb1.R += aabb1.L; } if (!perPixel2) { aabb2 = other->AABBInner; } else if (other->GetState(ActorState::IsFacingLeft)) { aabb2 = AABBf(other->_pos.X + hotspot2.X - size2.X, other->_pos.Y - hotspot2.Y, (float)size2.X, (float)size2.Y); aabb2.B += aabb2.T; aabb2.R += aabb2.L; } else { aabb2 = AABBf(other->_pos.X - hotspot2.X, other->_pos.Y - hotspot2.Y, (float)size2.X, (float)size2.Y); aabb2.B += aabb2.T; aabb2.R += aabb2.L; } // Bounding Box intersection AABBf inter = AABBf::Intersect(aabb1, aabb2); if (inter.R <= 0 || inter.B <= 0) { return false; } if (!perPixel1 || !perPixel2) { if (perPixel1 == perPixel2) { return true; } //PixelData p; uint8_t* p; GraphicResource* res; bool isFacingLeftCurrent; std::int32_t x1, y1, x2, y2, xs, dx, dy, stride; if (perPixel1) { res = res1; p = res->Base->Mask.get(); isFacingLeftCurrent = GetState(ActorState::IsFacingLeft); x1 = (std::int32_t)std::max(inter.L, other->AABBInner.L); y1 = (std::int32_t)std::max(inter.T, other->AABBInner.T); x2 = (std::int32_t)std::min(inter.R, other->AABBInner.R); y2 = (std::int32_t)std::min(inter.B, other->AABBInner.B); xs = (std::int32_t)aabb1.L; std::int32_t frame1 = std::min(_renderer.CurrentFrame, res->FrameCount - 1); dx = (frame1 % res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.X; dy = (frame1 / res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.Y - (std::int32_t)aabb1.T; } else { res = res2; p = res->Base->Mask.get(); isFacingLeftCurrent = other->GetState(ActorState::IsFacingLeft); x1 = (std::int32_t)std::max(inter.L, AABBInner.L); y1 = (std::int32_t)std::max(inter.T, AABBInner.T); x2 = (std::int32_t)std::min(inter.R, AABBInner.R); y2 = (std::int32_t)std::min(inter.B, AABBInner.B); xs = (std::int32_t)aabb2.L; std::int32_t frame2 = std::min(other->_renderer.CurrentFrame, res->FrameCount - 1); dx = (frame2 % res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.X; dy = (frame2 / res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.Y - (std::int32_t)aabb2.T; } stride = res->Base->FrameConfiguration.X * res->Base->FrameDimensions.X; // Per-pixel collision check for (std::int32_t i = x1; i < x2; i += PerPixelCollisionStep) { for (std::int32_t j = y1; j < y2; j += PerPixelCollisionStep) { std::int32_t i1 = i - xs; if (isFacingLeftCurrent) { i1 = res->Base->FrameDimensions.X - i1 - 1; } if (p[((j + dy) * stride) + i1 + dx] > AlphaThreshold) { return true; } } } } else { std::int32_t x1 = (std::int32_t)inter.L; std::int32_t y1 = (std::int32_t)inter.T; std::int32_t x2 = (std::int32_t)inter.R; std::int32_t y2 = (std::int32_t)inter.B; std::int32_t x1s = (std::int32_t)aabb1.L; std::int32_t x2s = (std::int32_t)aabb2.L; std::int32_t frame1 = std::min(_renderer.CurrentFrame, res1->FrameCount - 1); std::int32_t dx1 = (frame1 % res1->Base->FrameConfiguration.X) * res1->Base->FrameDimensions.X; std::int32_t dy1 = (frame1 / res1->Base->FrameConfiguration.X) * res1->Base->FrameDimensions.Y - (std::int32_t)aabb1.T; std::int32_t stride1 = res1->Base->FrameConfiguration.X * res1->Base->FrameDimensions.X; std::int32_t frame2 = std::min(other->_renderer.CurrentFrame, res2->FrameCount - 1); std::int32_t dx2 = (frame2 % res2->Base->FrameConfiguration.X) * res2->Base->FrameDimensions.X; std::int32_t dy2 = (frame2 / res2->Base->FrameConfiguration.X) * res2->Base->FrameDimensions.Y - (std::int32_t)aabb2.T; std::int32_t stride2 = res2->Base->FrameConfiguration.X * res2->Base->FrameDimensions.X; // Per-pixel collision check auto p1 = res1->Base->Mask.get(); auto p2 = res2->Base->Mask.get(); for (std::int32_t i = x1; i < x2; i += PerPixelCollisionStep) { for (std::int32_t j = y1; j < y2; j += PerPixelCollisionStep) { std::int32_t i1 = i - x1s; if (GetState(ActorState::IsFacingLeft)) { i1 = res1->Base->FrameDimensions.X - i1 - 1; } std::int32_t i2 = i - x2s; if (other->GetState(ActorState::IsFacingLeft)) { i2 = res2->Base->FrameDimensions.X - i2 - 1; } if (p1[((j + dy1) * stride1) + i1 + dx1] > AlphaThreshold && p2[((j + dy2) * stride2) + i2 + dx2] > AlphaThreshold) { return true; } } } } return false; } bool ActorBase::IsCollidingWith(const AABBf& aabb) { bool perPixel = (_state & ActorState::SkipPerPixelCollisions) != ActorState::SkipPerPixelCollisions; if (!perPixel) { return aabb.Overlaps(AABBInner); } else if (std::abs(_renderer.rotation()) > 0.1f) { return IsCollidingWithAngled(aabb); } GraphicResource* res = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); if (res == nullptr) { return false; } Vector2i& hotspot = res->Base->Hotspot; Vector2i& size = res->Base->FrameDimensions; AABBf aabbSelf; if (GetState(ActorState::IsFacingLeft)) { aabbSelf = AABBf(_pos.X + hotspot.X - size.X, _pos.Y - hotspot.Y, (float)size.X, (float)size.Y); aabbSelf.B += aabbSelf.T; aabbSelf.R += aabbSelf.L; } else { aabbSelf = AABBf(_pos.X - hotspot.X, _pos.Y - hotspot.Y, (float)size.X, (float)size.Y); aabbSelf.B += aabbSelf.T; aabbSelf.R += aabbSelf.L; } // Bounding Box intersection AABBf inter = AABBf::Intersect(aabb, aabbSelf); if (inter.R <= 0 || inter.B <= 0) { return false; } std::int32_t x1 = (std::int32_t)std::max(inter.L, aabb.L); std::int32_t y1 = (std::int32_t)std::max(inter.T, aabb.T); std::int32_t x2 = (std::int32_t)std::min(inter.R, aabb.R); std::int32_t y2 = (std::int32_t)std::min(inter.B, aabb.B); std::int32_t xs = (std::int32_t)aabbSelf.L; std::int32_t frame1 = std::min(_renderer.CurrentFrame, res->FrameCount - 1); std::int32_t dx = (frame1 % res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.X; std::int32_t dy = (frame1 / res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.Y - (int)aabbSelf.T; std::int32_t stride = res->Base->FrameConfiguration.X * res->Base->FrameDimensions.X; // Per-pixel collision check auto p = res->Base->Mask.get(); for (std::int32_t i = x1; i < x2; i += PerPixelCollisionStep) { for (std::int32_t j = y1; j < y2; j += PerPixelCollisionStep) { std::int32_t i1 = i - xs; if (GetState(ActorState::IsFacingLeft)) { i1 = res->Base->FrameDimensions.X - i1 - 1; } if (p[((j + dy) * stride) + i1 + dx] > AlphaThreshold) { return true; } } } return false; } bool ActorBase::IsCollidingWithAngled(ActorBase* other) { GraphicResource* res1 = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); GraphicResource* res2 = (other->_currentTransition != nullptr ? other->_currentTransition : other->_currentAnimation); if (res1 == nullptr || res2 == nullptr) { return false; } Matrix4x4f transform1 = Matrix4x4f::Translation((float)-res1->Base->Hotspot.X, (float)-res1->Base->Hotspot.Y, 0.0f); if (GetState(ActorState::IsFacingLeft)) { transform1 = Matrix4x4f::Scaling(-1.0f, 1.0f, 1.0f) * transform1; } transform1 = Matrix4x4f::Translation(_pos.X, _pos.Y, 0.0f) * Matrix4x4f::RotationZ(_renderer.rotation()) * transform1; Matrix4x4f transform2 = Matrix4x4f::Translation((float)-res2->Base->Hotspot.X, (float)-res2->Base->Hotspot.Y, 0.0f); if (other->GetState(ActorState::IsFacingLeft)) { transform2 = Matrix4x4f::Scaling(-1.0f, 1.0f, 1.0f) * transform2; } transform2 = Matrix4x4f::Translation(other->_pos.X, other->_pos.Y, 0.0f) * Matrix4x4f::RotationZ(other->_renderer.rotation()) * transform2; std::int32_t width1 = res1->Base->FrameDimensions.X; std::int32_t height1 = res1->Base->FrameDimensions.Y; std::int32_t width2 = res2->Base->FrameDimensions.X; std::int32_t height2 = res2->Base->FrameDimensions.Y; // Bounding Box intersection AABBf aabb1, aabb2; { Vector3f tl = Vector3f::Zero * transform1; Vector3f tr = Vector3f((float)width1, 0.0f, 0.0f) * transform1; Vector3f bl = Vector3f(0.0f, (float)height1, 0.0f) * transform1; Vector3f br = Vector3f((float)width1, (float)height1, 0.0f) * transform1; float minX = std::min(std::min(tl.X, tr.X), std::min(bl.X, br.X)); float minY = std::min(std::min(tl.Y, tr.Y), std::min(bl.Y, br.Y)); float maxX = std::max(std::max(tl.X, tr.X), std::max(bl.X, br.X)); float maxY = std::max(std::max(tl.Y, tr.Y), std::max(bl.Y, br.Y)); aabb1 = AABBf(std::floor(minX), std::floor(minY), std::ceil(maxX), std::ceil(maxY)); } { Vector3f tl = Vector3f::Zero * transform2; Vector3f tr = Vector3f((float)width2, 0.0f, 0.0f) * transform2; Vector3f bl = Vector3f(0.0f, (float)height2, 0.0f) * transform2; Vector3f br = Vector3f((float)width2, (float)height2, 0.0f) * transform2; float minX = std::min(std::min(tl.X, tr.X), std::min(bl.X, br.X)); float minY = std::min(std::min(tl.Y, tr.Y), std::min(bl.Y, br.Y)); float maxX = std::max(std::max(tl.X, tr.X), std::max(bl.X, br.X)); float maxY = std::max(std::max(tl.Y, tr.Y), std::max(bl.Y, br.Y)); aabb2 = AABBf(std::floor(minX), std::floor(minY), std::ceil(maxX), std::ceil(maxY)); } if (!aabb1.Overlaps(aabb2)) { return false; } // Per-pixel collision check Matrix4x4f transformAToB = transform2.Inverse() * transform1; // TransformNormal with [1, 0] and [0, 1] vectors Vector3f stepX = Vector3f(transformAToB[0][0], transformAToB[0][1], 0.0f) * PerPixelCollisionStep; Vector3f stepY = Vector3f(transformAToB[1][0], transformAToB[1][1], 0.0f) * PerPixelCollisionStep; Vector3f yPosIn2 = Vector3f::Zero * transformAToB; std::int32_t frame1 = std::min(_renderer.CurrentFrame, res1->FrameCount - 1); std::int32_t dx1 = (frame1 % res1->Base->FrameConfiguration.X) * res1->Base->FrameDimensions.X; std::int32_t dy1 = (frame1 / res1->Base->FrameConfiguration.X) * res1->Base->FrameDimensions.Y; std::int32_t stride1 = res1->Base->FrameConfiguration.X * res1->Base->FrameDimensions.X; std::int32_t frame2 = std::min(other->_renderer.CurrentFrame, res2->FrameCount - 1); std::int32_t dx2 = (frame2 % res2->Base->FrameConfiguration.X) * res2->Base->FrameDimensions.X; std::int32_t dy2 = (frame2 / res2->Base->FrameConfiguration.X) * res2->Base->FrameDimensions.Y; std::int32_t stride2 = res2->Base->FrameConfiguration.X * res2->Base->FrameDimensions.X; auto p1 = res1->Base->Mask.get(); auto p2 = res2->Base->Mask.get(); for (std::int32_t y1 = 0; y1 < height1; y1 += PerPixelCollisionStep) { Vector3f posIn2 = yPosIn2; for (std::int32_t x1 = 0; x1 < width1; x1 += PerPixelCollisionStep) { std::int32_t x2 = (std::int32_t)std::round(posIn2.X); std::int32_t y2 = (std::int32_t)std::round(posIn2.Y); if (x2 >= 0 && x2 < width2 && y2 >= 0 && y2 < height2) { if (p1[((y1 + dy1) * stride1) + x1 + dx1] > AlphaThreshold && p2[((y2 + dy2) * stride2) + x2 + dx2] > AlphaThreshold) { return true; } } posIn2 += stepX; } yPosIn2 += stepY; } return false; } bool ActorBase::IsCollidingWithAngled(const AABBf& aabb) { GraphicResource* res = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); if (res == nullptr) { return false; } Matrix4x4f transform = Matrix4x4f::Translation((float)-res->Base->Hotspot.X, (float)-res->Base->Hotspot.Y, 0.0f); if (GetState(ActorState::IsFacingLeft)) { transform = Matrix4x4f::Scaling(-1.0f, 1.0f, 1.0f) * transform; } transform = Matrix4x4f::Translation(_pos.X, _pos.Y, 0.0f) * Matrix4x4f::RotationZ(_renderer.rotation()) * transform; std::int32_t width = res->Base->FrameDimensions.X; std::int32_t height = res->Base->FrameDimensions.Y; // Bounding Box intersection AABBf aabbSelf; { Vector3f tl = Vector3f::Zero * transform; Vector3f tr = Vector3f((float)width, 0.0f, 0.0f) * transform; Vector3f bl = Vector3f(0.0f, (float)height, 0.0f) * transform; Vector3f br = Vector3f((float)width, (float)height, 0.0f) * transform; float minX = std::min(std::min(tl.X, tr.X), std::min(bl.X, br.X)); float minY = std::min(std::min(tl.Y, tr.Y), std::min(bl.Y, br.Y)); float maxX = std::max(std::max(tl.X, tr.X), std::max(bl.X, br.X)); float maxY = std::max(std::max(tl.Y, tr.Y), std::max(bl.Y, br.Y)); aabbSelf = AABBf(std::floor(minX), std::floor(minY), std::ceil(maxX), std::ceil(maxY)); } if (!aabb.Overlaps(aabbSelf)) { return false; } // TransformNormal with [1, 0] and [0, 1] vectors Vector3f stepX = Vector3f(transform[0][0], transform[0][1], 0.0f) * PerPixelCollisionStep; Vector3f stepY = Vector3f(transform[1][0], transform[1][1], 0.0f) * PerPixelCollisionStep; Vector3f yPosInAABB = Vector3f::Zero * transform; std::int32_t frame = std::min(_renderer.CurrentFrame, res->FrameCount - 1); std::int32_t dx = (frame % res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.X; std::int32_t dy = (frame / res->Base->FrameConfiguration.X) * res->Base->FrameDimensions.Y; std::int32_t stride = res->Base->FrameConfiguration.X * res->Base->FrameDimensions.X; auto p = res->Base->Mask.get(); for (std::int32_t y1 = 0; y1 < height; y1 += PerPixelCollisionStep) { Vector3f posInAABB = yPosInAABB; for (std::int32_t x1 = 0; x1 < width; x1 += PerPixelCollisionStep) { std::int32_t x2 = (std::int32_t)std::round(posInAABB.X); std::int32_t y2 = (std::int32_t)std::round(posInAABB.Y); if (p[((y1 + dy) * stride) + x1 + dx] > AlphaThreshold && x2 >= aabb.L && x2 < aabb.R && y2 >= aabb.T && y2 < aabb.B) { return true; } posInAABB += stepX; } yPosInAABB += stepY; } return false; } void ActorBase::UpdateAABB() { if ((_state & (ActorState::CollideWithOtherActors | ActorState::CollideWithSolidObjects | ActorState::IsSolidObject)) == ActorState::None) { // Collisions are deactivated return; } if ((_state & ActorState::SkipPerPixelCollisions) != ActorState::SkipPerPixelCollisions) { GraphicResource* res = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); if (res == nullptr) { return; } Vector2i hotspot = res->Base->Hotspot; Vector2i size = res->Base->FrameDimensions; if (std::abs(_renderer.rotation()) > 0.1f) { Matrix4x4f transform = Matrix4x4f::Translation((float)-hotspot.X, (float)-hotspot.Y, 0.0f); if (GetState(ActorState::IsFacingLeft)) { transform = Matrix4x4f::Scaling(-1.0f, 1.0f, 1.0f) * transform; } transform = Matrix4x4f::Translation(_pos.X, _pos.Y, 0.0f) * Matrix4x4f::RotationZ(_renderer.rotation()) * transform; Vector3f tl = Vector3f::Zero * transform; Vector3f tr = Vector3f((float)size.X, 0.0f, 0.0f) * transform; Vector3f bl = Vector3f(0.0f, (float)size.Y, 0.0f) * transform; Vector3f br = Vector3f((float)size.X, (float)size.Y, 0.0f) * transform; float minX = std::min(std::min(tl.X, tr.X), std::min(bl.X, br.X)); float minY = std::min(std::min(tl.Y, tr.Y), std::min(bl.Y, br.Y)); float maxX = std::max(std::max(tl.X, tr.X), std::max(bl.X, br.X)); float maxY = std::max(std::max(tl.Y, tr.Y), std::max(bl.Y, br.Y)); AABB.L = minX; AABB.T = minY; AABB.R = maxX; AABB.B = maxY; } else { AABB.L = (IsFacingLeft() ? (_pos.X + hotspot.X - size.X) : (_pos.X - hotspot.X)); AABB.T = _pos.Y - hotspot.Y; AABB.R = AABB.L + size.X; AABB.B = AABB.T + size.Y; } } else { OnUpdateHitbox(); AABB = AABBInner; } } void ActorBase::RefreshAnimation(bool skipAnimation) { GraphicResource* res = (_currentTransition != nullptr ? _currentTransition : _currentAnimation); if (res == nullptr) { return; } _renderer.FrameConfiguration = res->Base->FrameConfiguration; _renderer.FrameDimensions = res->Base->FrameDimensions; if (res->AnimDuration < 0.0f) { if (res->FrameCount > 1) { _renderer.FirstFrame = res->FrameOffset + nCine::Random().Next(0, res->FrameCount); } else { _renderer.FirstFrame = res->FrameOffset; } _renderer.LoopMode = AnimationLoopMode::FixedSingle; } else { _renderer.FirstFrame = res->FrameOffset; _renderer.LoopMode = res->LoopMode; } _renderer.FrameCount = res->FrameCount; _renderer.AnimDuration = res->AnimDuration; _renderer.AnimTime = (skipAnimation && res->AnimDuration >= 0.0f && _renderer.LoopMode != AnimationLoopMode::FixedSingle ? _renderer.AnimDuration : 0.0f); _renderer.Hotspot.X = static_cast(IsFacingLeft() ? (res->Base->FrameDimensions.X - res->Base->Hotspot.X) : res->Base->Hotspot.X); _renderer.Hotspot.Y = static_cast(res->Base->Hotspot.Y); _renderer.setTexture(res->Base->TextureDiffuse.get()); _renderer.UpdateVisibleFrames(); OnAnimationStarted(); if ((_state & ActorState::ForceDisableCollisions) != ActorState::ForceDisableCollisions) { _state |= ActorState::IsDirty; } } void ActorBase::PreloadMetadataAsync(StringView path) { ContentResolver::Get().PreloadMetadataAsync(path); } void ActorBase::RequestMetadata(StringView path) { _metadata = ContentResolver::Get().RequestMetadata(path); } #if !defined(WITH_COROUTINES) void ActorBase::RequestMetadataAsync(StringView path) { _metadata = ContentResolver::Get().RequestMetadata(path); } #endif void ActorBase::UpdateFrozenState(float timeMult) { if (_frozenTimeLeft > 0.0f) { _frozenTimeLeft -= timeMult; if (_frozenTimeLeft > 0.0f) { // Cannot be directly in `ActorBase::HandleFrozenStateChange()` due to bug in `BaseSprite::updateRenderCommand()`, // it would be called before `BaseSprite::updateRenderCommand()` but after `SceneNode::transform()` _renderer.Initialize(ActorRendererType::FrozenMask); } else { _renderer.AnimPaused = false; _renderer.Initialize(ActorRendererType::Default); float scale = GetIceShrapnelScale(); for (std::int32_t i = 0; i < 10; i++) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 10), Explosion::Type::IceShrapnel, scale); } Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 90), Explosion::Type::SmokeWhite, scale); _levelHandler->PlayCommonSfx("IceBreak"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); } } } void ActorBase::HandleFrozenStateChange(ActorBase* shot) { if (auto* freezerShot = runtime_cast(shot)) { if (runtime_cast(freezerShot->GetOwner()) != this) { _frozenTimeLeft = freezerShot->FrozenDuration(); _renderer.AnimPaused = true; freezerShot->DecreaseHealth(INT32_MAX); } } else if (runtime_cast(shot) || runtime_cast(shot) || runtime_cast(shot)) { _frozenTimeLeft = std::min(1.0f, _frozenTimeLeft); } } bool ActorBase::IsInvulnerable() { return GetState(ActorState::IsInvulnerable); } std::int32_t ActorBase::GetHealth() { return _health; } void ActorBase::SetHealth(std::int32_t value) { _health = value; } std::int32_t ActorBase::GetMaxHealth() { return _maxHealth; } void ActorBase::DecreaseHealth(std::int32_t amount, ActorBase* collider) { if (amount == 0) { return; } if (amount > _health) { _health = 0; } else { _health -= amount; } if (_health <= 0) { OnPerish(collider); } else { OnHealthChanged(collider); } } bool ActorBase::MoveInstantly(Vector2f pos, MoveType type, TileCollisionParams& params) { Vector2f newPos; AABBf aabb; if ((type & MoveType::Relative) == MoveType::Relative) { if (pos == Vector2f::Zero) { return true; } newPos = _pos + pos; aabb = AABBInner + pos; } else { newPos = pos; aabb = AABBInner + (pos - _pos); } bool free = ((type & MoveType::Force) == MoveType::Force || _levelHandler->IsPositionEmpty(this, aabb, params)); if (free) { AABBInner = aabb; _pos = newPos; if ((_state & ActorState::ForceDisableCollisions) != ActorState::ForceDisableCollisions) { _state |= ActorState::IsDirty; } } return free; } void ActorBase::AddExternalForce(float x, float y) { _externalForce.X += x; _externalForce.Y += y; } ActorBase::ActorRenderer::ActorRenderer(ActorBase* owner) : BaseSprite(nullptr, nullptr, 0.0f, 0.0f), AnimPaused(false), LoopMode(AnimationLoopMode::Loop), FirstFrame(0), FrameCount(0), AnimDuration(0.0f), AnimTime(0.0f), CurrentFrame(0), _owner(owner), _rendererType((ActorRendererType)-1), _rendererTransition(0.0f) { _type = ObjectType::Sprite; renderCommand_.SetType(RenderCommand::Type::Sprite); Initialize(ActorRendererType::Default); } void ActorBase::ActorRenderer::Initialize(ActorRendererType type) { if (_rendererType == type) { return; } _rendererType = type; auto& resolver = ContentResolver::Get(); if (resolver.IsHeadless()) { return; } bool shaderChanged; switch (type) { case ActorRendererType::Outline: shaderChanged = renderCommand_.GetMaterial().SetShader(resolver.GetShader(PrecompiledShader::Outline)); break; case ActorRendererType::WhiteMask: shaderChanged = renderCommand_.GetMaterial().SetShader(resolver.GetShader(PrecompiledShader::WhiteMask)); break; case ActorRendererType::PartialWhiteMask: shaderChanged = renderCommand_.GetMaterial().SetShader(resolver.GetShader(PrecompiledShader::PartialWhiteMask)); break; case ActorRendererType::FrozenMask: shaderChanged = renderCommand_.GetMaterial().SetShader(resolver.GetShader(PrecompiledShader::FrozenMask)); break; default: shaderChanged = renderCommand_.GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); break; } if (shaderChanged) { shaderHasChanged(); renderCommand_.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); if (type == ActorRendererType::Outline || type == ActorRendererType::FrozenMask) { _rendererTransition = 0.0f; if (texture_ != nullptr) { Vector2i texSize = texture_->GetSize(); setColor(Colorf(1.0f / texSize.X, 1.0f / texSize.Y, 1.0f, _rendererTransition)); } } else { setColor(Colorf::White); } } } void ActorBase::ActorRenderer::OnUpdate(float timeMult) { _owner->OnUpdate(timeMult); Vector2f pos = _owner->_pos; if (!PreferencesCache::UnalignedViewport || (_owner->_state & ActorState::IsDirty) != ActorState::IsDirty) { pos.X = std::floor(pos.X); pos.Y = std::floor(pos.Y); } setPosition(pos.X, pos.Y); if (IsAnimationRunning()) { switch (LoopMode) { case AnimationLoopMode::Loop: AnimTime += timeMult * FrameTimer::SecondsPerFrame; if (AnimTime > AnimDuration) { std::int32_t n = (std::int32_t)(AnimTime / AnimDuration); AnimTime -= AnimDuration * n; _owner->OnAnimationFinished(); } break; case AnimationLoopMode::Once: float newAnimTime = AnimTime + timeMult * FrameTimer::SecondsPerFrame; if (AnimTime > AnimDuration) { _owner->OnAnimationFinished(); } AnimTime = newAnimTime; break; } UpdateVisibleFrames(); } switch (_rendererType) { case ActorRendererType::Outline: if (_rendererTransition < 0.8f) { _rendererTransition = std::min(_rendererTransition + timeMult * 0.06f, 0.8f); if (texture_ != nullptr) { Vector2i texSize = texture_->GetSize(); setColor(Colorf(1.0f / texSize.X, 1.0f / texSize.Y, 1.0f, _rendererTransition)); } } break; case ActorRendererType::FrozenMask: if (_rendererTransition < 1.0f) { _rendererTransition = std::min(_rendererTransition + timeMult * 0.14f, 1.0f); if (texture_ != nullptr) { Vector2i texSize = texture_->GetSize(); setColor(Colorf(1.0f / texSize.X, 1.0f / texSize.Y, 1.0f, _rendererTransition)); } } break; } BaseSprite::OnUpdate(timeMult); } bool ActorBase::ActorRenderer::OnDraw(RenderQueue& renderQueue) { if (_owner->OnDraw(renderQueue)) { return true; } return BaseSprite::OnDraw(renderQueue); } void ActorBase::ActorRenderer::textureHasChanged(Texture* newTexture) { if (_rendererType == ActorRendererType::Outline || _rendererType == ActorRendererType::FrozenMask) { if (newTexture != nullptr) { Vector2i texSize = newTexture->GetSize(); setColor(Colorf(1.0f / texSize.X, 1.0f / texSize.Y, 1.0f, _rendererTransition)); } } } bool ActorBase::ActorRenderer::IsAnimationRunning() { if (FrameCount <= 0) { return false; } switch (LoopMode) { case AnimationLoopMode::FixedSingle: return false; case AnimationLoopMode::Loop: return !AnimPaused; case AnimationLoopMode::Once: return !AnimPaused && AnimTime < AnimDuration; default: return false; } } ActorRendererType ActorBase::ActorRenderer::GetRendererType() const { return _rendererType; } void ActorBase::ActorRenderer::UpdateVisibleFrames() { // Calculate visible frames CurrentFrame = 0; if (FrameCount > 0 && AnimDuration > 0.0f) { // Calculate currently visible frame float frameTemp = (FrameCount * AnimTime) / AnimDuration; CurrentFrame = (std::int32_t)frameTemp; // Normalize current frame when exceeding anim duration if (LoopMode == AnimationLoopMode::Once || LoopMode == AnimationLoopMode::FixedSingle) { CurrentFrame = std::clamp(CurrentFrame, 0, FrameCount - 1); } else { CurrentFrame = NormalizeFrame(CurrentFrame, 0, FrameCount); } } CurrentFrame = FirstFrame + std::clamp(CurrentFrame, 0, FrameCount - 1); // Set current animation frame rectangle std::int32_t col = CurrentFrame % FrameConfiguration.X; std::int32_t row = CurrentFrame / FrameConfiguration.X; setTexRect(Recti(FrameDimensions.X * col, FrameDimensions.Y * row, FrameDimensions.X, FrameDimensions.Y)); setAbsAnchorPoint(Hotspot.X, Hotspot.Y); } int ActorBase::ActorRenderer::NormalizeFrame(std::int32_t frame, std::int32_t min, std::int32_t max) { if (frame >= min && frame < max) { return frame; } else if (frame < min) { return max + ((frame - min) % max); } else { return min + (frame % (max - min)); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/ActorBase.h000066400000000000000000000370001512772601700250020ustar00rootroot00000000000000#pragma once #include "../EventType.h" #include "../LightEmitter.h" #include "../Resources.h" #include "../Tiles/TileCollisionParams.h" #include "../../nCine/Base/Task.h" #include "../../nCine/Primitives/AABB.h" #include "../../nCine/Audio/AudioBufferPlayer.h" #include "../../nCine/Graphics/BaseSprite.h" #include "../../nCine/Graphics/SceneNode.h" #include #include #include #include #ifndef DOXYGEN_GENERATING_OUTPUT // If coroutines are not supported, load resources synchronously #if defined(WITH_COROUTINES) # define async_return co_return # define async_await co_await #else # define async_return return # define async_await #endif #endif using namespace Death::Containers::Literals; using namespace Jazz2::Resources; namespace Jazz2 { class ILevelHandler; class LevelHandler; } namespace Jazz2::Rendering { class LightingRenderer; } #if defined(WITH_MULTIPLAYER) namespace Jazz2::Multiplayer { class MpLevelHandler; } namespace Jazz2::UI::Multiplayer { class MpInGameCanvasLayer; } #endif namespace Jazz2::Actors { class Player; /** @brief Flags that modify behaviour of @ref ActorBase, supports a bitwise combination of its member values */ enum class ActorState { None = 0x00, /** @brief Actor is created from event map, this flag is used automatically by event system */ IsCreatedFromEventMap = 0x01, /** @brief Actor is created by generator, this flag is used automatically by event system */ IsFromGenerator = 0x02, /** @brief Actor should be illuminated */ Illuminated = 0x04, /** @brief Actor should be created asynchronously, not to block main thread */ Async = 0x08, /** @brief Mask of all instantiation flags that can be used in @ref ActorActivationDetails */ InstantiationFlags = IsCreatedFromEventMap | IsFromGenerator | Illuminated | Async, /** @brief This flag is set automatically after call to @ref ActorBase::OnActivatedAsync() */ Initialized = 0x0100, // Actor instance flags /** @brief Actor is invulnerable */ IsInvulnerable = 0x0200, /** @brief Actor is standing on the ground and can jump */ CanJump = 0x0400, /** @brief Actor can be frozen */ CanBeFrozen = 0x0800, /** @brief Actor is facing left */ IsFacingLeft = 0x1000, //Reserved = 0x2000, /** @brief Actor should be preserved when state is rolled back to checkpoint */ PreserveOnRollback = 0x4000, // Collision flags /** @brief Collide with tiles */ CollideWithTileset = 0x10000, /** @brief Collide with other non-solid actors, @ref ActorBase::OnHandleCollision() will be called for each collision */ CollideWithOtherActors = 0x20000, /** @brief Collide with solid objects */ CollideWithSolidObjects = 0x40000, /** @brief Don't add object to collision tree at all (cannot be changed during object lifetime) */ ForceDisableCollisions = 0x80000, /** @brief Check collisions at the end of current frame, should be used if position or size changed */ IsDirty = 0x100000, /** @brief Remove object at the end of current frame */ IsDestroyed = 0x200000, /** @brief Apply gravitation */ ApplyGravitation = 0x400000, /** @brief Marks object as solid, so other objects with @ref CollideWithSolidObjects will collide with it */ IsSolidObject = 0x800000, /** @brief Check collisions only with hitbox */ SkipPerPixelCollisions = 0x1000000, /** @brief Don't use full hitbox for collisions with tiles, also exclude upper part of hitbox when falling down */ CollideWithTilesetReduced = 0x2000000, /** @brief Collide with other solid object only if it's above center of the other hitbox */ CollideWithSolidObjectsBelow = 0x4000000, /** @brief Ignore solid collisions agains similar objects that have this flag */ ExcludeSimilar = 0x8000000, }; DEATH_ENUM_FLAGS(ActorState); /** @brief Description how to initialize an actor */ struct ActorActivationDetails { /** @brief Current level handler */ ILevelHandler* LevelHandler; /** @brief Position */ Vector3i Pos; /** @brief Actor state */ ActorState State; /** @brief Event type */ EventType Type; /** @brief Event activation parameters */ const std::uint8_t* Params; ActorActivationDetails(ILevelHandler* levelHandler, const Vector3i& pos, const std::uint8_t* params = nullptr) : LevelHandler(levelHandler), Pos(pos), State(ActorState::None), Type(EventType::Empty), Params(params) { } }; /** @brief Move type, supports a bitwise combination of its member values */ enum class MoveType { Absolute = 0x00, /**< Move to absolute position */ Relative = 0x01, /**< Move to relative position */ Force = 0x02 /**< Ignore all collision checks */ }; DEATH_ENUM_FLAGS(MoveType); /** @brief Actor renderer type */ enum class ActorRendererType { Default, /**< Default rendering */ Outline, /**< Draw outline around the sprite */ WhiteMask, /**< Draw all non-transparent pixels as white */ PartialWhiteMask, /**< Draw all non-transparent pixels as boosted shades of gray */ FrozenMask /**< Apply frozen effect to the sprite */ }; /** @brief Effect type of @ref ActorBase::CreateParticleDebrisOnPerish() */ enum class ParticleDebrisEffect { Unknown, /**< Unspecified */ Standard, /**< Standard */ StandardInWater, /**< Standard (in water) */ Dissolve, /**< Dissolve (for ghosts) */ Frozen, /**< Frozen (for breaking the ice) */ Fire, /**< Fire (for toasting) */ Lightning /**< Lightning (for electrocuting) */ }; /** @brief Base class of an object */ class ActorBase : public std::enable_shared_from_this { DEATH_RUNTIME_OBJECT(); friend class Player; friend class Jazz2::LevelHandler; friend class Jazz2::Rendering::LightingRenderer; #if defined(WITH_MULTIPLAYER) friend class Jazz2::Multiplayer::MpLevelHandler; friend class Jazz2::UI::Multiplayer::MpInGameCanvasLayer; #endif public: ActorBase(); virtual ~ActorBase(); /** @brief Outer AABB hitbox */ AABBf AABB; /** @brief Inner AABB hitbox */ AABBf AABBInner; /** @brief Returns `true` if the object is currently facing left */ bool IsFacingLeft() const; /** @brief Called after the object is created */ Task OnActivated(const ActorActivationDetails& details); /** @brief Called when the object collides with another object */ virtual bool OnHandleCollision(std::shared_ptr other); /** @brief Called to check whether @p collider can cause damage to the object */ virtual bool CanCauseDamage(ActorBase* collider); /** @brief Returns `true` if the object is invulnerable */ bool IsInvulnerable(); /** @brief Returns current health */ std::int32_t GetHealth(); /** @brief Sets current health */ void SetHealth(std::int32_t value); /** @brief Returns maximum health */ std::int32_t GetMaxHealth(); /** @brief Decreases health by specified amount */ void DecreaseHealth(std::int32_t amount = 1, ActorBase* collider = nullptr); /** @brief Moves the object */ bool MoveInstantly(Vector2f pos, MoveType type, Tiles::TileCollisionParams& params); /** @overload */ bool MoveInstantly(Vector2f pos, MoveType type) { Tiles::TileCollisionParams params = { Tiles::TileDestructType::None, _speed.Y >= 0.0f }; return MoveInstantly(pos, type, params); } /** @brief Adds external force */ void AddExternalForce(float x, float y); /** @brief Returns `true` if this object is colliding with a given object */ bool IsCollidingWith(ActorBase* other); /** @brief Returns `true` if this object is colliding with a given AABB */ bool IsCollidingWith(const AABBf& aabb); /** @brief Updates AABB for current position, rotation and animation frame */ void UpdateAABB(); /** @brief Returns current position */ Vector2f GetPos() { return _pos; } /** @brief Returns current speed */ Vector2f GetSpeed() { return _speed; } /** @brief Returns actor state */ constexpr ActorState GetState() const noexcept { return _state; } /** @overload */ constexpr bool GetState(ActorState flag) const noexcept { return (_state & flag) == flag; } protected: /** @brief Actor renderer */ class ActorRenderer : public BaseSprite { friend class ActorBase; public: ActorRenderer(ActorBase* owner); /** @brief Whether the animation is paused */ bool AnimPaused; /** @brief Frame configuration */ Vector2i FrameConfiguration; /** @brief Frame dimensions */ Vector2i FrameDimensions; /** @brief Animation loop mode */ AnimationLoopMode LoopMode; /** @brief Frame offset */ std::int32_t FirstFrame; /** @brief Frame count */ std::int32_t FrameCount; /** @brief Animation duration (in normalized frames) */ float AnimDuration; /** @brief Current animation progress */ float AnimTime; /** @brief Current animation frame */ std::int32_t CurrentFrame; /** @brief Hotspot */ Vector2f Hotspot; /** @brief Initializes the renderer to the specified renderer type */ void Initialize(ActorRendererType type); void OnUpdate(float timeMult) override; bool OnDraw(RenderQueue& renderQueue) override; /** @brief Returns `true` if animation is running */ bool IsAnimationRunning(); /** @brief Returns active renderer type */ ActorRendererType GetRendererType() const; protected: void textureHasChanged(Texture* newTexture) override; private: ActorBase* _owner; ActorRendererType _rendererType; float _rendererTransition; void UpdateVisibleFrames(); static std::int32_t NormalizeFrame(std::int32_t frame, std::int32_t min, std::int32_t max); }; /** @{ @name Constants */ /** @brief Alpha transparency threshold */ static constexpr std::uint8_t AlphaThreshold = 40; /** @brief Step for collision checking */ static constexpr float CollisionCheckStep = 0.5f; /** @brief Step for per-pixel collisions */ static constexpr std::int32_t PerPixelCollisionStep = 3; /** @brief Maximum number of animation candidates */ static constexpr std::int32_t AnimationCandidatesCount = 5; /** @} */ #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring ILevelHandler* _levelHandler; Vector2f _pos; Vector2f _speed; Vector2f _externalForce; float _internalForceY; float _elasticity; float _friction; float _unstuckCooldown; float _frozenTimeLeft; std::int32_t _maxHealth; std::int32_t _health; Vector2i _originTile; float _spawnFrames; Metadata* _metadata; ActorRenderer _renderer; GraphicResource* _currentAnimation; GraphicResource* _currentTransition; bool _currentTransitionCancellable; #endif /** @brief Sets internal parent node */ void SetParent(SceneNode* parent); /** @brief Sets whether the object is facing left */ void SetFacingLeft(bool value); /** @brief Called when the object is created and activated */ virtual Task OnActivatedAsync(const ActorActivationDetails& details); /** @brief Called when corresponding tile should be deactivated */ virtual bool OnTileDeactivated(); /** @brief Called when the object is attached to an another object */ virtual void OnAttach(ActorBase* parent); /** @brief Called when the object is detached from the previously attached object */ virtual void OnDetach(ActorBase* parent); /** @brief Called when health of the object changed */ virtual void OnHealthChanged(ActorBase* collider); /** @brief Called when the object has no health left and should perish */ virtual bool OnPerish(ActorBase* collider); /** @brief Called every frame to update the object state */ virtual void OnUpdate(float timeMult); /** @brief Called when the hitbox needs to be updated */ virtual void OnUpdateHitbox(); /** @brief Called when the object needs to be drawn */ virtual bool OnDraw(RenderQueue& renderQueue); /** @brief Called when emitting lights */ virtual void OnEmitLights(SmallVectorImpl& lights) { } /** @brief Called when the object hits a floor */ virtual void OnHitFloor(float timeMult); /** @brief Called when the object hits a ceiling */ virtual void OnHitCeiling(float timeMult); /** @brief Called when the object hits a wall */ virtual void OnHitWall(float timeMult); /** @brief Called when an event is triggered */ virtual void OnTriggeredEvent(EventType eventType, std::uint8_t* eventParams); /** @brief Performs standard movement behavior */ void TryStandardMovement(float timeMult, Tiles::TileCollisionParams& params); /** @brief Updates hitbox to a given size */ void UpdateHitbox(std::int32_t w, std::int32_t h); /** @brief Updates frozen state of the object */ void UpdateFrozenState(float timeMult); /** @brief Handles change of frozen state after collision with other object */ void HandleFrozenStateChange(ActorBase* shot); /** @brief Creates a particle debris from a sprite when the object is going to perish */ void CreateParticleDebrisOnPerish(ActorBase* collider); /** @overload */ void CreateParticleDebrisOnPerish(ParticleDebrisEffect effect, Vector2f speed); /** @brief Creates a sprite debris */ void CreateSpriteDebris(AnimState state, std::int32_t count); /** @brief Returns scale of ice shrapnels */ virtual float GetIceShrapnelScale() const; /** @brief Plays a sound effect for the object */ std::shared_ptr PlaySfx(StringView identifier, float gain = 1.0f, float pitch = 1.0f); /** @brief Sets an animation of the object */ bool SetAnimation(AnimState state, bool skipAnimation = false); /** @brief Sets a transition animation of the object */ bool SetTransition(AnimState state, bool cancellable, Function&& callback = {}); /** @brief Cancels a cancellable transition */ void CancelTransition(); /** @brief Cancels any transition */ void ForceCancelTransition(); /** @brief Called when an animation started */ virtual void OnAnimationStarted(); /** @brief Called when an animation finished */ virtual void OnAnimationFinished(); /** @brief Called when the object receives a network packet */ virtual void OnPacketReceived(MemoryStream& packet); /** @brief Sends a packet to the other side of a non-local session */ void SendPacket(ArrayView data); /** @brief Preloads specified metadata and its linked assets to cache */ static void PreloadMetadataAsync(StringView path); /** @brief Loads specified metadata and its linked assets */ void RequestMetadata(StringView path); /** @brief Loads specified metadata and its linked assets asynchronously if supported */ #if defined(WITH_COROUTINES) auto RequestMetadataAsync(StringView path) { struct awaitable { ActorBase* actor; const StringView& path; bool await_ready() { return false; } void await_suspend(std::coroutine_handle<> handle) { // TODO: implement async auto metadata = ContentResolver::Get().RequestMetadata(path); actor->_metadata = metadata; handle(); } void await_resume() { } }; return awaitable{this, path}; } #else void RequestMetadataAsync(StringView path); #endif /** @brief Sets actor state */ constexpr void SetState(ActorState flags) noexcept { _state = flags; } /** @overload */ constexpr void SetState(ActorState flag, bool value) noexcept { if (value) { _state = _state | flag; } else { _state = _state & (~flag); } } private: ActorBase(const ActorBase&) = delete; ActorBase& operator=(const ActorBase&) = delete; std::int32_t _collisionProxyID; ActorState _state; Function _currentTransitionCallback; bool IsCollidingWithAngled(ActorBase* other); bool IsCollidingWithAngled(const AABBf& aabb); void RefreshAnimation(bool skipAnimation = false); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/000077500000000000000000000000001512772601700253725ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/AmmoCollectible.cpp000066400000000000000000000061621512772601700311360ustar00rootroot00000000000000#include "AmmoCollectible.h" #include "../../ILevelHandler.h" #include "../../WeaponType.h" #include "../Player.h" namespace Jazz2::Actors::Collectibles { AmmoCollectible::AmmoCollectible() : _weaponType(WeaponType::Unknown) { } void AmmoCollectible::Preload(const ActorActivationDetails& details) { WeaponType weaponType = (WeaponType)details.Params[0]; switch (weaponType) { case WeaponType::Bouncer: PreloadMetadataAsync("Collectible/AmmoBouncer"_s); PreloadMetadataAsync("Weapon/Bouncer"_s); break; case WeaponType::Freezer: PreloadMetadataAsync("Collectible/AmmoFreezer"_s); PreloadMetadataAsync("Weapon/Freezer"_s); break; case WeaponType::Seeker: PreloadMetadataAsync("Collectible/AmmoSeeker"_s); PreloadMetadataAsync("Weapon/Seeker"_s); break; case WeaponType::RF: PreloadMetadataAsync("Collectible/AmmoRF"_s); PreloadMetadataAsync("Weapon/RF"_s); break; case WeaponType::Toaster: PreloadMetadataAsync("Collectible/AmmoToaster"_s); PreloadMetadataAsync("Weapon/Toaster"_s); break; case WeaponType::TNT: PreloadMetadataAsync("Collectible/AmmoTNT"_s); PreloadMetadataAsync("Weapon/TNT"_s); break; case WeaponType::Pepper: PreloadMetadataAsync("Collectible/AmmoPepper"_s); PreloadMetadataAsync("Weapon/Pepper"_s); break; case WeaponType::Electro: PreloadMetadataAsync("Collectible/AmmoElectro"_s); PreloadMetadataAsync("Weapon/Electro"_s); break; case WeaponType::Thunderbolt: PreloadMetadataAsync("Collectible/AmmoThunderbolt"_s); PreloadMetadataAsync("Weapon/Thunderbolt"_s); break; } } Task AmmoCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _scoreValue = 100; _weaponType = (WeaponType)details.Params[0]; switch (_weaponType) { case WeaponType::Bouncer: async_await RequestMetadataAsync("Collectible/AmmoBouncer"_s); break; case WeaponType::Freezer: async_await RequestMetadataAsync("Collectible/AmmoFreezer"_s); break; case WeaponType::Seeker: async_await RequestMetadataAsync("Collectible/AmmoSeeker"_s); break; case WeaponType::RF: async_await RequestMetadataAsync("Collectible/AmmoRF"_s); break; case WeaponType::Toaster: async_await RequestMetadataAsync("Collectible/AmmoToaster"_s); break; case WeaponType::TNT: async_await RequestMetadataAsync("Collectible/AmmoTNT"_s); break; case WeaponType::Pepper: async_await RequestMetadataAsync("Collectible/AmmoPepper"_s); break; case WeaponType::Electro: async_await RequestMetadataAsync("Collectible/AmmoElectro"_s); break; case WeaponType::Thunderbolt: async_await RequestMetadataAsync("Collectible/AmmoThunderbolt"_s); break; } // Show upgraded ammo if player has upgraded weapon, but only in local sessions const auto& players = _levelHandler->GetPlayers(); bool upgraded = (_levelHandler->IsLocalSession() && !players.empty() && (players[0]->GetWeaponUpgrades()[(std::uint8_t)_weaponType] & 0x01) != 0); SetAnimation((AnimState)(upgraded ? 1 : 0)); SetFacingDirection(); async_return true; } void AmmoCollectible::OnCollect(Player* player) { if (player->AddAmmo(_weaponType, 3)) { CollectibleBase::OnCollect(player); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/AmmoCollectible.h000066400000000000000000000010521512772601700305740ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" #include "../../LevelInitialization.h" namespace Jazz2::Actors::Collectibles { /** @brief Ammo (collectible) */ class AmmoCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: AmmoCollectible(); static void Preload(const ActorActivationDetails& details); protected: #ifndef DOXYGEN_GENERATING_OUTPUT WeaponType _weaponType; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CarrotCollectible.cpp000066400000000000000000000030231512772601700314700ustar00rootroot00000000000000#include "CarrotCollectible.h" #include "../Player.h" #include "../../ILevelHandler.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Collectibles { CarrotCollectible::CarrotCollectible() : _maxCarrot(false) { } void CarrotCollectible::Preload(const ActorActivationDetails& details) { bool maxCarrot = (details.Params[0] != 0); if (maxCarrot) { PreloadMetadataAsync("Collectible/CarrotFull"_s); } else { PreloadMetadataAsync("Collectible/Carrot"_s); } } Task CarrotCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _maxCarrot = (details.Params[0] != 0); if (_maxCarrot) { _scoreValue = 500; async_await RequestMetadataAsync("Collectible/CarrotFull"_s); } else { _scoreValue = 200; async_await RequestMetadataAsync("Collectible/Carrot"_s); } SetAnimation(AnimState::Default); SetFacingDirection(); async_return true; } void CarrotCollectible::OnCollect(Player* player) { if (_maxCarrot) { bool healthNotFull = player->AddHealth(-1); // Always collect if Reforged is enabled if (healthNotFull || _levelHandler->IsReforged()) { player->SetInvulnerability(5.0f * FrameTimer::FramesPerSecond, Player::InvulnerableType::Shielded); CollectibleBase::OnCollect(player); } } else { if (player->AddHealth(1)) { player->SetInvulnerability(0.8f * FrameTimer::FramesPerSecond, Player::InvulnerableType::Shielded); CollectibleBase::OnCollect(player); } } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CarrotCollectible.h000066400000000000000000000010021512772601700311300ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Carrot (collectible) */ class CarrotCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: CarrotCollectible(); static void Preload(const ActorActivationDetails& details); protected: #ifndef DOXYGEN_GENERATING_OUTPUT bool _maxCarrot; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CarrotFlyCollectible.cpp000066400000000000000000000014021512772601700321420ustar00rootroot00000000000000#include "CarrotFlyCollectible.h" #include "../Player.h" namespace Jazz2::Actors::Collectibles { CarrotFlyCollectible::CarrotFlyCollectible() { } void CarrotFlyCollectible::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/CarrotFly"_s); } Task CarrotFlyCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _scoreValue = 500; async_await RequestMetadataAsync("Collectible/CarrotFly"_s); SetAnimation(AnimState::Default); SetFacingDirection(); async_return true; } void CarrotFlyCollectible::OnCollect(Player* player) { if (player->SetModifier(Player::Modifier::Copter)) { CollectibleBase::OnCollect(player); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CarrotFlyCollectible.h000066400000000000000000000007171512772601700316170ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Fly carrot (collectible) */ class CarrotFlyCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: CarrotFlyCollectible(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CarrotInvincibleCollectible.cpp000066400000000000000000000016231512772601700334770ustar00rootroot00000000000000#include "CarrotInvincibleCollectible.h" #include "../Player.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Collectibles { CarrotInvincibleCollectible::CarrotInvincibleCollectible() { } void CarrotInvincibleCollectible::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/CarrotInvincible"_s); } Task CarrotInvincibleCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _scoreValue = 500; async_await RequestMetadataAsync("Collectible/CarrotInvincible"_s); SetAnimation(AnimState::Default); SetFacingDirection(); async_return true; } void CarrotInvincibleCollectible::OnCollect(Player* player) { player->SetInvulnerability(30.0f * FrameTimer::FramesPerSecond, Player::InvulnerableType::Shielded); CollectibleBase::OnCollect(player); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CarrotInvincibleCollectible.h000066400000000000000000000007441512772601700331470ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Invincible carrot (collectible) */ class CarrotInvincibleCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: CarrotInvincibleCollectible(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CoinCollectible.cpp000066400000000000000000000016411512772601700311320ustar00rootroot00000000000000#include "CoinCollectible.h" #include "../Player.h" namespace Jazz2::Actors::Collectibles { CoinCollectible::CoinCollectible() : _coinValue(0) { } void CoinCollectible::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/Coins"_s); } Task CoinCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); uint8_t coinType = details.Params[0]; switch (coinType) { default: case 0: // Silver _coinValue = 1; _scoreValue = 500; break; case 1: // Gold _coinValue = 5; _scoreValue = 1000; break; } async_await RequestMetadataAsync("Collectible/Coins"_s); SetAnimation((AnimState)coinType); SetFacingDirection(); async_return true; } void CoinCollectible::OnCollect(Player* player) { player->AddCoins(_coinValue); CollectibleBase::OnCollect(player); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CoinCollectible.h000066400000000000000000000010041512772601700305700ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Coin (collectible) */ class CoinCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: CoinCollectible(); static void Preload(const ActorActivationDetails& details); protected: #ifndef DOXYGEN_GENERATING_OUTPUT std::int32_t _coinValue; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CollectibleBase.cpp000066400000000000000000000075251512772601700311230ustar00rootroot00000000000000#include "CollectibleBase.h" #include "../Player.h" #include "../Explosion.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../Enemies/TurtleShell.h" #include "../../ILevelHandler.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" namespace Jazz2::Actors::Collectibles { CollectibleBase::CollectibleBase() : _untouched(true), _scoreValue(0), _phase(0.0f), _timeLeft(0.0f), _startingY(0.0f) { } Task CollectibleBase::OnActivatedAsync(const ActorActivationDetails& details) { _elasticity = 0.6f; SetState(ActorState::SkipPerPixelCollisions, true); Vector2f pos = _pos; _phase = ((pos.X / 32) + (pos.Y / 32)) * 2.0f; if ((GetState() & (ActorState::IsCreatedFromEventMap | ActorState::IsFromGenerator)) != ActorState::None) { _untouched = true; SetState(ActorState::ApplyGravitation, false); _startingY = pos.Y; } else { _untouched = false; SetState(ActorState::ApplyGravitation, true); if (_levelHandler->CanEventDisappear(details.Type)) { _timeLeft = 90.0f * FrameTimer::FramesPerSecond; } } if ((details.State & ActorState::Illuminated) == ActorState::Illuminated) { _illuminateLights.reserve(IlluminateLightCount); for (std::int32_t i = 0; i < IlluminateLightCount; i++) { auto& light = _illuminateLights.emplace_back(); light.Intensity = Random().NextFloat(0.22f, 0.42f); light.Distance = Random().NextFloat(4.0f, 36.0f); light.Phase = Random().NextFloat(0.0f, fTwoPi); light.Speed = Random().NextFloat(-0.12f, -0.04f); } } async_return true; } void CollectibleBase::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); if (_untouched) { _phase += timeMult * 0.15f; float waveOffset = 3.2f * cosf((_phase * 0.25f) * fPi) + 0.6f; MoveInstantly(Vector2f(_pos.X, _startingY + waveOffset), MoveType::Absolute); } else if (_timeLeft > 0.0f) { _timeLeft -= timeMult; if (_timeLeft <= 0.0f) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), Explosion::Type::Generator); DecreaseHealth(INT32_MAX); } } for (auto& current : _illuminateLights) { current.Phase += current.Speed * timeMult; } } void CollectibleBase::OnEmitLights(SmallVectorImpl& lights) { for (auto& current : _illuminateLights) { auto& light = lights.emplace_back(); light.Pos = Vector2f(_pos.X + cosf(current.Phase + cosf(current.Phase * 0.33f) * 0.33f) * current.Distance, _pos.Y + sinf(current.Phase + sinf(current.Phase) * 0.33f) * current.Distance); light.Intensity = current.Intensity * 0.7f; light.Brightness = current.Intensity; light.RadiusNear = 0.0f; light.RadiusFar = current.Intensity * 86.0f; } } bool CollectibleBase::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { OnCollect(player); return true; } else { bool shouldDrop = _untouched && (runtime_cast(other.get()) || runtime_cast(other.get()) || runtime_cast(other.get())); if (shouldDrop) { Vector2f speed = other->GetSpeed(); _externalForce.X += speed.X / 2.0f * (0.9f + Random().NextFloat(0.0f, 0.2f)); _externalForce.Y += speed.Y / 4.0f * (0.9f + Random().NextFloat(0.0f, 0.2f)); _untouched = false; SetState(ActorState::ApplyGravitation, true); } } return false; } void CollectibleBase::OnCollect(Player* player) { player->AddScore(_scoreValue); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), Explosion::Type::Generator); DecreaseHealth(INT32_MAX); } void CollectibleBase::SetFacingDirection(bool inverse) { SetFacingLeft(((((std::int32_t)(_pos.X + _pos.Y) / 32) & 1) == 0) ^ inverse); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/CollectibleBase.h000066400000000000000000000023171512772601700305620ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors { class Player; } namespace Jazz2::Actors::Collectibles { /** @brief Base class of a collectible object */ class CollectibleBase : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: CollectibleBase(); bool OnHandleCollision(std::shared_ptr other) override; protected: /** @{ @name Constants */ static constexpr std::int32_t IlluminateLightCount = 20; /** @} */ #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring struct IlluminateLight { float Intensity; float Distance; float Phase; float Speed; }; bool _untouched; std::int32_t _scoreValue; float _timeLeft; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; /** @brief Called when the collectible is collected */ virtual void OnCollect(Player* player); /** @brief Sets facing direction */ void SetFacingDirection(bool inverse = false); private: float _phase; float _startingY; SmallVector _illuminateLights; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/FastFireCollectible.cpp000066400000000000000000000022661512772601700317510ustar00rootroot00000000000000#include "FastFireCollectible.h" #include "../../ILevelHandler.h" #include "../Player.h" namespace Jazz2::Actors::Collectibles { FastFireCollectible::FastFireCollectible() { } void FastFireCollectible::Preload(const ActorActivationDetails& details) { // Preloading is not supported here } Task FastFireCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _scoreValue = 200; auto players = _levelHandler->GetPlayers(); PlayerType playerType = (_levelHandler->IsLocalSession() && !players.empty() ? players[0]->GetPlayerType() : PlayerType::Jazz); switch (playerType) { default: case PlayerType::Jazz: async_await RequestMetadataAsync("Collectible/FastFireJazz"_s); break; case PlayerType::Spaz: async_await RequestMetadataAsync("Collectible/FastFireSpaz"_s); break; case PlayerType::Lori: async_await RequestMetadataAsync("Collectible/FastFireLori"_s); break; } SetAnimation(AnimState::Default); SetFacingDirection(); async_return true; } void FastFireCollectible::OnCollect(Player* player) { if (player->AddFastFire(1)) { CollectibleBase::OnCollect(player); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/FastFireCollectible.h000066400000000000000000000007141512772601700314120ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Fast fire (collectible) */ class FastFireCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: FastFireCollectible(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/FoodCollectible.cpp000066400000000000000000000163061512772601700311350ustar00rootroot00000000000000#include "FoodCollectible.h" #include "../Player.h" namespace Jazz2::Actors::Collectibles { FoodCollectible::FoodCollectible() : _isDrinkable(false) { } void FoodCollectible::Preload(const ActorActivationDetails& details) { FoodType foodType = (FoodType)details.Params[0]; switch (foodType) { case FoodType::Apple: PreloadMetadataAsync("Collectible/FoodApple"_s); break; case FoodType::Banana: PreloadMetadataAsync("Collectible/FoodBanana"_s); break; case FoodType::Cherry: PreloadMetadataAsync("Collectible/FoodCherry"_s); break; case FoodType::Orange: PreloadMetadataAsync("Collectible/FoodOrange"_s); break; case FoodType::Pear: PreloadMetadataAsync("Collectible/FoodPear"_s); break; case FoodType::Pretzel: PreloadMetadataAsync("Collectible/FoodPretzel"_s); break; case FoodType::Strawberry: PreloadMetadataAsync("Collectible/FoodStrawberry"_s); break; case FoodType::Lemon: PreloadMetadataAsync("Collectible/FoodLemon"_s); break; case FoodType::Lime: PreloadMetadataAsync("Collectible/FoodLime"_s); break; case FoodType::Thing: PreloadMetadataAsync("Collectible/FoodThing"_s); break; case FoodType::WaterMelon: PreloadMetadataAsync("Collectible/FoodWaterMelon"_s); break; case FoodType::Peach: PreloadMetadataAsync("Collectible/FoodPeach"_s); break; case FoodType::Grapes: PreloadMetadataAsync("Collectible/FoodGrapes"_s); break; case FoodType::Lettuce: PreloadMetadataAsync("Collectible/FoodLettuce"_s); break; case FoodType::Eggplant: PreloadMetadataAsync("Collectible/FoodEggplant"_s); break; case FoodType::Cucumber: PreloadMetadataAsync("Collectible/FoodCucumber"_s); break; case FoodType::Pepsi: PreloadMetadataAsync("Collectible/FoodPepsi"_s); break; case FoodType::Coke: PreloadMetadataAsync("Collectible/FoodCoke"_s); break; case FoodType::Milk: PreloadMetadataAsync("Collectible/FoodMilk"_s); break; case FoodType::Pie: PreloadMetadataAsync("Collectible/FoodPie"_s); break; case FoodType::Cake: PreloadMetadataAsync("Collectible/FoodCake"_s); break; case FoodType::Donut: PreloadMetadataAsync("Collectible/FoodDonut"_s); break; case FoodType::Cupcake: PreloadMetadataAsync("Collectible/FoodCupcake"_s); break; case FoodType::Chips: PreloadMetadataAsync("Collectible/FoodChips"_s); break; case FoodType::Candy: PreloadMetadataAsync("Collectible/FoodCandy"_s); break; case FoodType::Chocolate: PreloadMetadataAsync("Collectible/FoodChocolate"_s); break; case FoodType::IceCream: PreloadMetadataAsync("Collectible/FoodIceCream"_s); break; case FoodType::Burger: PreloadMetadataAsync("Collectible/FoodBurger"_s); break; case FoodType::Pizza: PreloadMetadataAsync("Collectible/FoodPizza"_s); break; case FoodType::Fries: PreloadMetadataAsync("Collectible/FoodFries"_s); break; case FoodType::ChickenLeg: PreloadMetadataAsync("Collectible/FoodChickenLeg"_s); break; case FoodType::Sandwich: PreloadMetadataAsync("Collectible/FoodSandwich"_s); break; case FoodType::Taco: PreloadMetadataAsync("Collectible/FoodTaco"_s); break; case FoodType::HotDog: PreloadMetadataAsync("Collectible/FoodHotDog"_s); break; case FoodType::Ham: PreloadMetadataAsync("Collectible/FoodHam"_s); break; case FoodType::Cheese: PreloadMetadataAsync("Collectible/FoodCheese"_s); break; } } Task FoodCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _scoreValue = 50; FoodType foodType = (FoodType)details.Params[0]; switch (foodType) { case FoodType::Apple: async_await RequestMetadataAsync("Collectible/FoodApple"_s); break; case FoodType::Banana: async_await RequestMetadataAsync("Collectible/FoodBanana"_s); break; case FoodType::Cherry: async_await RequestMetadataAsync("Collectible/FoodCherry"_s); break; case FoodType::Orange: async_await RequestMetadataAsync("Collectible/FoodOrange"_s); break; case FoodType::Pear: async_await RequestMetadataAsync("Collectible/FoodPear"_s); break; case FoodType::Pretzel: async_await RequestMetadataAsync("Collectible/FoodPretzel"_s); break; case FoodType::Strawberry: async_await RequestMetadataAsync("Collectible/FoodStrawberry"_s); break; case FoodType::Lemon: async_await RequestMetadataAsync("Collectible/FoodLemon"_s); break; case FoodType::Lime: async_await RequestMetadataAsync("Collectible/FoodLime"_s); break; case FoodType::Thing: async_await RequestMetadataAsync("Collectible/FoodThing"_s); break; case FoodType::WaterMelon: async_await RequestMetadataAsync("Collectible/FoodWaterMelon"_s); break; case FoodType::Peach: async_await RequestMetadataAsync("Collectible/FoodPeach"_s); break; case FoodType::Grapes: async_await RequestMetadataAsync("Collectible/FoodGrapes"_s); break; case FoodType::Lettuce: async_await RequestMetadataAsync("Collectible/FoodLettuce"_s); break; case FoodType::Eggplant: async_await RequestMetadataAsync("Collectible/FoodEggplant"_s); break; case FoodType::Cucumber: async_await RequestMetadataAsync("Collectible/FoodCucumber"_s); break; case FoodType::Pepsi: async_await RequestMetadataAsync("Collectible/FoodPepsi"_s); break; case FoodType::Coke: async_await RequestMetadataAsync("Collectible/FoodCoke"_s); break; case FoodType::Milk: async_await RequestMetadataAsync("Collectible/FoodMilk"_s); break; case FoodType::Pie: async_await RequestMetadataAsync("Collectible/FoodPie"_s); break; case FoodType::Cake: async_await RequestMetadataAsync("Collectible/FoodCake"_s); break; case FoodType::Donut: async_await RequestMetadataAsync("Collectible/FoodDonut"_s); break; case FoodType::Cupcake: async_await RequestMetadataAsync("Collectible/FoodCupcake"_s); break; case FoodType::Chips: async_await RequestMetadataAsync("Collectible/FoodChips"_s); break; case FoodType::Candy: async_await RequestMetadataAsync("Collectible/FoodCandy"_s); break; case FoodType::Chocolate: async_await RequestMetadataAsync("Collectible/FoodChocolate"_s); break; case FoodType::IceCream: async_await RequestMetadataAsync("Collectible/FoodIceCream"_s); break; case FoodType::Burger: async_await RequestMetadataAsync("Collectible/FoodBurger"_s); break; case FoodType::Pizza: async_await RequestMetadataAsync("Collectible/FoodPizza"_s); break; case FoodType::Fries: async_await RequestMetadataAsync("Collectible/FoodFries"_s); break; case FoodType::ChickenLeg: async_await RequestMetadataAsync("Collectible/FoodChickenLeg"_s); break; case FoodType::Sandwich: async_await RequestMetadataAsync("Collectible/FoodSandwich"_s); break; case FoodType::Taco: async_await RequestMetadataAsync("Collectible/FoodTaco"_s); break; case FoodType::HotDog: async_await RequestMetadataAsync("Collectible/FoodHotDog"_s); break; case FoodType::Ham: async_await RequestMetadataAsync("Collectible/FoodHam"_s); break; case FoodType::Cheese: async_await RequestMetadataAsync("Collectible/FoodCheese"_s); break; } switch (foodType) { case FoodType::Pepsi: case FoodType::Coke: case FoodType::Milk: _isDrinkable = true; break; default: _isDrinkable = false; break; } SetAnimation(AnimState::Default); SetFacingDirection(); async_return true; } void FoodCollectible::OnCollect(Player* player) { player->ConsumeFood(_isDrinkable); CollectibleBase::OnCollect(player); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/FoodCollectible.h000066400000000000000000000021051512772601700305720ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Food type */ enum class FoodType { Apple = 1, Banana = 2, Cherry = 3, Orange = 4, Pear = 5, Pretzel = 6, Strawberry = 7, Lemon = 8, Lime = 9, Thing = 10, WaterMelon = 11, Peach = 12, Grapes = 13, Lettuce = 14, Eggplant = 15, Cucumber = 16, Pepsi = 17, Coke = 18, Milk = 19, Pie = 20, Cake = 21, Donut = 22, Cupcake = 23, Chips = 24, Candy = 25, Chocolate = 26, IceCream = 27, Burger = 28, Pizza = 29, Fries = 30, ChickenLeg = 31, Sandwich = 32, Taco = 33, HotDog = 34, Ham = 35, Cheese = 36, }; /** @brief Food (collectible) */ class FoodCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: FoodCollectible(); static void Preload(const ActorActivationDetails& details); protected: #ifndef DOXYGEN_GENERATING_OUTPUT bool _isDrinkable; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/GemCollectible.cpp000066400000000000000000000034111512772601700307470ustar00rootroot00000000000000#include "GemCollectible.h" #include "../Player.h" #include "../../ILevelHandler.h" namespace Jazz2::Actors::Collectibles { GemCollectible::GemCollectible() : _ignoreTime(0.0f), _gemType(0) { } void GemCollectible::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/Gems"_s); } Task GemCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _gemType = (std::uint8_t)(details.Params[0] & 0x03); bool hasLimitedLifetime = (details.Params[1] & 0x01) == 0x01; bool isFlipped = (details.Params[1] & 0x02) == 0x02; bool isDelayed = (details.Params[1] & 0x04) == 0x04; if (hasLimitedLifetime && _levelHandler->CanEventDisappear(EventType::Gem)) { _timeLeft = 8.0f * FrameTimer::FramesPerSecond; } if (isDelayed) { _ignoreTime = 40.0f; } async_await RequestMetadataAsync("Collectible/Gems"_s); std::int32_t weightedCount; switch (_gemType) { default: case 0: // Red (+1) weightedCount = 1; break; case 1: // Green (+5) weightedCount = 5; break; case 2: // Blue (+10) weightedCount = 10; break; case 3: // Purple weightedCount = 1; break; } _scoreValue = weightedCount * 100; SetAnimation((AnimState)_gemType); SetFacingDirection(isFlipped); _renderer.setAlphaF(0.7f); async_return true; } void GemCollectible::OnUpdate(float timeMult) { CollectibleBase::OnUpdate(timeMult); if (_ignoreTime > 0.0f) { _ignoreTime -= timeMult; } } void GemCollectible::OnUpdateHitbox() { UpdateHitbox(20, 20); } void GemCollectible::OnCollect(Player* player) { if (_ignoreTime > 0.0f) { return; } player->AddGems(_gemType, 1); CollectibleBase::OnCollect(player); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/GemCollectible.h000066400000000000000000000011401512772601700304110ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Gem (collectible) */ class GemCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: GemCollectible(); static void Preload(const ActorActivationDetails& details); protected: #ifndef DOXYGEN_GENERATING_OUTPUT float _ignoreTime; std::uint8_t _gemType; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/GemGiant.cpp000066400000000000000000000041651512772601700275770ustar00rootroot00000000000000#include "GemGiant.h" #include "../../ILevelHandler.h" #include "../../Events/EventSpawner.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Collectibles { GemGiant::GemGiant() { } void GemGiant::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/GemGiant"_s); PreloadMetadataAsync("Collectible/Gems"_s); } Task GemGiant::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/GemGiant"_s); SetAnimation(AnimState::Default); _renderer.setAlphaF(0.7f); async_return true; } bool GemGiant::OnHandleCollision(std::shared_ptr other) { if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(1); return true; } } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return ActorBase::OnHandleCollision(other); } bool GemGiant::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); PlaySfx("Break"_s); std::int32_t count = Random().Next(5, 12); for (int i = 0; i < count; i++) { float fx = Random().NextFloat(-16.0f, 16.0f); float fy = Random().NextFloat(-2.0f, 2.0f); std::uint8_t eventParams[Events::EventSpawner::SpawnParamsSize] = { 0, 0x01 }; std::shared_ptr actor = _levelHandler->EventSpawner()->SpawnEvent(EventType::Gem, eventParams, ActorState::None, Vector3i((std::int32_t)(_pos.X + fx * 2.0f), (std::int32_t)(_pos.Y + fy * 4.0f), _renderer.layer() - 10)); if (actor != nullptr) { actor->AddExternalForce(fx, fy); _levelHandler->AddActor(actor); } } return ActorBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/GemGiant.h000066400000000000000000000007431512772601700272420ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Giant gem */ class GemGiant : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: GemGiant(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/GemRing.cpp000066400000000000000000000112401512772601700274240ustar00rootroot00000000000000#include "GemRing.h" #include "../../ContentResolver.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Actors::Collectibles { GemRing::GemRing() : _phase(0.0f), _collected(false), _collectedPhase(0.0f) { } void GemRing::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/Gems"_s); } Task GemRing::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); std::int32_t length = (details.Params[0] > 0 ? details.Params[0] : 8); _speed = (details.Params[1] > 0 ? details.Params[1] : 8) * 0.00625f; _untouched = false; SetState(ActorState::SkipPerPixelCollisions, true); async_await RequestMetadataAsync("Collectible/Gems"_s); auto& resolver = ContentResolver::Get(); if (!resolver.IsHeadless()) { for (std::int32_t i = 0; i < length; i++) { ChainPiece& piece = _pieces.emplace_back(); piece.Scale = 0.8f; piece.Command = std::make_unique(RenderCommand::Type::Sprite); piece.Command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); piece.Command->GetMaterial().SetBlendingEnabled(true); piece.Command->GetMaterial().ReserveUniformsDataMemory(); piece.Command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = piece.Command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } async_return true; } void GemRing::OnUpdate(float timeMult) { if (_collected) { if (_collectedPhase > 100.0f) { DecreaseHealth(INT32_MAX); return; } for (int i = 0; i < _pieces.size(); i++) { float angle = _phase * (1.0f + _collectedPhase * 0.001f) + (i * fTwoPi / _pieces.size()); float distance = 8 * 4 + _collectedPhase * 3.6f; _pieces[i].Pos = Vector2f(_pos.X + cosf(angle) * distance, _pos.Y + sinf(angle) * distance); _pieces[i].Angle = angle + fPiOver2; _pieces[i].Scale += 0.02f * timeMult; } _phase += timeMult * _speed * 3.0f; _collectedPhase += timeMult; } else { for (int i = 0; i < _pieces.size(); i++) { float angle = _phase + (i * fTwoPi / _pieces.size()); float distance = 8 * (4 + sinf(_phase * 1.1f)); _pieces[i].Pos = Vector2f(_pos.X + cosf(angle) * distance, _pos.Y + sinf(angle) * distance); _pieces[i].Angle = angle + fPiOver2; } _phase += timeMult * _speed; } } void GemRing::OnUpdateHitbox() { AABBInner = AABBf( _pos.X - 20.0f, _pos.Y - 20.0f, _pos.X + 20.0f, _pos.Y + 20.0f ); } bool GemRing::OnDraw(RenderQueue& renderQueue) { if (!_pieces.empty()) { auto* res = _metadata->FindAnimation(AnimState::Default); // GemRed if (res != nullptr && res->Base->TextureDiffuse != nullptr) { Vector2i texSize = res->Base->TextureDiffuse->GetSize(); for (std::int32_t i = 0; i < _pieces.size(); i++) { auto command = _pieces[i].Command.get(); std::int32_t curAnimFrame = res->FrameOffset + (i % res->FrameCount); std::int32_t col = curAnimFrame % res->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / res->Base->FrameConfiguration.X; float texScaleX = (float(res->Base->FrameDimensions.X) / float(texSize.X)); float texBiasX = (float(res->Base->FrameDimensions.X * col) / float(texSize.X)); float texScaleY = (float(res->Base->FrameDimensions.Y) / float(texSize.Y)); float texBiasY = (float(res->Base->FrameDimensions.Y * row) / float(texSize.Y)); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(res->Base->FrameDimensions.X * _pieces[i].Scale, res->Base->FrameDimensions.Y * _pieces[i].Scale); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf(1.0f, 1.0f, 1.0f, 0.7f).Data()); auto& pos = _pieces[i].Pos; command->SetTransformation(Matrix4x4f::Translation(pos.X, pos.Y, 0.0f).RotateZ(_pieces[i].Angle)); command->SetLayer(_renderer.layer() - 2); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command); } } } return true; } void GemRing::OnCollect(Player* player) { if (!_collected) { _collected = true; SetState(ActorState::CollideWithOtherActors, false); player->AddGems(0, (std::int32_t)_pieces.size()); player->AddScore(800); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/GemRing.h000066400000000000000000000016201512772601700270720ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Gem ring */ class GemRing : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: GemRing(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnDraw(RenderQueue& renderQueue) override; void OnCollect(Player* player) override; private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct ChainPiece { Vector2f Pos; float Angle; float Scale; std::unique_ptr Command; }; #endif float _speed; float _phase; bool _collected; float _collectedPhase; SmallVector _pieces; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/OneUpCollectible.cpp000066400000000000000000000012611512772601700312660ustar00rootroot00000000000000#include "OneUpCollectible.h" #include "../Player.h" namespace Jazz2::Actors::Collectibles { OneUpCollectible::OneUpCollectible() { } void OneUpCollectible::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/OneUp"_s); } Task OneUpCollectible::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); _scoreValue = 2000; async_await RequestMetadataAsync("Collectible/OneUp"_s); SetAnimation(AnimState::Default); async_return true; } void OneUpCollectible::OnCollect(Player* player) { if (player->AddLives(1)) { CollectibleBase::OnCollect(player); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/OneUpCollectible.h000066400000000000000000000007001512772601700307300ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief 1up (collectible) */ class OneUpCollectible : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: OneUpCollectible(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/Stopwatch.cpp000066400000000000000000000015741512772601700300610ustar00rootroot00000000000000#include "Stopwatch.h" #include "../Player.h" #include "../../ILevelHandler.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Collectibles { Stopwatch::Stopwatch() { } void Stopwatch::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Collectible/Stopwatch"_s); } Task Stopwatch::OnActivatedAsync(const ActorActivationDetails& details) { async_await CollectibleBase::OnActivatedAsync(details); async_await RequestMetadataAsync("Collectible/Stopwatch"_s); SetAnimation(AnimState::Default); SetFacingDirection(); async_return true; } void Stopwatch::OnCollect(Player* player) { bool timeIncreased = player->IncreaseShieldTime(10.0f * FrameTimer::FramesPerSecond); // Always collect if Reforged is disabled if (timeIncreased || !_levelHandler->IsReforged()) { CollectibleBase::OnCollect(player); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Collectibles/Stopwatch.h000066400000000000000000000006521512772601700275220ustar00rootroot00000000000000#pragma once #include "CollectibleBase.h" namespace Jazz2::Actors::Collectibles { /** @brief Stopwatch */ class Stopwatch : public CollectibleBase { DEATH_RUNTIME_OBJECT(CollectibleBase); public: Stopwatch(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnCollect(Player* player) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/000077500000000000000000000000001512772601700243535ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bat.cpp000066400000000000000000000065761512772601700256030ustar00rootroot00000000000000#include "Bat.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Bat::Bat() : _attacking(false), _noiseCooldown(0.0f) { } void Bat::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Bat"_s); } Task Bat::OnActivatedAsync(const ActorActivationDetails& details) { _originPos = _pos; SetState(ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); SetHealthByDifficulty(1); _scoreValue = 100; async_await RequestMetadataAsync("Enemy/Bat"_s); SetAnimation(AnimState::Idle); async_return true; } void Bat::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } SetState(ActorState::CanJump, false); Vector2f targetPos; if (FindNearestPlayer(targetPos)) { if (_attacking) { // Can't fly into the water float waterLevel = _levelHandler->GetWaterLevel(); if (targetPos.Y > waterLevel - 20.0f) { targetPos.Y = waterLevel - 20.0f; } Vector2f direction = (_pos - targetPos); if (direction.Length() > 20.0f) { direction.Normalize(); _speed.X = direction.X * DefaultSpeed; _speed.Y = direction.Y * DefaultSpeed; SetFacingLeft(_speed.X < 0.0f); } if (_noiseCooldown > 0.0f) { _noiseCooldown -= timeMult; } else { _noiseCooldown = 60.0f; PlaySfx("Noise"_s); } } else { if (_currentTransition != nullptr) { return; } _speed.X = 0; _speed.Y = 0; _noiseCooldown = 0.0f; SetAnimation(AnimState::Walk); SetTransition((AnimState)1073741824, false, [this]() { _attacking = true; SetTransition((AnimState)1073741825, false); }); } } else { if (_attacking && _currentTransition == nullptr) { Vector2f direction = (_pos - _originPos); float length = direction.Length(); if (length < 2.0f) { _attacking = false; MoveInstantly(_originPos, MoveType::Absolute | MoveType::Force); _speed.X = 0.0f; _speed.Y = 0.0f; _noiseCooldown = 210.0f; SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false); } else { direction.Normalize(); _speed.X = direction.X * DefaultSpeed; _speed.Y = direction.Y * DefaultSpeed; SetFacingLeft(_speed.X < 0.0f); } } else { if (_noiseCooldown > 0.0f) { _noiseCooldown -= timeMult; } else { _noiseCooldown = 210.0f; SetTransition((AnimState)1073741827, true); } } } } void Bat::OnUpdateHitbox() { UpdateHitbox(24, 24); } bool Bat::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } bool Bat::FindNearestPlayer(Vector2f& targetPos) { constexpr float VisionDistanceIdle = 120.0f; constexpr float VisionDistanceAttacking = 320.0f; for (auto& player : _levelHandler->GetPlayers()) { if (player->GetHealth() <= 0) { continue; } targetPos = player->GetPos(); float visionDistance = (_currentAnimation->State == AnimState::Idle ? VisionDistanceIdle : VisionDistanceAttacking); if ((_originPos - targetPos).Length() < visionDistance) { return true; } } targetPos = Vector2f::Zero; return false; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bat.h000066400000000000000000000011761512772601700252370ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Bat */ class Bat : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Bat(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = -1.0f; Vector2f _originPos; bool _attacking; float _noiseCooldown; bool FindNearestPlayer(Vector2f& targetPos); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bee.cpp000066400000000000000000000064001512772601700255520ustar00rootroot00000000000000#include "Bee.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Bee::Bee() : _anglePhase(0.0f), _attackTime(80.0f), _attacking(false), _returning(false) { } Bee::~Bee() { if (_noise != nullptr) { _noise->stop(); _noise = nullptr; } } void Bee::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Bee"_s); } Task Bee::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); SetHealthByDifficulty(1); _scoreValue = 200; _originPos = _lastPos = _targetPos = _pos; async_await RequestMetadataAsync("Enemy/Bee"_s); SetAnimation(AnimState::Idle); async_return true; } void Bee::OnUpdate(float timeMult) { OnUpdateHitbox(); HandleBlinking(timeMult); UpdateFrozenState(timeMult); if (_frozenTimeLeft > 0.0f) { if (_noise != nullptr) { _noise->pause(); } return; } if (_attackTime > 0.0f) { _attackTime -= timeMult; } else { if (_attacking) { _targetPos = _originPos; _attackTime = 90.0f; _attacking = false; _returning = true; } else { if (_noise != nullptr) { // TODO: Fade-out _noise->stop(); _noise = nullptr; } AttackNearestPlayer(); } } if (_noise != nullptr) { _noise->play(); } _anglePhase += timeMult * 0.05f; Vector2f speed = ((_targetPos - _lastPos) * (_returning ? 0.01f : 0.002f) + _lastSpeed * 1.4f) / 2.4f; _lastPos.X += speed.X; _lastPos.Y += speed.Y; _lastSpeed = speed; bool willFaceLeft = (speed.X < 0.0f); if (IsFacingLeft() != willFaceLeft) { SetTransition(AnimState::TransitionTurn, false, [this, willFaceLeft]() { SetFacingLeft(willFaceLeft); }); } MoveInstantly(_lastPos + Vector2f(cosf(_anglePhase) * 16.0f, sinf(_anglePhase) * -16.0f), MoveType::Absolute | MoveType::Force); } bool Bee::OnPerish(ActorBase* collider) { if (_noise != nullptr) { _noise->stop(); _noise = nullptr; } CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } void Bee::AttackNearestPlayer() { bool found = false; float targetDistance = 300.0f; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); float distance = (_lastPos - newPos).Length(); if (distance < targetDistance) { _targetPos = newPos; targetDistance = distance; found = true; } } if (found) { _targetPos.X += (_targetPos.X - _originPos.X) * 1.6f; _targetPos.Y += (_targetPos.Y - _originPos.Y) * 1.6f; // Can't fly into the water float waterLevel = _levelHandler->GetWaterLevel() - 12.0f; if (_targetPos.Y > waterLevel) { _targetPos.Y = waterLevel; } _attackTime = 110.0f; _attacking = true; _returning = false; if (_noise == nullptr) { _noise = PlaySfx("Noise"_s, 0.5f, 2.0f); if (_noise != nullptr) { _noise->setLooping(true); } } } else { _targetPos = _originPos; _returning = true; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bee.h000066400000000000000000000012221512772601700252140ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Bee */ class Bee : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Bee(); ~Bee(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: Vector2f _originPos, _lastPos, _targetPos, _lastSpeed; float _anglePhase; float _attackTime; bool _attacking, _returning; std::shared_ptr _noise; void AttackNearestPlayer(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/000077500000000000000000000000001512772601700256115ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Bilsy.cpp000066400000000000000000000167361512772601700274140ustar00rootroot00000000000000#include "Bilsy.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { Bilsy::Bilsy() : _state(StateTransition), _stateTime(0.0f), _endText(0) { } void Bilsy::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { case 0: default: PreloadMetadataAsync("Boss/Bilsy"_s); break; case 1: // Xmas PreloadMetadataAsync("Boss/BilsyXmas"_s); break; } } Task Bilsy::OnActivatedAsync(const ActorActivationDetails& details) { _theme = details.Params[0]; _endText = details.Params[1]; _originPos = _pos; _scoreValue = 3000; SetState(ActorState::ApplyGravitation, false); switch (_theme) { case 0: default: async_await RequestMetadataAsync("Boss/Bilsy"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Boss/BilsyXmas"_s); break; } SetAnimation(AnimState::Idle); _renderer.setDrawEnabled(false); async_return true; } bool Bilsy::OnActivatedBoss() { SetHealthByDifficulty(120); Teleport(); return true; } void Bilsy::OnUpdate(float timeMult) { BossBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } switch (_state) { case StateWaiting: { if (_stateTime <= 0.0f) { bool isFacingPlayer; if (_levelHandler->GetDifficulty() < GameDifficulty::Hard) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } isFacingPlayer = (found && (targetPos.X < _pos.X) == IsFacingLeft()); } else { isFacingPlayer = true; } if (isFacingPlayer) { _state = StateTransition; SetTransition((AnimState)1073741826, false, [this]() { PlaySfx("ThrowFireball"_s); std::shared_ptr fireball = std::make_shared(); uint8_t fireballParams[2] = { _theme, (uint8_t)(IsFacingLeft() ? 1 : 0) }; fireball->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -26 : 26), (std::int32_t)_pos.Y - 20, _renderer.layer() + 2), fireballParams )); _levelHandler->AddActor(fireball); SetTransition((AnimState)1073741827, false, [this]() { _state = StateWaiting2; _stateTime = 30.0f; }); }); } else { _state = StateWaiting2; _stateTime = 30.0f; } } break; } case StateWaiting2: { if (_stateTime <= 0.0f) { SetState(ActorState::CanBeFrozen, false); PlaySfx("Disappear"_s, 0.8f); if (_levelHandler->GetDifficulty() < GameDifficulty::Hard) { _canHurtPlayer = false; } _state = StateTransition; SetTransition((AnimState)1073741825, false, [this]() { Teleport(); }); } break; } } _stateTime -= timeMult; } void Bilsy::OnUpdateHitbox() { UpdateHitbox(20, 60); } bool Bilsy::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); return BossBase::OnPerish(collider); } void Bilsy::Teleport() { for (std::int32_t i = 0; i < 20; i++) { Vector2f pos = Vector2f(_originPos.X + Random().NextFloat(-320.0f, 320.0f), _originPos.Y + Random().NextFloat(-240.0f, 240.0f)); if (MoveInstantly(pos, MoveType::Absolute)) { break; } } OnUpdateHitbox(); std::int32_t j = 60; while (j-- > 0 && MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative)) { // Nothing to do... } while (j-- > 0 && MoveInstantly(Vector2f(0.0f, 1.0f), MoveType::Relative)) { // Nothing to do... } bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { SetFacingLeft(targetPos.X < _pos.X); } _renderer.setDrawEnabled(true); _state = StateTransition; SetTransition((AnimState)1073741824, false, [this]() { SetState(ActorState::CanBeFrozen, true); if (_levelHandler->GetDifficulty() < GameDifficulty::Hard) { _canHurtPlayer = true; } _state = StateWaiting; _stateTime = 30.0f; }); PlaySfx("Appear"_s, 0.8f); } Task Bilsy::Fireball::OnActivatedAsync(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; SetFacingLeft(details.Params[1] != 0); _timeLeft = 90.0f; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); CanCollideWithShots = false; _health = INT32_MAX; _speed.X = (IsFacingLeft() ? -4.0f : 4.0f); _speed.Y = 2.0f; switch (theme) { case 0: default: async_await RequestMetadataAsync("Boss/Bilsy"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Boss/BilsyXmas"_s); break; } SetAnimation((AnimState)1073741828); PlaySfx("FireStart"_s); async_return true; } void Bilsy::Fireball::OnUpdate(float timeMult) { MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _timeLeft -= timeMult; FollowNearestPlayer(); } // TODO: Spawn fire particles } void Bilsy::Fireball::OnUpdateHitbox() { UpdateHitbox(18, 18); } void Bilsy::Fireball::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.85f; light.Brightness = 0.4f; light.RadiusNear = 0.0f; light.RadiusFar = 30.0f; } bool Bilsy::Fireball::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX); } return ActorBase::OnHandleCollision(std::move(other)); } bool Bilsy::Fireball::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::RF); return EnemyBase::OnPerish(collider); } void Bilsy::Fireball::FollowNearestPlayer() { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } if (found) { Vector2f diff = (targetPos - _pos).Normalized(); Vector2f speed = (Vector2f(_speed.X, _speed.Y) + diff * 0.4f).Normalized(); _speed.X = speed.X * 4.0f; _speed.Y = speed.Y * 4.0f; if (_speed.X < 0.0f) { SetFacingLeft(true); _renderer.setRotation(atan2f(-_speed.Y, -_speed.X)); } else { SetFacingLeft(false); _renderer.setRotation(atan2f(_speed.Y, _speed.X)); } } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Bilsy.h000066400000000000000000000026261512772601700270520ustar00rootroot00000000000000#pragma once #include "BossBase.h" namespace Jazz2::Actors::Bosses { /** @brief Bilsy (boss) */ class Bilsy : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: Bilsy(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateTransition = -1; static constexpr std::int32_t StateWaiting = 0; static constexpr std::int32_t StateWaiting2 = 1; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Fireball : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; private: float _timeLeft; void FollowNearestPlayer(); }; #endif std::int32_t _state; float _stateTime; std::uint8_t _theme, _endText; Vector2f _originPos; void Teleport(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Bolly.cpp000066400000000000000000000241101512772601700273740ustar00rootroot00000000000000#include "Bolly.h" #include "../../../ILevelHandler.h" #include "../Crab.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../Weapons/ShotBase.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { Bolly::Bolly() : _state(StateWaiting), _stateTime(0.0f), _endText(0), _noiseCooldown(0.0f), _rocketsLeft(0), _chainPhase(0.0f) { } Bolly::~Bolly() { if (_bottom != nullptr) { _bottom->SetState(ActorState::IsDestroyed, true); } /*if (_turret != nullptr) { _turret->SetState(ActorState::IsDestroyed, true); }*/ for (std::int32_t i = 0; i < std::int32_t(arraySize(_chain)); i++) { if (_chain[i] != nullptr) { _chain[i]->SetState(ActorState::IsDestroyed, true); } } } void Bolly::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/Bolly"_s); } Task Bolly::OnActivatedAsync(const ActorActivationDetails& details) { _endText = details.Params[1]; _originPos = _pos; SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); _scoreValue = 3000; async_await RequestMetadataAsync("Boss/Bolly"_s); SetAnimation(AnimState::Idle); _bottom = std::make_shared(); std::uint8_t bottomParams[1] = { 1 }; _bottom->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), bottomParams )); _levelHandler->AddActor(_bottom); /*_turret = std::make_shared(); uint8_t turretParams[1] = { 2 }; _turret->OnActivated({ .LevelHandler = _levelHandler, .Pos = Vector3i((int)_pos.X, (int)_pos.Y, _renderer.layer() + 6), .Params = turretParams }); _levelHandler->AddActor(_turret);*/ std::int32_t chainLength = (_levelHandler->GetDifficulty() < GameDifficulty::Hard ? NormalChainLength : HardChainLength); for (std::int32_t i = 0; i < chainLength; i++) { _chain[i] = std::make_shared(); std::uint8_t chainParams[1] = { (std::uint8_t)((i % 3) == 2 ? 3 : 4) }; _chain[i]->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - ((i % 3) == 2 ? 2 : 4)), chainParams )); _levelHandler->AddActor(_chain[i]); } async_return true; } bool Bolly::OnActivatedBoss() { SetHealthByDifficulty(42); MoveInstantly(_originPos, MoveType::Absolute | MoveType::Force); FollowNearestPlayer(StateFlying, 100.0f); return true; } void Bolly::OnUpdate(float timeMult) { HandleBlinking(timeMult); MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); switch (_state) { case StateFlying: { if (_stateTime <= 0.0f) { if (Random().NextFloat() < 0.1f) { _state = StatePrepairingToAttack; _stateTime = 120.0f; _speed.X = 0; _speed.Y = 0; } else { _state = StateNewDirection; _stateTime = 50.0f; } } break; } case StateNewDirection: { if (_stateTime <= 0.0f) { FollowNearestPlayer(StateFlying, 1.0f); } break; } case StatePrepairingToAttack: { if (_stateTime <= 0.0f) { _state = StateAttacking; _stateTime = 20.0f; _rocketsLeft = 5; PlaySfx("PreAttack"_s); } break; } case StateAttacking: { if (_stateTime <= 0.0f) { if (_rocketsLeft > 0) { _stateTime = 20.0f; FireRocket(); _rocketsLeft--; PlaySfx("Attack"_s); } else { _state = StateNewDirection; _stateTime = 100.0f; PlaySfx("PostAttack"_s); } } break; } } if (_bottom != nullptr) { _bottom->MoveInstantly(Vector2f(_pos.X + _speed.X * timeMult, _pos.Y + _speed.Y * timeMult), MoveType::Absolute | MoveType::Force); _bottom->SetFacingLeft(IsFacingLeft()); } /*if (_turret != nullptr) { UpdateTurret(timeMult); }*/ float distance = 30.0f; for (std::int32_t i = 0; i < std::int32_t(arraySize(_chain)); i++) { if (_chain[i] != nullptr) { float angle = sinf(_chainPhase - i * 0.08f) * 1.2f + fPiOver2; Vector2f piecePos = _pos; piecePos.X += cosf(angle) * distance; piecePos.Y += sinf(angle) * distance; _chain[i]->MoveInstantly(piecePos, MoveType::Absolute | MoveType::Force); distance += _chain[i]->Size; } } if (_noiseCooldown > 0.0f) { _noiseCooldown -= timeMult; } else { _noiseCooldown = 120.0f; PlaySfx("Noise"_s, 0.2f); } _stateTime -= timeMult; _chainPhase += timeMult * 0.06f; } bool Bolly::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i(std::int32_t(_pos.X), std::int32_t(_pos.Y), _renderer.layer() + 2), Explosion::Type::Large); CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); return BossBase::OnPerish(collider); } /*void Bolly::UpdateTurret(float timeMult) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto& players = _levelHandler->GetPlayers(); for (auto& player : players) { Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } if (found) { bool facingLeft = (targetPos.X < _pos.X); Vector2f diff = (targetPos - _pos).Normalized(); _turret->_renderer.setRotation(facingLeft ? atan2f(-diff.Y, -diff.X) : atan2f(diff.Y, diff.X)); _turret->SetFacingLeft(facingLeft); } _turret->MoveInstantly(Vector2f(_pos.X + (IsFacingLeft() ? 10.0f : -10.0f) + _speed.X * timeMult, _pos.Y + 10.0f + _speed.Y * timeMult), MoveType::Absolute | MoveType::Force); }*/ void Bolly::FollowNearestPlayer(std::int32_t newState, float time) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { _state = newState; _stateTime = time; targetPos.Y += Random().FastFloat(-100.0f, 20.0f); SetFacingLeft(targetPos.X < _pos.X); float speedMultiplier = (_levelHandler->GetDifficulty() < GameDifficulty::Hard ? 0.6f : 0.8f); Vector2f speed = (targetPos - _pos).Normalized(); _speed.X = speed.X * speedMultiplier; _speed.Y = speed.Y * speedMultiplier; } } void Bolly::FireRocket() { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { Vector2f diff = (targetPos - _pos).Normalized(); std::shared_ptr rocket = std::make_shared(); rocket->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? 10 : -10), (std::int32_t)_pos.Y + 10, _renderer.layer() - 4) )); rocket->_renderer.setRotation(atan2f(diff.Y, diff.X)); _levelHandler->AddActor(rocket); } } Task Bolly::BollyPart::OnActivatedAsync(const ActorActivationDetails& details) { uint8_t partType = details.Params[0]; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); _health = INT32_MAX; async_await RequestMetadataAsync("Boss/Bolly"_s); SetAnimation((AnimState)partType); switch (partType) { case 1: // Bottom SetState(ActorState::IsSolidObject, true); SetState(ActorState::CollideWithOtherActors, false); CanCollideWithShots = true; Size = 0.0f; break; case 2: // Turret SetState(ActorState::CollideWithOtherActors, false); CanCollideWithShots = false; Size = 0.0f; break; case 3: // Chain 1 CanCollideWithShots = false; Size = 14.0f; break; case 4: // Chain 2 CanCollideWithShots = false; Size = 7.0f; break; } async_return true; } void Bolly::BollyPart::OnUpdate(float timeMult) { } Task Bolly::Rocket::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); CanCollideWithShots = false; _timeLeft = 300.0f; _health = INT32_MAX; async_await RequestMetadataAsync("Boss/Bolly"_s); SetAnimation((AnimState)5); async_return true; } void Bolly::Rocket::OnUpdate(float timeMult) { float angle = _renderer.rotation(); _speed.X += cosf(angle) * 0.14f * timeMult; _speed.Y += sinf(angle) * 0.14f * timeMult; EnemyBase::OnUpdate(timeMult); if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _timeLeft -= timeMult; } } void Bolly::Rocket::OnUpdateHitbox() { UpdateHitbox(20, 20); } void Bolly::Rocket::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.Brightness = 0.8f; light.RadiusNear = 3.0f; light.RadiusFar = 12.0f; } bool Bolly::Rocket::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX); } return ActorBase::OnHandleCollision(std::move(other)); } bool Bolly::Rocket::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::RF); return EnemyBase::OnPerish(collider); } void Bolly::Rocket::OnHitFloor(float timeMult) { DecreaseHealth(INT32_MAX); } void Bolly::Rocket::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void Bolly::Rocket::OnHitCeiling(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Bolly.h000066400000000000000000000045071512772601700270510ustar00rootroot00000000000000#pragma once #include "BossBase.h" namespace Jazz2::Actors::Bosses { /** @brief Bolly (boss) */ class Bolly : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: Bolly(); ~Bolly(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateTransition = -1; static constexpr std::int32_t StateWaiting = 0; static constexpr std::int32_t StateFlying = 1; static constexpr std::int32_t StateNewDirection = 2; static constexpr std::int32_t StatePrepairingToAttack = 3; static constexpr std::int32_t StateAttacking = 4; static constexpr std::int32_t NormalChainLength = 5 * 3; static constexpr std::int32_t HardChainLength = 6 * 3; static constexpr std::int32_t MaxChainLength = HardChainLength; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class BollyPart : public EnemyBase { friend class Bolly; public: float Size; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; }; class Rocket : public EnemyBase { friend class Bolly; public: bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitCeiling(float timeMult) override; private: float _timeLeft; }; #endif std::int32_t _state; float _stateTime; uint8_t _endText; std::shared_ptr _bottom; //std::shared_ptr _turret; std::shared_ptr _chain[MaxChainLength]; Vector2f _originPos; float _noiseCooldown; std::int32_t _rocketsLeft; float _chainPhase; //void UpdateTurret(float timeMult); void FollowNearestPlayer(std::int32_t newState, float time); void FireRocket(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/BossBase.cpp000066400000000000000000000021561512772601700300220ustar00rootroot00000000000000#include "BossBase.h" #include "../../../ILevelHandler.h" #include "../../../Events/EventMap.h" namespace Jazz2::Actors::Bosses { bool BossBase::OnPlayerDied() { if ((GetState() & (ActorState::IsCreatedFromEventMap | ActorState::IsFromGenerator)) != ActorState::None) { auto events = _levelHandler->EventMap(); if (events != nullptr) { if (GetState(ActorState::IsFromGenerator)) { events->ResetGenerator(_originTile.X, _originTile.Y); } events->Deactivate(_originTile.X, _originTile.Y); } OnDeactivatedBoss(); SetState(ActorState::IsDestroyed | ActorState::SkipPerPixelCollisions, true); return true; } return false; } bool BossBase::OnTileDeactivated() { // Boss cannot be deactivated return false; } void BossBase::SetHealthByDifficulty(std::int32_t health) { // Each player adds 50% health, up to +1000% (20 players) if (_levelHandler->IsReforged()) { float multiplier = 1.0f + (std::clamp((std::int32_t)_levelHandler->GetPlayers().size() - 1, 0, 20) * 0.5f); health = (std::int32_t)(health * multiplier); } EnemyBase::SetHealthByDifficulty(health); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/BossBase.h000066400000000000000000000006761512772601700274740ustar00rootroot00000000000000#pragma once #include "../EnemyBase.h" namespace Jazz2::Actors::Bosses { /** @brief Base class of an enemy boss */ class BossBase : public Enemies::EnemyBase { DEATH_RUNTIME_OBJECT(Enemies::EnemyBase); public: virtual bool OnActivatedBoss() = 0; virtual void OnDeactivatedBoss() {}; virtual bool OnPlayerDied(); protected: bool OnTileDeactivated() override; void SetHealthByDifficulty(std::int32_t health) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Bubba.cpp000066400000000000000000000204341512772601700273330ustar00rootroot00000000000000#include "Bubba.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { Bubba::Bubba() : _state(State::Waiting), _stateTime(0.0f), _tornadoCooldown(120.0f), _endText(0) { } void Bubba::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/Bubba"_s); } Task Bubba::OnActivatedAsync(const ActorActivationDetails& details) { _endText = details.Params[1]; _scoreValue = 4000; SetState(ActorState::CollideWithTilesetReduced, true); async_await RequestMetadataAsync("Boss/Bubba"_s); SetAnimation(AnimState::Idle); async_return true; } bool Bubba::OnActivatedBoss() { SetHealthByDifficulty(93); FollowNearestPlayer(); return true; } void Bubba::OnUpdate(float timeMult) { BossBase::OnUpdate(timeMult); if (_tornadoCooldown > 0.0f) { _tornadoCooldown -= timeMult; } // Process level bounds Recti levelBounds = _levelHandler->GetLevelBounds(); if (_pos.X < levelBounds.X) { _pos.X = static_cast(levelBounds.X); } else if (_pos.X > levelBounds.X + levelBounds.W) { _pos.X = static_cast(levelBounds.X + levelBounds.W); } if (_frozenTimeLeft > 0.0f) { return; } switch (_state) { case State::Jumping: { if (_speed.Y >= 0.0f) { _state = State::Falling; SetAnimation(AnimState::Fall); } break; } case State::Falling: { if (GetState(ActorState::CanJump)) { _speed.Y = 0.0f; _speed.X = 0.0f; _state = State::Transition; SetTransition(AnimState::TransitionFallToIdle, false, [this]() { float rand = Random().NextFloat(); bool spewFileball = (rand < 0.35f); bool tornado = (rand < 0.65f && _tornadoCooldown <= 0.0f); if (spewFileball) { PlaySfx("Sneeze"_s); SetTransition(AnimState::Shoot, false, [this]() { float x = (IsFacingLeft() ? -16.0f : 16.0f); float y = -5.0f; std::shared_ptr fireball = std::make_shared(); uint8_t fireballParams[1] = { (uint8_t)(IsFacingLeft() ? 1 : 0) }; fireball->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)(_pos.X + x), (std::int32_t)(_pos.Y + y), _renderer.layer() - 2), fireballParams )); _levelHandler->AddActor(fireball); SetTransition(AnimState::TransitionShootToIdle, false, [this]() { FollowNearestPlayer(); }); }); } else if (tornado) { TornadoToNearestPlayer(); } else { FollowNearestPlayer(); } }); } break; } case State::Tornado: { if (_stateTime <= 0.0f) { float cooldownMin, cooldownMax; switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Easy: cooldownMin = 600.0f; cooldownMax = 1200.0f; break; default: case GameDifficulty::Normal: cooldownMin = 300.0f; cooldownMax = 600.0f; break; case GameDifficulty::Hard: cooldownMin = 60.0f; cooldownMax = 480.0f; break; } _tornadoCooldown = Random().NextFloat(cooldownMin, cooldownMax); _state = State::Transition; SetTransition((AnimState)1073741832, false, [this]() { SetState(ActorState::CollideWithTilesetReduced | ActorState::ApplyGravitation, true); _state = State::Falling; if (_tornadoNoise != nullptr) { _tornadoNoise->stop(); _tornadoNoise = nullptr; } SetAnimation((AnimState)1073741833); }); } break; } case State::Dying: { float time = (_renderer.AnimTime / _renderer.AnimDuration); _renderer.setColor(Colorf(1.0f, 1.0f, 1.0f, 1.0f - (time * time * time * time))); break; } } _stateTime -= timeMult; } void Bubba::OnUpdateHitbox() { UpdateHitbox(20, 24); } bool Bubba::OnPerish(ActorBase* collider) { // It must be done here, because the player may not exist after animation callback AddScoreToCollider(collider); ForceCancelTransition(); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); _speed.X = 0.0f; _speed.Y = -2.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _frozenTimeLeft = std::min(1.0f, _frozenTimeLeft); SetState(ActorState::CollideWithTileset | ActorState::CollideWithTilesetReduced | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); _state = State::Dying; SetTransition(AnimState::TransitionDeath, false, [this, collider]() { BossBase::OnPerish(collider); }); return false; } void Bubba::OnHitWall(float timeMult) { if (_state == State::Tornado && _stateTime > 1.0f) { _stateTime = 1.0f; } } void Bubba::FollowNearestPlayer() { bool found = false; bool isFacingLeft = false; float randomValue = Random().NextFloat(); if (randomValue < 0.1f) { found = true; isFacingLeft = true; } else if (randomValue > 0.9f) { found = true; isFacingLeft = false; } else { Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } isFacingLeft = (targetPos.X < _pos.X); } if (found) { _state = State::Jumping; _stateTime = 26; SetFacingLeft(isFacingLeft); _speed.X = (isFacingLeft ? -1.3f : 1.3f); _speed.Y = -5.5f; _internalForceY = -0.8f; PlaySfx("Jump"_s); SetTransition((AnimState)1073741825, false); SetAnimation(AnimState::Jump); } } void Bubba::TornadoToNearestPlayer() { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { _state = State::Tornado; _stateTime = 60.0f; SetState(ActorState::CollideWithTilesetReduced | ActorState::ApplyGravitation, false); Vector2f diff = (targetPos - _pos); _tornadoNoise = PlaySfx("Tornado"_s); SetTransition((AnimState)1073741830, false, [this, diff]() { _speed.X = (diff.X / _stateTime); _speed.Y = (diff.Y / _stateTime); _internalForceY = 0.0f; _externalForce.Y = 0.0f; SetAnimation((AnimState)1073741831); }); } } Task Bubba::Fireball::OnActivatedAsync(const ActorActivationDetails& details) { SetFacingLeft(details.Params[0] != 0); _speed.X = (IsFacingLeft() ? -4.8f : 4.8f); _timeLeft = 50.0f; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); CanCollideWithShots = false; _health = INT32_MAX; async_await RequestMetadataAsync("Boss/Bubba"_s); SetAnimation((AnimState)1073741834); async_return true; } void Bubba::Fireball::OnUpdate(float timeMult) { MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _timeLeft -= timeMult; } } void Bubba::Fireball::OnUpdateHitbox() { UpdateHitbox(18, 18); } void Bubba::Fireball::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.85f; light.Brightness = 0.4f; light.RadiusNear = 0.0f; light.RadiusFar = 30.0f; } bool Bubba::Fireball::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX); } return ActorBase::OnHandleCollision(std::move(other)); } bool Bubba::Fireball::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::RF); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Bubba.h000066400000000000000000000026601512772601700270010ustar00rootroot00000000000000#pragma once #include "BossBase.h" namespace Jazz2::Actors::Bosses { /** @brief Bubba (boss) */ class Bubba : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: Bubba(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; private: enum class State { Transition = -1, Waiting = 0, Jumping = 1, Falling = 2, Tornado = 3, Dying = 4 }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Fireball : public EnemyBase { public: bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; private: float _timeLeft; }; #endif State _state; float _stateTime; float _tornadoCooldown; std::uint8_t _endText; std::shared_ptr _tornadoNoise; void FollowNearestPlayer(); void TornadoToNearestPlayer(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Devan.cpp000066400000000000000000000375411512772601700273640ustar00rootroot00000000000000#include "Devan.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../Weapons/ShotBase.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { static constexpr AnimState ShootStart = (AnimState)10; static constexpr AnimState ShootInProgress = (AnimState)11; static constexpr AnimState ShootEnd = (AnimState)12; static constexpr AnimState ShootEnd2 = (AnimState)13; static constexpr AnimState CrouchStart = (AnimState)14; // TODO: Unused static constexpr AnimState CrouchInProgress = (AnimState)15; // TODO: Unused static constexpr AnimState CrouchEnd = (AnimState)16; // TODO: Unused static constexpr AnimState JumpStart = (AnimState)17; // TODO: Unused static constexpr AnimState JumpInProgress = (AnimState)18; // TODO: Unused static constexpr AnimState JumpEnd = (AnimState)19; // TODO: Unused static constexpr AnimState JumpEnd2 = (AnimState)20; // TODO: Unused static constexpr AnimState DisarmedStart = (AnimState)22; static constexpr AnimState DisarmedGunDecor = (AnimState)23; static constexpr AnimState DisorientedStart = (AnimState)24; static constexpr AnimState Disoriented = (AnimState)25; static constexpr AnimState DisorientedWarpOut = (AnimState)26; static constexpr AnimState DevanBullet = (AnimState)27; static constexpr AnimState DemonFly = (AnimState)30; static constexpr AnimState DemonTransformStart = (AnimState)31; static constexpr AnimState DemonTransformEnd = (AnimState)32; static constexpr AnimState DemonTurn = (AnimState)33; static constexpr AnimState DemonSpewFireball = (AnimState)34; static constexpr AnimState DemonSpewFireballEnd = (AnimState)35; static constexpr AnimState DemonFireball = (AnimState)36; Devan::Devan() : _state(State::Waiting), _stateTime(0.0f), _crouchCooldown(60.0f), _endText(0), _shots(0), _isDemon(false), _isDead(false) { } void Devan::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/Devan"_s); } Task Devan::OnActivatedAsync(const ActorActivationDetails& details) { _endText = details.Params[1]; _scoreValue = 10000; SetState(ActorState::CollideWithOtherActors, false); async_await RequestMetadataAsync("Boss/Devan"_s); SetAnimation(AnimState::Idle); _renderer.setDrawEnabled(false); async_return true; } bool Devan::OnActivatedBoss() { SetHealthByDifficulty(140 * 2); _state = State::WarpingIn; _stateTime = 120.0f; _attackTime = 90.0f; _anglePhase = 0.0f; _shots = 0; _isDemon = false; _isDead = false; SetState(ActorState::CollideWithOtherActors, true); return true; } void Devan::OnUpdate(float timeMult) { if (_isDemon) { OnUpdateHitbox(); HandleBlinking(timeMult); MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); } else { BossBase::OnUpdate(timeMult); } if (_frozenTimeLeft > 0.0f) { return; } switch (_state) { case State::WarpingIn: { if (_stateTime <= 0.0f) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } if (found) { SetFacingLeft(targetPos.X < _pos.X); } _renderer.setDrawEnabled(true); _state = State::Transition; SetTransition(AnimState::TransitionWarpIn, false, [this]() { _state = State::Idling; _stateTime = 80.0f; SetState(ActorState::IsInvulnerable, false); _canHurtPlayer = true; }); } break; } case State::Idling: { if (_stateTime <= 0.0f) { FollowNearestPlayer(State::Running1, Random().NextFloat(60.0f, 120.0f)); } break; } case State::Running1: { if (_crouchCooldown <= 0.0 && ShouldCrouch()) { Crouch(); } else if (_health < _maxHealth / 2) { _isDemon = true; _speed.X = 0.0f; _speed.Y = 0.0f; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen, false); std::shared_ptr gun = std::make_shared(); std::uint8_t gunParams[1] = { (std::uint8_t)(IsFacingLeft() ? 1 : 0) }; gun->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? 12 : -12), (std::int32_t)_pos.Y - 8, _renderer.layer() + 40), gunParams )); _levelHandler->AddActor(gun); _state = State::Transition; SetTransition(DisarmedStart, false, [this]() { SetAnimation(DemonFly); SetTransition(DemonTransformStart, false, [this]() { SetState(ActorState::ApplyGravitation | ActorState::IsInvulnerable, false); _state = State::DemonFlying; _lastPos = _pos; _targetPos = _lastPos + Vector2f(0.0f, -200.0f); }); }); } else if (_stateTime <= 0.0f) { if (Random().NextFloat() < 0.5f) { FollowNearestPlayer(State::Running1, Random().NextFloat(60, 120)); } else { FollowNearestPlayer(State::Running2, Random().NextFloat(10, 30)); } } else { if (!CanMoveToPosition(_speed.X, 0)) { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -4.0f : 4.0f); } } break; } case State::Running2: { if (_stateTime <= 0.0f) { _speed.X = 0.0f; _state = State::Transition; SetTransition(AnimState::TransitionRunToIdle, false, [this]() { SetTransition(ShootStart, false, [this]() { _shots = Random().Next(1, 8); Shoot(); }); }); } break; } case State::Crouch: { if (_stateTime <= 0.0f) { _state = State::Transition; switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Easy: _crouchCooldown = Random().NextFloat(360.0f, 600.0f); break; default: case GameDifficulty::Normal: _crouchCooldown = Random().NextFloat(180.0f, 360.0f); break; case GameDifficulty::Hard: _crouchCooldown = Random().NextFloat(100.0f, 240.0f); break; } SetState(ActorState::SkipPerPixelCollisions, false); SetTransition(CrouchEnd, false, [this]() { FollowNearestPlayer(State::Running1, Random().NextFloat(60.0f, 150.0f)); }); } break; } case State::DemonFlying: { if (_attackTime <= 0.0f) { _state = State::DemonSpewingFireball; } else { _attackTime -= timeMult; FollowNearestPlayerDemon(timeMult); } break; } case State::DemonSpewingFireball: { _state = State::Transition; SetTransition(DemonSpewFireball, false, [this]() { PlaySfx("SpitFireball"_s); std::shared_ptr fireball = std::make_shared(); std::uint8_t fireballParams[1] = { (std::uint8_t)(IsFacingLeft() ? 1 : 0) }; fireball->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -26 : 26), (std::int32_t)_pos.Y - 14, _renderer.layer() + 2), fireballParams )); _levelHandler->AddActor(fireball); SetTransition(DemonSpewFireballEnd, false, [this]() { _state = State::DemonFlying; _attackTime = Random().NextFloat(100.0f, 240.0f); }); }); break; } case State::Falling: { if (GetState(ActorState::CanJump)) { _state = State::Transition; SetTransition(DisorientedStart, false, [this]() { SetTransition(Disoriented, false, [this]() { SetTransition(Disoriented, false, [this]() { SetTransition(DisorientedWarpOut, false, [this]() { BossBase::OnPerish(nullptr); }); }); }); }); } break; } } _stateTime -= timeMult; _crouchCooldown -= timeMult; } void Devan::OnUpdateHitbox() { BossBase::OnUpdateHitbox(); if (_state == State::Crouch) { // Smaller hitbox when Devan is crouching AABBInner.T = AABBInner.B - 16.0f; } } bool Devan::OnPerish(ActorBase* collider) { if (_isDead) { return false; } StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); _isDead = true; _speed.X = 0.0f; _speed.Y = 0.0f; SetState(ActorState::ApplyGravitation, false); _state = State::Transition; SetTransition(DemonTransformEnd, false, [this]() { SetState(ActorState::ApplyGravitation, true); _isDemon = false; _state = State::Falling; SetAnimation(AnimState::Freefall); }); return false; } void Devan::FollowNearestPlayer(State newState, float time) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } if (found) { _state = newState; _stateTime = time; SetFacingLeft(targetPos.X < _pos.X); _speed.X = (IsFacingLeft() ? -4.0f : 4.0f); SetAnimation(AnimState::Run); } } void Devan::FollowNearestPlayerDemon(float timeMult) { bool found = false; Vector2f foundPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - foundPos).Length()) { foundPos = newPos; found = true; } } if (found) { bool willFaceLeft = (foundPos.X < _pos.X); float xOffset, yOffset, speedMult; if (_levelHandler->GetDifficulty() == GameDifficulty::Easy) { xOffset = 100.0f; yOffset = 70.0f; speedMult = 0.01f; } else { xOffset = 70.0f; yOffset = 30.0f; speedMult = 0.005f; } _targetPos = foundPos; _targetPos.X += (willFaceLeft ? xOffset : -xOffset); _targetPos.Y -= yOffset; Vector2f speed = ((_targetPos - _lastPos) * speedMult + _lastSpeed * 1.4f) / 2.4f; _lastPos.X += speed.X; _lastPos.Y += speed.Y; _lastSpeed = speed; if (IsFacingLeft() != willFaceLeft) { SetFacingLeft(willFaceLeft); SetTransition(DemonTurn, false); } } _anglePhase += timeMult * 0.02f; MoveInstantly(_lastPos + Vector2f(0.0f, sinf(_anglePhase) * 22.0f), MoveType::Absolute | MoveType::Force); } void Devan::Shoot() { PlaySfx("Shoot"_s); SetTransition(ShootInProgress, false, [this]() { std::shared_ptr bullet = std::make_shared(); std::uint8_t fireballParams[1] = { (std::uint8_t)(IsFacingLeft() ? 1 : 0) }; bullet->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -24 : 24), (std::int32_t)_pos.Y + 2, _renderer.layer() + 2), fireballParams )); _levelHandler->AddActor(bullet); _shots--; SetTransition(ShootEnd, false, [this]() { if (_shots > 0) { Shoot(); } else { Run(); } }); }); } void Devan::Run() { SetTransition(ShootEnd2, false, [this]() { FollowNearestPlayer(State::Running1, Random().NextFloat(60.0f, 150.0f)); }); } void Devan::Crouch() { _speed.X = 0.0f; _state = State::Crouch; switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Easy: _stateTime = Random().NextFloat(100.0f, 240.0f); break; default: case GameDifficulty::Normal: _stateTime = Random().NextFloat(60.0f, 120.0f); break; case GameDifficulty::Hard: _stateTime = Random().NextFloat(30.0f, 80.0f); break; } SetState(ActorState::SkipPerPixelCollisions, true); SetAnimation(CrouchInProgress); SetTransition(CrouchStart, false); } bool Devan::ShouldCrouch() const { constexpr float Distance = 64.0f; bool shouldCrouch = false; AABBf crouchAabb = AABB; crouchAabb.L -= Distance; crouchAabb.R += Distance; _levelHandler->FindCollisionActorsByAABB(this, crouchAabb, [this, &shouldCrouch](ActorBase* actor) { if (auto* shot = runtime_cast(actor)) { float xSpeed = shot->GetSpeed().X; float x = shot->GetPos().X; float xSelf = _pos.X; // Check if the shot is moving towards the boss if (std::abs(xSpeed) > 0.0f && std::signbit(xSelf - x) == std::signbit(xSpeed)) { shouldCrouch = true; return false; } } return true; }); return shouldCrouch; } Devan::DisarmedGun::DisarmedGun() { } Task Devan::DisarmedGun::OnActivatedAsync(const ActorActivationDetails& details) { SetFacingLeft(details.Params[0] != 0); _speed.X = (IsFacingLeft() ? 3.5f : -3.5f); _speed.Y = -2.0f; SetState(ActorState::IsInvulnerable | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithOtherActors | ActorState::CollideWithSolidObjects | ActorState::CollideWithTileset, false); _health = INT32_MAX; _timeLeft = 10.0f * FrameTimer::FramesPerSecond; async_await RequestMetadataAsync("Boss/Devan"_s); SetAnimation(DisarmedGunDecor); async_return true; } void Devan::DisarmedGun::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); _timeLeft -= timeMult; if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } } void Devan::DisarmedGun::OnUpdateHitbox() { } Devan::Bullet::Bullet() { } Task Devan::Bullet::OnActivatedAsync(const ActorActivationDetails& details) { SetFacingLeft(details.Params[0] != 0); _speed.X = (IsFacingLeft() ? -8.0f : 8.0f); SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); CanCollideWithShots = false; _health = INT32_MAX; async_await RequestMetadataAsync("Boss/Devan"_s); SetAnimation(DevanBullet); async_return true; } void Devan::Bullet::OnUpdateHitbox() { UpdateHitbox(6, 6); } void Devan::Bullet::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.Brightness = 0.8f; light.RadiusNear = 0.0f; light.RadiusFar = 28.0f; } bool Devan::Bullet::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Small); return EnemyBase::OnPerish(collider); } void Devan::Bullet::OnHitFloor(float timeMult) { PlaySfx("WallPoof"_s); DecreaseHealth(INT32_MAX); } void Devan::Bullet::OnHitWall(float timeMult) { PlaySfx("WallPoof"_s); DecreaseHealth(INT32_MAX); } void Devan::Bullet::OnHitCeiling(float timeMult) { PlaySfx("WallPoof"_s); DecreaseHealth(INT32_MAX); } Devan::Fireball::Fireball() { } Task Devan::Fireball::OnActivatedAsync(const ActorActivationDetails& details) { SetFacingLeft(details.Params[0] != 0); _speed.X = (IsFacingLeft() ? -5.0f : 5.0f); _speed.Y = 3.5f; _timeLeft = 50.0f; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); _health = INT32_MAX; async_await RequestMetadataAsync("Boss/Devan"_s); SetAnimation(DemonFireball); async_return true; } void Devan::Fireball::OnUpdateHitbox() { UpdateHitbox(6, 6); } void Devan::Fireball::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.85f; light.Brightness = 0.4f; light.RadiusNear = 0.0f; light.RadiusFar = 30.0f; } bool Devan::Fireball::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::SmallDark); PlaySfx("Flap"_s); return EnemyBase::OnPerish(collider); } void Devan::Fireball::OnHitFloor(float timeMult) { DecreaseHealth(INT32_MAX); } void Devan::Fireball::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void Devan::Fireball::OnHitCeiling(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Devan.h000066400000000000000000000046041512772601700270230ustar00rootroot00000000000000#pragma once #include "BossBase.h" namespace Jazz2::Actors::Bosses { /** @brief Devan (boss) */ class Devan : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: Devan(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: enum class State { Transition = -1, Waiting = 0, WarpingIn, Idling, Running1, Running2, Crouch, DemonFlying, DemonSpewingFireball, Falling }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class DisarmedGun : public ActorBase { public: DisarmedGun(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; private: float _timeLeft; }; class Bullet : public EnemyBase { public: Bullet(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitCeiling(float timeMult) override; }; class Fireball : public EnemyBase { public: Fireball(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitCeiling(float timeMult) override; private: float _timeLeft; }; #endif State _state; float _stateTime; float _attackTime; float _anglePhase; float _crouchCooldown; std::int32_t _shots; bool _isDemon, _isDead; Vector2f _lastPos, _targetPos, _lastSpeed; std::uint8_t _endText; void FollowNearestPlayer(State newState, float time); void FollowNearestPlayerDemon(float timeMult); void Shoot(); void Run(); void Crouch(); bool ShouldCrouch() const; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/DevanRemote.cpp000066400000000000000000000040371512772601700305320ustar00rootroot00000000000000#include "DevanRemote.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { DevanRemote::DevanRemote() : _introText(0), _endText(0) { } DevanRemote::~DevanRemote() { if (_robot != nullptr) { _robot->Deactivate(); _robot = nullptr; } } void DevanRemote::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/DevanRemote"_s); } Task DevanRemote::OnActivatedAsync(const ActorActivationDetails& details) { _introText = details.Params[1]; _endText = details.Params[2]; SetState(ActorState::CollideWithOtherActors | ActorState::CanBeFrozen, false); async_await RequestMetadataAsync("Boss/DevanRemote"_s); SetAnimation(AnimState::Idle); SetFacingLeft(true); async_return true; } bool DevanRemote::OnActivatedBoss() { StringView text = _levelHandler->GetLevelText(_introText); _levelHandler->ShowLevelText(text); auto actors = _levelHandler->GetActors(); for (auto& actor : actors) { if (auto robot = runtime_cast(actor)) { _robot = std::move(robot); _robot->Activate(); // Copy health to Devan to enable HealthBar _health = _robot->GetHealth(); _maxHealth = _robot->GetMaxHealth(); break; } } return true; } bool DevanRemote::OnPlayerDied() { if (_robot != nullptr) { _robot->Deactivate(); _robot = nullptr; } return BossBase::OnPlayerDied(); } void DevanRemote::OnUpdate(float timeMult) { BossBase::OnUpdate(timeMult); if (_robot != nullptr) { int robotHealth = _robot->GetHealth(); if (robotHealth <= 0) { _robot = nullptr; _health = 1; StringView text = _levelHandler->GetLevelText(_endText, -1, '|'); _levelHandler->ShowLevelText(text); PlaySfx("WarpOut"_s); SetTransition(AnimState::TransitionWarpOut, false, [this]() { _renderer.setDrawEnabled(false); DecreaseHealth(INT32_MAX); }); } else { _health = robotHealth; } } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/DevanRemote.h000066400000000000000000000011421512772601700301710ustar00rootroot00000000000000#pragma once #include "BossBase.h" #include "Robot.h" namespace Jazz2::Actors::Bosses { /** @brief Devan with remote control (boss) */ class DevanRemote : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: DevanRemote(); ~DevanRemote(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; bool OnPlayerDied() override; void OnUpdate(float timeMult) override; private: std::uint8_t _introText, _endText; std::shared_ptr _robot; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Queen.cpp000066400000000000000000000207401512772601700273750ustar00rootroot00000000000000#include "Queen.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Environment/Spring.h" #include "../../../../nCine/Base/Random.h" #include using namespace Jazz2::Tiles; namespace Jazz2::Actors::Bosses { Queen::Queen() : _state(StateWaiting), _stateTime(0.0f), _endText(0), _queuedBackstep(false) { } Queen::~Queen() { if (_block != nullptr) { _block->SetState(ActorState::IsDestroyed, true); _block = nullptr; } } void Queen::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/Queen"_s); } Task Queen::OnActivatedAsync(const ActorActivationDetails& details) { _endText = details.Params[1]; _pos.Y -= 8.0f; _originPos = _pos; _canHurtPlayer = false; SetState(ActorState::IsInvulnerable | ActorState::IsSolidObject | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen, false); _health = INT32_MAX; _maxHealth = INT32_MAX; _scoreValue = 0; _lastHealth = _health; _stepSize = 0.3f; switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Easy: _stepSize *= 1.3f; break; case GameDifficulty::Hard: _stepSize *= 0.7f; break; } async_await RequestMetadataAsync("Boss/Queen"_s); SetAnimation(AnimState::Idle); // Invisible block above the queen _block = std::make_shared(); _block->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 1) )); _levelHandler->AddActor(_block); async_return true; } bool Queen::OnActivatedBoss() { MoveInstantly(_originPos, MoveType::Absolute | MoveType::Force); _state = StateWaiting; return true; } void Queen::OnUpdate(float timeMult) { TileCollisionParams params = { TileDestructType::Weapon | TileDestructType::Speed, _speed.Y >= 0.0f, WeaponType::Blaster, 1 }; TryStandardMovement(timeMult, params); OnUpdateHitbox(); if (params.TilesDestroyed > 0) { _levelHandler->ShakeCameraViewNear(_pos, 20.0f); } HandleBlinking(timeMult); if (_block != nullptr) { _block->MoveInstantly(Vector2f(_pos.X, _pos.Y - 32.0f), MoveType::Absolute | MoveType::Force); } switch (_state) { case StateWaiting: { // Waiting for player to enter the arena _levelHandler->FindCollisionActorsByAABB(this, AABBf(_pos.X - 300, _pos.Y - 120, _pos.X + 60, _pos.Y + 120), [this](ActorBase* actor) { if (auto* player = runtime_cast(actor)) { _state = StateIdleToScream; _stateTime = 260.0f; return false; } return true; }); break; } case StateIdleToScream: { // Scream towards the player if (_stateTime <= 0.0f) { _lastHealth = _health; SetState(ActorState::IsInvulnerable, false); _state = StateScreaming; PlaySfx("Scream"_s); SetTransition((AnimState)1073741824, false, [this]() { _state = (Random().NextFloat() < 0.8f ? StateIdleToStomp : StateIdleToBackstep); _stateTime = Random().NextFloat(65.0f, 85.0f); SetState(ActorState::IsInvulnerable, true); if (_lastHealth - _health >= 2) { _queuedBackstep = true; } }); } break; } case StateIdleToStomp: { if (_stateTime <= 0.0f) { _state = StateTransition; SetTransition((AnimState)1073741825, false, [this]() { PlaySfx("Stomp"_s); SetTransition((AnimState)1073741830, false, [this]() { _state = StateIdleToBackstep; _stateTime = Random().NextFloat(70.0f, 80.0f); auto players = _levelHandler->GetPlayers(); auto* player = players[Random().Next(0, (std::uint32_t)players.size())]; std::shared_ptr brick = std::make_shared(); brick->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)(player->GetPos().X + Random().NextFloat(-50.0f, 50.0f)), (std::int32_t)(_pos.Y - 200.0f), _renderer.layer() - 20) )); _levelHandler->AddActor(std::move(brick)); _levelHandler->ShakeCameraViewNear(_pos, 20.0f); }); }); } break; } case StateIdleToBackstep: { if (_stateTime <= 0.0f) { if (_queuedBackstep) { _queuedBackstep = false; // 2 hits by player while screaming, step backwards _speed.X = _stepSize; _state = StateTransition; SetTransition((AnimState)1073741826, false, [this]() { _speed.X = 0.0f; // Check it it's on the ledge AABBf aabb1 = AABBf(_pos.X - 10, _pos.Y + 24, _pos.X - 6, _pos.Y + 28); AABBf aabb2 = AABBf(_pos.X + 6, _pos.Y + 24, _pos.X + 10, _pos.Y + 28); TileCollisionParams params = { TileDestructType::None, true }; if (!_levelHandler->IsPositionEmpty(this, aabb1, params) && _levelHandler->IsPositionEmpty(this, aabb2, params)) { _lastHealth = _health; SetState(ActorState::IsInvulnerable, false); // It's on the ledge _state = StateTransition; SetTransition((AnimState)1073741827, false, [this]() { SetState(ActorState::IsInvulnerable, true); // 1 hits by player if (_lastHealth - _health >= 1) { // Fall off the ledge _speed.X = 2.2f; SetAnimation(AnimState::Fall); } else { // Recover, step forward SetTransition((AnimState)1073741828, false, [this]() { _speed.X = _stepSize * -1.3f; SetTransition((AnimState)1073741826, false, [this]() { _speed.X = 0.0f; _state = StateIdleToScream; _stateTime = Random().NextFloat(150.0f, 180.0f); }); }); } }); } else { _state = StateIdleToScream; _stateTime = Random().NextFloat(160.0f, 200.0f); } }); } else { _state = StateIdleToScream; _stateTime = Random().NextFloat(150.0f, 180.0f); } } break; } case StateDead: { // Thrown away by spring if (_stateTime <= 0.0f) { DecreaseHealth(INT32_MAX); } break; } case StateScreaming: { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } //player->AddExternalForce(-1.5f * timeMult, 0.0f); player->MoveInstantly(Vector2f(-1.5f * timeMult, 0.0f), MoveType::Relative); } break; } } _stateTime -= timeMult; } bool Queen::OnHandleCollision(std::shared_ptr other) { if (auto* spring = runtime_cast(other.get())) { // Collide only with hitbox if (AABBInner.Overlaps(spring->AABBInner)) { Vector2f force = spring->Activate(); int sign = ((force.X + force.Y) > 0.0f ? 1 : -1); if (std::abs(force.X) > 0.0f) { _speed.X = (4 + std::abs(force.X)) * sign; _externalForce.X = force.X; } else if (std::abs(force.Y) > 0.0f) { _speed.Y = (4.0f + std::abs(force.Y)) * sign; _externalForce.Y = force.Y; } else { return BossBase::OnHandleCollision(std::move(other)); } SetState(ActorState::CanJump, false); SetAnimation(AnimState::Fall); PlaySfx("Spring"_s); if (_state != StateDead) { StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); _state = StateDead; _stateTime = 50.0f; } } } return BossBase::OnHandleCollision(std::move(other)); } Task Queen::Brick::OnActivatedAsync(const ActorActivationDetails& details) { _timeLeft = 50.0f; SetState(ActorState::IsInvulnerable | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CollideWithTileset, false); async_await RequestMetadataAsync("Boss/Queen"_s); SetAnimation((AnimState)1073741829); PlaySfx("BrickFalling"_s, 0.3f); async_return true; } void Queen::Brick::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _timeLeft -= timeMult; } } bool Queen::Brick::OnPerish(ActorBase* collider) { if (collider != nullptr) { CreateParticleDebrisOnPerish(collider); } return EnemyBase::OnPerish(collider); } Task Queen::InvisibleBlock::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::IsSolidObject | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CollideWithTileset, false); _health = INT32_MAX; async_await RequestMetadataAsync("Boss/Queen"_s); SetAnimation(AnimState::Idle); _renderer.setDrawEnabled(false); async_return true; } void Queen::InvisibleBlock::OnUpdate(float timeMult) { // Nothing to update } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Queen.h000066400000000000000000000032431512772601700270410ustar00rootroot00000000000000#pragma once #include "BossBase.h" namespace Jazz2::Actors::Bosses { /** @brief Queen (boss) */ class Queen : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: Queen(); ~Queen(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; private: static constexpr std::int32_t StateTransition = -1; static constexpr std::int32_t StateWaiting = 0; static constexpr std::int32_t StateIdleToScream = 1; static constexpr std::int32_t StateIdleToStomp = 2; static constexpr std::int32_t StateIdleToBackstep = 3; static constexpr std::int32_t StateDead = 4; static constexpr std::int32_t StateScreaming = 5; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Brick : public EnemyBase { protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: float _timeLeft; }; class InvisibleBlock : public ActorBase { friend class Queen; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; }; #endif std::int32_t _state = StateWaiting; float _stateTime; std::uint8_t _endText; std::int32_t _lastHealth; bool _queuedBackstep; float _stepSize; Vector2f _originPos; std::shared_ptr _block; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Robot.cpp000066400000000000000000000132721512772601700274070ustar00rootroot00000000000000#include "Robot.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { Robot::Robot() : _state(StateWaiting), _stateTime(0.0f), _shots(0) { } void Robot::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/Robot"_s); } Task Robot::OnActivatedAsync(const ActorActivationDetails& details) { _scoreValue = 2000; _originPos = Vector2f(_pos.X, _pos.Y - 300.0f); async_await RequestMetadataAsync("Boss/Robot"_s); SetAnimation(AnimState::Idle); SetFacingLeft(true); _renderer.setDrawEnabled(false); async_return true; } void Robot::Activate() { SetHealthByDifficulty(100); _renderer.setDrawEnabled(true); _state = StateCopter; MoveInstantly(_originPos, MoveType::Absolute); SetAnimation((AnimState)1073741828); } void Robot::Deactivate() { _renderer.setDrawEnabled(false); _state = StateWaiting; } void Robot::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } switch (_state) { case StateCopter: { if (GetState(ActorState::CanJump)) { _speed.Y = 0.0f; _state = StateTransition; SetTransition((AnimState)1073741829, false, [this]() { FollowNearestPlayer(StateRunning1, Random().NextFloat(20, 40)); }); } else { _speed.Y -= (_levelHandler->IsReforged() ? 0.27f : 0.21f) * timeMult; } break; } case StateRunning1: { if (_stateTime <= 0.0f) { FollowNearestPlayer( Random().NextFloat() < 0.65f ? StateRunning1 : StateRunning2, Random().NextFloat(10, 30)); } break; } case StateRunning2: { if (_stateTime <= 0.0f) { _speed.X = 0.0f; _state = StateTransition; PlaySfx("AttackStart"_s); SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741824, false, [this]() { _shots = Random().Next(1, 4); Shoot(); }); } break; } case StatePreparingToRun: { if (_stateTime <= 0.0f) { FollowNearestPlayer(StateRunning1, Random().NextFloat(10, 30)); } break; } } _stateTime -= timeMult; } void Robot::OnHealthChanged(ActorBase* collider) { EnemyBase::OnHealthChanged(collider); std::int32_t n = Random().Next(1, 4); for (std::int32_t i = 0; i < n; i++) { CreateSpriteDebris((AnimState)Random().Fast(100, 109), 1); } PlaySfx("Shrapnel"_s); } bool Robot::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); for (std::int32_t i = 0; i < 8; i++) { CreateSpriteDebris((AnimState)Random().Fast(100, 109), 1); } _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); return EnemyBase::OnPerish(collider); } void Robot::FollowNearestPlayer(std::int32_t newState, float time) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { _state = newState; _stateTime = time; SetFacingLeft(targetPos.X < _pos.X); float mult = Random().NextFloat(0.6f, 0.9f); _speed.X = (IsFacingLeft() ? -3.0f : 3.0f) * mult; _renderer.AnimDuration = _currentAnimation->AnimDuration / mult; PlaySfx("Run"_s); SetAnimation(AnimState::Run); } } void Robot::Shoot() { if (_state == StateWaiting) { return; } std::shared_ptr spikeBall = std::make_shared(); uint8_t spikeBallParams[1] = { (uint8_t)(IsFacingLeft() ? 1 : 0) }; spikeBall->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y - 32, _renderer.layer() + 2), spikeBallParams )); _levelHandler->AddActor(spikeBall); _shots--; PlaySfx("Attack"_s); SetTransition((AnimState)1073741825, false, [this]() { if (_shots > 0) { PlaySfx("AttackShutter"_s); Shoot(); } else { Run(); } }); } void Robot::Run() { if (_state == StateWaiting) { return; } PlaySfx("AttackEnd"_s); SetTransition((AnimState)1073741826, false, [this]() { _state = StatePreparingToRun; _stateTime = 10.0f; }); } Task Robot::SpikeBall::OnActivatedAsync(const ActorActivationDetails& details) { SetFacingLeft(details.Params[0] != 0); SetState(ActorState::IsInvulnerable | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen, false); CanCollideWithShots = false; _health = INT32_MAX; _speed.X = (IsFacingLeft() ? -8.0f : 8.0f); async_await RequestMetadataAsync("Boss/Robot"_s); SetAnimation((AnimState)1073741827); async_return true; } void Robot::SpikeBall::OnUpdateHitbox() { UpdateHitbox(8, 8); } void Robot::SpikeBall::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.1f; light.Brightness = 0.8f; light.RadiusNear = 0.0f; light.RadiusFar = 22.0f; } bool Robot::SpikeBall::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::SmallDark); return EnemyBase::OnPerish(collider); } void Robot::SpikeBall::OnHitFloor(float timeMult) { DecreaseHealth(INT32_MAX); } void Robot::SpikeBall::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void Robot::SpikeBall::OnHitCeiling(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Robot.h000066400000000000000000000031261512772601700270510ustar00rootroot00000000000000#pragma once #include "../EnemyBase.h" namespace Jazz2::Actors::Bosses { /** @brief Robot (boss) */ class Robot : public Enemies::EnemyBase { DEATH_RUNTIME_OBJECT(Enemies::EnemyBase); public: Robot(); static void Preload(const ActorActivationDetails& details); void Activate(); void Deactivate(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnHealthChanged(ActorBase* collider) override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateTransition = -1; static constexpr std::int32_t StateWaiting = 0; static constexpr std::int32_t StateCopter = 1; static constexpr std::int32_t StateRunning1 = 2; static constexpr std::int32_t StateRunning2 = 3; static constexpr std::int32_t StatePreparingToRun = 4; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class SpikeBall : public Enemies::EnemyBase { protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitCeiling(float timeMult) override; }; #endif std::int32_t _state; float _stateTime; std::int32_t _shots; Vector2f _originPos; void FollowNearestPlayer(std::int32_t newState, float time); void Shoot(); void Run(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/TurtleBoss.cpp000066400000000000000000000160441512772601700304300ustar00rootroot00000000000000#include "TurtleBoss.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../TurtleShell.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { TurtleBoss::TurtleBoss() : _state(StateWaiting), _stateTime(0.0f), _endText(0), _maceTime(0.0f) { } TurtleBoss::~TurtleBoss() { if (_mace != nullptr) { _mace->DecreaseHealth(INT32_MAX); _mace = nullptr; } } void TurtleBoss::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/TurtleBoss"_s); PreloadMetadataAsync("Boss/TurtleBossShell"_s); } Task TurtleBoss::OnActivatedAsync(const ActorActivationDetails& details) { _endText = details.Params[1]; _originPos = _pos; _scoreValue = 5000; async_await RequestMetadataAsync("Boss/TurtleBoss"_s); SetAnimation(AnimState::Idle); SetFacingLeft(true); async_return true; } bool TurtleBoss::OnActivatedBoss() { SetHealthByDifficulty(100); MoveInstantly(_originPos, MoveType::Absolute | MoveType::Force); FollowNearestPlayer(StateWalking1, Random().NextFloat(120.0f, 160.0f)); return true; } void TurtleBoss::OnUpdate(float timeMult) { BossBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } switch (_state) { case StateWalking1: { if (_stateTime <= 0.0f) { FollowNearestPlayer(StateWalking2, 16); } else if (!CanMoveToPosition(_speed.X, 0)) { SetFacingLeft(!IsFacingLeft()); _speed.X = -_speed.X; } break; } case StateWalking2: { if (_stateTime <= 0.0f) { _speed.X = 0.0f; PlaySfx("AttackStart"_s); _state = StateTransition; SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741824, false, [this]() { _mace = std::make_shared(); _mace->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2) )); _levelHandler->AddActor(_mace); SetTransition((AnimState)1073741825, false, [this]() { _state = StateAttacking; _stateTime = 10.0f; _maceTime = 480.0f; }); }); } else if (!CanMoveToPosition(_speed.X, 0)) { _speed.X = 0; SetAnimation(AnimState::Idle); } break; } case StateAttacking: { _maceTime -= timeMult; if (_maceTime <= 0.0f && _mace != nullptr) { _mace->DecreaseHealth(INT32_MAX); _mace = nullptr; SetTransition((AnimState)1073741826, false, [this]() { FollowNearestPlayer(StateWalking1, Random().NextFloat(80.0f, 160.0f)); }); } break; } } _stateTime -= timeMult; } bool TurtleBoss::OnHandleCollision(std::shared_ptr other) { if (_state == StateAttacking && _stateTime <= 0.0f) { if (auto* mace = runtime_cast(other.get())) { if (mace == _mace.get()) { _mace->DecreaseHealth(INT32_MAX); _mace = nullptr; PlaySfx("AttackEnd"_s); SetTransition((AnimState)1073741826, false, [this]() { FollowNearestPlayer(StateWalking1, Random().NextFloat(80.0f, 160.0f)); }); return true; } } } return EnemyBase::OnHandleCollision(std::move(other)); } bool TurtleBoss::OnPerish(ActorBase* collider) { float shellSpeedY; if (_pos.Y > _levelHandler->GetWaterLevel()) { shellSpeedY = -0.65f; } else if (_levelHandler->IsReforged()) { shellSpeedY = -1.1f; } else { shellSpeedY = -0.98f; } std::shared_ptr shell = std::make_shared(); uint8_t shellParams[9]; *(float*)&shellParams[0] = _speed.X * 1.1f; *(float*)&shellParams[4] = shellSpeedY; shellParams[8] = 2; shell->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), shellParams )); _levelHandler->AddActor(shell); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::SmokeGray); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); return BossBase::OnPerish(collider); } void TurtleBoss::FollowNearestPlayer(std::int32_t newState, float time) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { _state = newState; _stateTime = time; SetFacingLeft(targetPos.X < _pos.X); _speed.X = (IsFacingLeft() ? -1.6f : 1.6f); SetAnimation(AnimState::Walk); } } TurtleBoss::Mace::~Mace() { #if defined(WITH_AUDIO) if (_sound != nullptr) { _sound->stop(); _sound = nullptr; } #endif } Task TurtleBoss::Mace::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); CanCollideWithShots = false; _health = INT32_MAX; async_await RequestMetadataAsync("Boss/TurtleBoss"_s); SetAnimation((AnimState)1073741827); _originPos = _pos; FollowNearestPlayer(); #if defined(WITH_AUDIO) _sound = PlaySfx("Mace"_s, 0.7f); if (_sound != nullptr) { _sound->setLooping(true); } #endif async_return true; } void TurtleBoss::Mace::OnUpdate(float timeMult) { MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); #if defined(WITH_AUDIO) if (_sound != nullptr) { _sound->setPosition(Vector3f(_pos.X, _pos.Y, 0.8f)); } #endif if (_returning) { Vector2f diff = (_targetSpeed - _speed); if (diff.SqrLength() > 1.0f) { _speed.X += diff.X * 0.04f; _speed.Y += diff.Y * 0.04f; } } else { if (_returnTime > 0.0f) { _returnTime -= timeMult; } else { _returning = true; _targetSpeed = (_originPos - _pos) / (TotalTime / 2); } } } void TurtleBoss::Mace::OnUpdateHitbox() { UpdateHitbox(18, 18); } void TurtleBoss::Mace::FollowNearestPlayer() { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { SetFacingLeft(targetPos.X < _originPos.X); _returnTime = (TotalTime / 2); Vector2f diff = (targetPos - _originPos); float length = diff.Length(); if (length > 500.0f) { diff = diff * 500.0f / length; } _speed.X = (diff.X / _returnTime); _speed.Y = (diff.Y / _returnTime); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/TurtleBoss.h000066400000000000000000000032721512772601700300740ustar00rootroot00000000000000#pragma once #include "BossBase.h" namespace Jazz2::Actors::Bosses { /** @brief Turtle (boss) */ class TurtleBoss : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: TurtleBoss(); ~TurtleBoss(); static void Preload(const ActorActivationDetails& details); bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateTransition = -1; static constexpr std::int32_t StateWaiting = 0; static constexpr std::int32_t StateWalking1 = 1; static constexpr std::int32_t StateWalking2 = 2; static constexpr std::int32_t StateAttacking = 3; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Mace : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: ~Mace(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; private: static constexpr float TotalTime = 60.0f; Vector2f _originPos; Vector2f _targetPos; bool _returning; float _returnTime; Vector2f _targetSpeed; #if defined(WITH_AUDIO) std::shared_ptr _sound; #endif void FollowNearestPlayer(); }; #endif std::int32_t _state; float _stateTime; std::uint8_t _endText; Vector2f _originPos; std::shared_ptr _mace; float _maceTime; void FollowNearestPlayer(std::int32_t newState, float time); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Uterus.cpp000066400000000000000000000171071512772601700276120ustar00rootroot00000000000000#include "Uterus.h" #include "../../../ILevelHandler.h" #include "../../Player.h" #include "../../Explosion.h" #include "../../Weapons/ShotBase.h" #include "../../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Bosses { Uterus::Uterus() : _state(StateWaiting), _stateTime(0.0f), _endText(0), _anglePhase(0.0f), _hasShield(false) { } Uterus::~Uterus() { for (std::int32_t i = 0; i < std::int32_t(arraySize(_shields)); i++) { if (_shields[i] != nullptr) { _shields[i]->SetState(ActorState::IsDestroyed, true); } } } void Uterus::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Boss/Uterus"_s); PreloadMetadataAsync("Enemy/Crab"_s); } Task Uterus::OnActivatedAsync(const ActorActivationDetails& details) { _endText = details.Params[1]; _originPos = _pos; _lastPos = _pos; SetState(ActorState::SkipPerPixelCollisions | ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); _scoreValue = 3000; async_await RequestMetadataAsync("Boss/Uterus"_s); SetAnimation(AnimState::Idle); _renderer.setDrawEnabled(false); async_return true; } bool Uterus::OnActivatedBoss() { SetHealthByDifficulty(50); _renderer.setDrawEnabled(true); MoveInstantly(_originPos, MoveType::Absolute | MoveType::Force); _state = StateOpen; _stateTime = 240.0f; _spawnCrabTime = 500.0f; _hasShield = true; for (std::int32_t i = 0; i < std::int32_t(arraySize(_shields)); i++) { _shields[i] = std::make_shared(); _shields[i]->Phase = (fTwoPi * i / std::int32_t(arraySize(_shields))); _shields[i]->OnActivated(ActorActivationDetails( _levelHandler, Vector3i(std::int32_t(_pos.X), std::int32_t(_pos.Y), _renderer.layer() + 2) )); _levelHandler->AddActor(_shields[i]); } return true; } void Uterus::OnUpdate(float timeMult) { OnUpdateHitbox(); HandleBlinking(timeMult); switch (_state) { case StateOpen: { if (_stateTime <= 0.0f) { PlaySfx("Closing"_s); _state = StateTransition; SetAnimation((AnimState)1073741825); SetTransition((AnimState)1073741824, false, [this]() { _state = StateClosed; _stateTime = 280.0f; SetState(ActorState::IsInvulnerable, true); }); } if (_spawnCrabTime <= 0.0f) { float force = Random().NextFloat(-15.0f, 15.0f); // TODO: Implement Crab spawn animation std::shared_ptr crab = std::make_shared(); crab->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 4) )); crab->AddExternalForce(force, 0.0f); _levelHandler->AddActor(crab); _spawnCrabTime = (_hasShield ? Random().NextFloat(160.0f, 220.0f) : Random().NextFloat(120.0f, 200.0f)); } else { _spawnCrabTime -= timeMult; } break; } case StateClosed: { if (_stateTime <= 0.0f) { PlaySfx("Opening"_s); _state = StateTransition; SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false, [this]() { _state = StateOpen; _stateTime = 280.0f; SetState(ActorState::IsInvulnerable, _hasShield); }); } break; } } _stateTime -= timeMult; if (_state != StateWaiting) { FollowNearestPlayer(timeMult); _anglePhase += timeMult * 0.02f; _renderer.setRotation(fPiOver2 + sinf(_anglePhase) * 0.2f); Vector2f pos = _lastPos + Vector2f(cosf(_anglePhase) * 60.0f, sinf(_anglePhase) * 60.0f); MoveInstantly(pos, MoveType::Absolute | MoveType::Force); if (_hasShield) { std::int32_t shieldCount = 0; for (std::int32_t i = 0; i < std::int32_t(arraySize(_shields)); i++) { if (_shields[i] != nullptr) { if (_shields[i]->GetHealth() > 0) { if (_shields[i]->FallTime <= 0.0f) { _shields[i]->MoveInstantly(pos + Vector2f(cosf(_anglePhase + _shields[i]->Phase) * 50.0f, sinf(_anglePhase + _shields[i]->Phase) * 50.0f), MoveType::Absolute | MoveType::Force); _shields[i]->Recover(_anglePhase + _shields[i]->Phase); } shieldCount++; } else { _shields[i] = nullptr; } } } if (shieldCount == 0) { _hasShield = false; SetState(ActorState::IsInvulnerable, _state != StateOpen); } } } OnUpdateHitbox(); } void Uterus::OnUpdateHitbox() { UpdateHitbox(38, 60); } bool Uterus::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::SmokePoof); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::RF); CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); StringView text = _levelHandler->GetLevelText(_endText); _levelHandler->ShowLevelText(text); return BossBase::OnPerish(collider); } void Uterus::FollowNearestPlayer(float timeMult) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } if (found) { targetPos.Y -= 100.0f; Vector2f diff = (targetPos - _lastPos); if (diff.Length() > 40) { Vector2f speed = (Vector2f(_speed.X, _speed.Y) + diff.Normalized() * 0.4f).Normalized(); float mult = (_hasShield ? 0.8f : 2.0f) * timeMult; switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Easy: mult *= 0.8f; break; case GameDifficulty::Hard: mult *= 1.1f; break; } _lastPos.X += speed.X * mult; _lastPos.Y += speed.Y * mult; } } } Task Uterus::ShieldPart::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::ApplyGravitation, false); CanCollideWithShots = false; _elasticity = 0.3f; _health = 3; async_await RequestMetadataAsync("Boss/Uterus"_s); SetAnimation((AnimState)1073741827); async_return true; } void Uterus::ShieldPart::OnUpdate(float timeMult) { UpdateFrozenState(timeMult); if (FallTime > 0.0f) { EnemyBase::OnUpdate(timeMult); FallTime -= timeMult; } else { HandleBlinking(timeMult); } } void Uterus::ShieldPart::OnUpdateHitbox() { UpdateHitbox(6, 6); } bool Uterus::ShieldPart::OnHandleCollision(std::shared_ptr other) { if (auto* shotBase = runtime_cast(other.get())) { DecreaseHealth(shotBase->GetStrength(), shotBase); FallTime = 400.0f; SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::ApplyGravitation, true); if (GetState(ActorState::CanBeFrozen)) { HandleFrozenStateChange(shotBase); } return true; } return EnemyBase::OnHandleCollision(std::move(other)); } bool Uterus::ShieldPart::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::Tiny); return EnemyBase::OnPerish(collider); } void Uterus::ShieldPart::Recover(float phase) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::ApplyGravitation, false); _renderer.setRotation(phase); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Bosses/Uterus.h000066400000000000000000000031451512772601700272540ustar00rootroot00000000000000#pragma once #include "BossBase.h" #include "../Crab.h" namespace Jazz2::Actors::Bosses { /** @brief Uterus (boss) */ class Uterus : public BossBase { DEATH_RUNTIME_OBJECT(BossBase); public: Uterus(); ~Uterus(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnActivatedBoss() override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateTransition = -1; static constexpr std::int32_t StateWaiting = 0; static constexpr std::int32_t StateOpen = 1; static constexpr std::int32_t StateClosed = 2; static constexpr std::int32_t ShieldPartCount = 6; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class ShieldPart : public EnemyBase { friend class Uterus; public: float Phase; float FallTime; bool OnHandleCollision(std::shared_ptr other) override; void Recover(float phase); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; }; #endif std::int32_t _state; float _stateTime; std::uint8_t _endText; std::shared_ptr _shields[ShieldPartCount]; Vector2f _originPos, _lastPos; float _spawnCrabTime; float _anglePhase; bool _hasShield; void FollowNearestPlayer(float timeMult); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Caterpillar.cpp000066400000000000000000000074471512772601700273350ustar00rootroot00000000000000#include "Caterpillar.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Caterpillar::Caterpillar() : _state(StateIdle), _smokesLeft(0), _attackTime(0.0f) { } void Caterpillar::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Caterpillar"_s); } Task Caterpillar::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CanBeFrozen, false); SetState(ActorState::IsInvulnerable, true); SetFacingLeft(true); _canHurtPlayer = false; _health = INT32_MAX; async_await RequestMetadataAsync("Enemy/Caterpillar"_s); SetAnimation(AnimState::Idle); async_return true; } void Caterpillar::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } switch (_state) { case StateIdle: { if (_currentTransition == nullptr) { // InhaleStart SetTransition((AnimState)1, true, [this]() { // Inhale SetTransition((AnimState)2, true, [this]() { // ExhaleStart SetTransition((AnimState)3, true, [this]() { _state = StateAttacking; _smokesLeft = Random().Next(6, 12); }); }); }); } break; } case StateAttacking: { if (_attackTime <= 0.0f) { _attackTime = 60.0f; SetAnimation((AnimState)5); SetTransition((AnimState)4, true, [this]() { std::shared_ptr smoke = std::make_shared(); smoke->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X - 26, (std::int32_t)_pos.Y - 18, _renderer.layer() + 20) )); _levelHandler->AddActor(smoke); _smokesLeft--; if (_smokesLeft <= 0) { _state = StateIdle; SetAnimation(AnimState::Idle); } }); } else { _attackTime -= timeMult; } break; } } } bool Caterpillar::OnHandleCollision(std::shared_ptr other) { if (auto* shotBase = runtime_cast(other.get())) { if (_state != StateDisoriented) { Disoriented(Random().Next(8, 13)); } } return false; } void Caterpillar::Disoriented(std::int32_t count) { _attackTime = 0.0f; _smokesLeft = 0; _state = StateDisoriented; SetTransition((AnimState)6, false, [this, count]() { if (count > 1) { Disoriented(count - 1); } else { _state = StateIdle; SetAnimation(AnimState::Idle); } }); } Task Caterpillar::Smoke::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); SetState(ActorState::IsInvulnerable | ActorState::SkipPerPixelCollisions, true); CanCollideWithShots = false; _canHurtPlayer = false; _health = INT32_MAX; _baseSpeed.X = Random().NextFloat(-1.4f, -0.8f); _baseSpeed.Y = Random().NextFloat(-1.6f, -0.8f); _time = 500.0f; async_await RequestMetadataAsync("Enemy/Caterpillar"_s); SetAnimation((AnimState)7); async_return true; } void Caterpillar::Smoke::OnUpdate(float timeMult) { _speed.X = _baseSpeed.X + cosf((500.0f - _time) * 0.09f) * 0.5f; _speed.Y = _baseSpeed.Y + sinf((500.0f - _time) * 0.05f) * 0.5f; MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); _renderer.setScale(_renderer.scale() - 0.0011f * timeMult); if (_time <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _time -= timeMult; } } bool Caterpillar::Smoke::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { if (player->SetDizzy(180.0f)) { // TODO: Add fade-out PlaySfx("Dizzy"_s); } } return false; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Caterpillar.h000066400000000000000000000023071512772601700267700ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Caterpillar */ class Caterpillar : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Caterpillar(); static void Preload(const ActorActivationDetails& details); bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: static constexpr std::int32_t StateIdle = 0; static constexpr std::int32_t StateAttacking = 1; static constexpr std::int32_t StateDisoriented = 2; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Smoke : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: float _time; Vector2f _baseSpeed; }; #endif std::int32_t _state; std::int32_t _smokesLeft; float _attackTime; void Disoriented(std::int32_t count); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Crab.cpp000066400000000000000000000044151512772601700257320ustar00rootroot00000000000000#include "Crab.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Crab::Crab() : _noiseCooldown(80.0f), _stepCooldown(8.0f), _canJumpPrev(false), _stuck(false) { } void Crab::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Crab"_s); } Task Crab::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(3); _scoreValue = 300; async_await RequestMetadataAsync("Enemy/Crab"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _canJumpPrev = GetState(ActorState::CanJump); PlaceOnGround(); async_return true; } void Crab::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (GetState(ActorState::CanJump)) { if (!_canJumpPrev) { _canJumpPrev = true; SetAnimation(AnimState::Walk); SetTransition(AnimState::TransitionFallToIdle, false); } if (!CanMoveToPosition(_speed.X * 4, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; _stuck = true; } } else { _stuck = false; } if (_noiseCooldown <= 0.0f) { _noiseCooldown = Random().NextFloat(60, 160); PlaySfx("Noise"_s, 0.3f); } else { _noiseCooldown -= timeMult; } if (_stepCooldown <= 0.0f) { _stepCooldown = Random().NextFloat(7, 10); PlaySfx("Step"_s, 0.08f); } else { _stepCooldown -= timeMult; } } else { if (_canJumpPrev) { _canJumpPrev = false; SetAnimation(AnimState::Fall); } } } void Crab::OnUpdateHitbox() { UpdateHitbox(26, 20); } bool Crab::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::Large); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Crab.h000066400000000000000000000011411512772601700253700ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Crab */ class Crab : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Crab(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 0.7f; float _noiseCooldown; float _stepCooldown; bool _canJumpPrev; bool _stuck; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Demon.cpp000066400000000000000000000053671512772601700261340ustar00rootroot00000000000000#include "Demon.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Demon::Demon() : _attackTime(80.0f), _attacking(false), _stuck(false), _turnCooldown(0.0f) { } void Demon::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Demon"_s); } Task Demon::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(2); _scoreValue = 100; SetState(ActorState::CollideWithTilesetReduced, true); async_await RequestMetadataAsync("Enemy/Demon"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); PlaceOnGround(); async_return true; } void Demon::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_currentTransition == nullptr) { if (_attacking) { if (_attackTime <= 0.0f) { _attacking = false; _attackTime = Random().NextFloat(60.0f, 90.0f); _speed.X = 0.0f; SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false); } else if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4.0f, 0.0f)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else if (_turnCooldown <= 0.0f) { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.8f : 1.8f); _turnCooldown = 90.0f; _stuck = true; } else { _attacking = false; _attackTime = 90.0f - _turnCooldown; _turnCooldown = 0.0f; _speed.X = 0.0f; SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false); } } else { _stuck = false; } _attackTime -= timeMult; } } else { if (_attackTime <= 0.0f) { for (auto& player : _levelHandler->GetPlayers()) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((newPos - _pos).Length() <= 300.0f) { _attacking = true; _attackTime = Random().NextFloat(130.0f, 180.0f); SetFacingLeft(newPos.X < _pos.X); _speed.X = (IsFacingLeft() ? -1.8f : 1.8f); SetAnimation((AnimState)1073741824); SetTransition((AnimState)1073741825, false); break; } } } else { _attackTime -= timeMult; } } } _turnCooldown -= timeMult; } void Demon::OnUpdateHitbox() { UpdateHitbox(28, 26); } bool Demon::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Demon.h000066400000000000000000000010601512772601700255630ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Demon */ class Demon : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Demon(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: float _attackTime; bool _attacking; bool _stuck; float _turnCooldown; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Doggy.cpp000066400000000000000000000061131512772601700261310ustar00rootroot00000000000000#include "Doggy.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Weapons/ShotBase.h" #include "../Weapons/FreezerShot.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Doggy::Doggy() : _attackSpeed(0.0f), _attackTime(0.0f), _noiseCooldown(120.0f), _stuck(false) { } void Doggy::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { case 0: default: PreloadMetadataAsync("Enemy/Doggy"_s); break; case 1: // TSF Cat PreloadMetadataAsync("Enemy/Cat"_s); break; } } Task Doggy::OnActivatedAsync(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; SetHealthByDifficulty(3); _scoreValue = 200; SetState(ActorState::CollideWithTilesetReduced, true); switch (theme) { case 0: default: async_await RequestMetadataAsync("Enemy/Doggy"_s); _attackSpeed = 3.2f; break; case 1: // TSF Cat async_await RequestMetadataAsync("Enemy/Cat"_s); _attackSpeed = 3.8f; break; } SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f); PlaceOnGround(); async_return true; } void Doggy::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_attackTime <= 0.0f) { _speed.X = (IsFacingLeft() ? -1.0f : 1.0f); SetAnimation(AnimState::Walk); if (_noiseCooldown <= 0.0f) { _noiseCooldown = Random().NextFloat(100, 300); PlaySfx("Noise"_s, 0.4f); } else { _noiseCooldown -= timeMult; } } else { _attackTime -= timeMult; if (_noiseCooldown <= 0.0f) { _noiseCooldown = Random().NextFloat(25, 40); PlaySfx("Woof"_s); } else { _noiseCooldown -= timeMult; } } if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4.0f, 0.0f)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * (_attackTime <= 0.0f ? 1.0f : _attackSpeed); _stuck = true; } } else { _stuck = false; } } } void Doggy::OnUpdateHitbox() { UpdateHitbox(50, 30); } bool Doggy::OnHandleCollision(std::shared_ptr other) { if (auto* shotBase = runtime_cast(other.get())) { DecreaseHealth(shotBase->GetStrength(), shotBase); if (_health <= 0.0f) { return true; } HandleFrozenStateChange(shotBase); if (!runtime_cast(shotBase)) { if (_attackTime <= 0.0f) { PlaySfx("Attack"); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * _attackSpeed; SetAnimation(AnimState::TransitionAttack); } _attackTime = 200.0f; _noiseCooldown = 45.0f; } } return false; } bool Doggy::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Doggy.h000066400000000000000000000011721512772601700255760ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Doggy */ class Doggy : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Doggy(); static void Preload(const ActorActivationDetails& details); bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: float _attackSpeed; float _attackTime; float _noiseCooldown; bool _stuck; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Dragon.cpp000066400000000000000000000107731512772601700263010ustar00rootroot00000000000000#include "Dragon.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Dragon::Dragon() : _attacking(false), _stateTime(0.0f), _attackTime(0.0f) { } void Dragon::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Dragon"_s); PreloadMetadataAsync("Weapon/Toaster"_s); } Task Dragon::OnActivatedAsync(const ActorActivationDetails& details) { // Move the dragon down a bit to avoid being stuck in the ceiling in the first level on hard difficulty MoveInstantly(Vector2f(0.0f, 8.0f), MoveType::Relative | MoveType::Force); SetHealthByDifficulty(1); _scoreValue = 200; async_await RequestMetadataAsync("Enemy/Dragon"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); async_return true; } void Dragon::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } bool found = false; Vector2f targetPos; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { Vector2f newPos = player->GetPos(); if ((newPos - _pos).Length() <= 220.0f) { targetPos = newPos; found = true; break; } } if (found) { if (_currentTransition == nullptr) { if (!_attacking) { if (_stateTime <= 0.0f) { bool willFaceLeft = (_pos.X > targetPos.X); if (IsFacingLeft() != willFaceLeft) { SetTransition(AnimState::TransitionTurn, false, [this, willFaceLeft]() { SetFacingLeft(willFaceLeft); SetAnimation((AnimState)1073741825); SetTransition((AnimState)1073741824, false, [this]() { _attacking = true; _stateTime = 30.f; }); }); } else { SetAnimation((AnimState)1073741825); SetTransition((AnimState)1073741824, false, [this]() { _attacking = true; _stateTime = 30.0f; }); } } } else { if (_stateTime <= 0.0f) { SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false, [this]() { _attacking = false; _stateTime = 60.0f; }); } else { if (_attackTime <= 0.0f) { std::shared_ptr fire = std::make_shared(); uint8_t fireParams[1]; fireParams[0] = (IsFacingLeft() ? 1 : 0); fire->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -14 : 14), (std::int32_t)_pos.Y - 6, _renderer.layer() + 2), fireParams )); _levelHandler->AddActor(fire); _attackTime = 10.0f; } else { _attackTime -= timeMult; } } } } _stateTime -= timeMult; } else if (_attacking) { SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false, [this]() { _attacking = false; _stateTime = 80.0f; }); } } bool Dragon::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::Tiny); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } Task Dragon::Fire::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); // Collide with player ammo if Reforged CanCollideWithShots = _levelHandler->IsReforged(); SetFacingLeft(details.Params[0] != 0); async_await RequestMetadataAsync("Weapon/Toaster"_s); SetAnimation(AnimState::Default); constexpr float BaseSpeed = 1.6f; _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * (BaseSpeed + Random().NextFloat(0.0f, 0.2f)); _speed.Y += Random().NextFloat(-0.5f, 0.5f); _timeLeft = 60.0f; async_return true; } void Dragon::Fire::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _timeLeft -= timeMult; } } void Dragon::Fire::OnEmitLights(SmallVectorImpl& lights) { auto& light1 = lights.emplace_back(); light1.Pos = _pos; light1.Intensity = 0.85f; light1.Brightness = 0.6f; light1.RadiusNear = 0.0f; light1.RadiusFar = 30.0f; auto& light2 = lights.emplace_back(); light2.Pos = _pos; light2.Intensity = 0.2f; light2.RadiusNear = 0.0f; light2.RadiusFar = 140.0f; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Dragon.h000066400000000000000000000017411512772601700257410ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Dragon */ class Dragon : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Dragon(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 0.7f; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Fire : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; private: float _timeLeft; }; #endif bool _attacking; float _stateTime; float _attackTime; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Dragonfly.cpp000066400000000000000000000070011512772601700270020ustar00rootroot00000000000000#include "Dragonfly.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Dragonfly::Dragonfly() : _state(StateIdle), _idleTime(0.0f), _attackCooldown(60.0f) { } Dragonfly::~Dragonfly() { if (_noise != nullptr) { _noise->stop(); _noise = nullptr; } } void Dragonfly::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Dragonfly"_s); } Task Dragonfly::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::ApplyGravitation, false); SetHealthByDifficulty(1); _scoreValue = 200; async_await RequestMetadataAsync("Enemy/Dragonfly"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); async_return true; } void Dragonfly::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { if (_noise != nullptr) { _noise->pause(); } return; } SetState(ActorState::CanJump, false); if (_attackCooldown < 0.0f) { _attackCooldown = 40.0f; Vector2f targetPos; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } targetPos = player->GetPos(); _direction.X = targetPos.X - _pos.X; _direction.Y = targetPos.Y - _pos.Y; float length = _direction.Length(); if (length < 320.0f && targetPos.Y < _levelHandler->GetWaterLevel()) { _direction.Normalize(); _speed.X = 0.0f; _speed.Y = 0.0f; SetFacingLeft(_direction.X < 0.0f); _state = StateAttacking; _idleTime = Random().NextFloat(40.0f, 60.0f); _attackCooldown = Random().NextFloat(130.0f, 200.0f); _noise = PlaySfx("Noise"_s, 0.6f); break; } } } else { if (_state == StateAttacking) { if (_idleTime < 0.0f) { _state = StateBraking; if (_noise != nullptr) { // TODO: Fade-out //_noise.FadeOut(1f); _noise->stop(); _noise = nullptr; } } else { _idleTime -= timeMult; _speed.X += _direction.X * 0.14f * timeMult; _speed.Y += _direction.Y * 0.14f * timeMult; } } else if (_state == StateBraking) { float speedMult = powf(0.88f, timeMult); _speed.X *= speedMult; _speed.Y *= speedMult; if (std::abs(_speed.X) < 0.01f && std::abs(_speed.Y) < 0.01f) { _state = StateIdle; } } else { if (_idleTime < 0.0f) { float x = Random().NextFloat(-0.4f, 0.4f); float y = Random().NextFloat(-2.0f, 2.0f); _speed.X = (_speed.X + x) * 0.2f; _speed.Y = (_speed.Y + y) * 0.2f; _idleTime = 20.0f; } else { _idleTime -= timeMult; } } // Can't fly into the water if (_pos.Y > _levelHandler->GetWaterLevel() - 12.0f) { _speed.Y = -0.4f; _state = StateIdle; if (_noise != nullptr) { // TODO: Fade-out //_noise.FadeOut(0.4f); _noise->stop(); _noise = nullptr; } } _attackCooldown -= timeMult; } if (_noise != nullptr) { _noise->play(); } } void Dragonfly::OnHitWall(float timeMult) { } void Dragonfly::OnHitFloor(float timeMult) { } void Dragonfly::OnHitCeiling(float timeMult) { } bool Dragonfly::OnPerish(ActorBase* collider) { if (_noise != nullptr) { _noise->stop(); _noise = nullptr; } CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Dragonfly.h000066400000000000000000000015761512772601700264620ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Dragonfly */ class Dragonfly : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Dragonfly(); ~Dragonfly(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitFloor(float timeMult) override; void OnHitCeiling(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateIdle = 0; static constexpr std::int32_t StateAttacking = 1; static constexpr std::int32_t StateBraking = 2; std::int32_t _state; float _idleTime; float _attackCooldown; Vector2f _direction; std::shared_ptr _noise; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/EnemyBase.cpp000066400000000000000000000144031512772601700267310ustar00rootroot00000000000000#include "EnemyBase.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../../Tiles/TileMap.h" #include "../Weapons/ShotBase.h" #include "../Weapons/ShieldFireShot.h" #include "../Weapons/ToasterShot.h" #include "../Weapons/Thunderbolt.h" #include "../Weapons/TNT.h" #include "../Explosion.h" #include "../Player.h" #include "../Solid/Pole.h" #include "../Solid/PushableBox.h" #include "../../../nCine/Base/Random.h" #include using namespace Jazz2::Tiles; namespace Jazz2::Actors::Enemies { EnemyBase::EnemyBase() : CanCollideWithShots(true), _canHurtPlayer(true), _scoreValue(0), _blinkingTimeout(0.0f) { } void EnemyBase::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); HandleBlinking(timeMult); } void EnemyBase::AddScoreToCollider(ActorBase* collider) { if (_scoreValue > 0) { if (auto* player = runtime_cast(collider)) { player->AddScore(_scoreValue); } else if (auto* shotBase = runtime_cast(collider)) { auto* owner = shotBase->GetOwner(); if (owner != nullptr) { owner->AddScore(_scoreValue); } } else if (auto* tnt = runtime_cast(collider)) { auto* owner = tnt->GetOwner(); if (owner != nullptr) { owner->AddScore(_scoreValue); } } _scoreValue = 0; } } void EnemyBase::SetHealthByDifficulty(std::int32_t health) { switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Easy: health = (std::int32_t)std::round(health * 0.6f); break; case GameDifficulty::Hard: health = (std::int32_t)std::round(health * 1.4f); break; } _health = std::max(health, 1); _maxHealth = _health; } void EnemyBase::PlaceOnGround() { if (!GetState(ActorState::CollideWithTileset | ActorState::ApplyGravitation)) { return; } OnUpdateHitbox(); TileCollisionParams params = { TileDestructType::None, _speed.Y >= 0.0f }; if (_levelHandler->IsPositionEmpty(this, AABBInner, params)) { // Apply instant gravitation std::int32_t i = 10; while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative, params)) { // Try it multiple times } } else { // Enemy is probably stuck inside a tile, move up std::int32_t i = 1; while (i++ <= 6 && !MoveInstantly(Vector2f(0.0f, i * -4.0f), MoveType::Relative, params)) { // Try it multiple times } } } bool EnemyBase::CanMoveToPosition(float x, float y) { uint8_t* eventParams; auto events = _levelHandler->EventMap(); if (events != nullptr && events->GetEventByPosition(_pos.X + x, _pos.Y + y, &eventParams) == EventType::AreaStopEnemy) { return false; } ActorState prevState = GetState(); SetState(ActorState::CollideWithTilesetReduced, false); SetState(ActorState::SkipPerPixelCollisions, true); bool success; TileCollisionParams params = { TileDestructType::None, true }; AABBf aabbAbove = AABBf(x < 0.0f ? AABBInner.L - 8.0f + x : AABBInner.R, AABBInner.T, x < 0.0f ? AABBInner.L : AABBInner.R + 8.0f + x, AABBInner.B - 2.0f); if (_levelHandler->IsPositionEmpty(this, aabbAbove, params)) { AABBf aabbBelow = AABBf(x < 0.0f ? AABBInner.L - 8.0f + x : AABBInner.R + 2.0f, AABBInner.B, x < 0.0f ? AABBInner.L - 2.0f : AABBInner.R + 8.0f + x, AABBInner.B + 8.0f); success = !_levelHandler->IsPositionEmpty(this, aabbBelow, params); } else { success = false; } SetState(prevState); return success; } void EnemyBase::TryGenerateRandomDrop() { constexpr struct { EventType Event; int Chance; } DropChanges[] = { { EventType::Empty, 10 }, { EventType::Carrot, 2 }, { EventType::FastFire, 2 }, { EventType::Gem, 6 } }; // TODO: This cannot be constexpr because of Android int combinedChance = std::accumulate(std::begin(DropChanges), std::end(DropChanges), 0, [](const int& sum, const auto& item) { return sum + item.Chance; }); int drop = Random().Next(0, combinedChance); for (auto& item : DropChanges) { if (drop < item.Chance) { if (item.Event != EventType::Empty) { uint8_t eventParams[16] { }; std::shared_ptr actor = _levelHandler->EventSpawner()->SpawnEvent(item.Event, eventParams, ActorState::None, Vector3i((int)_pos.X, (int)_pos.Y, _renderer.layer())); if (actor != nullptr) { _levelHandler->AddActor(actor); } } break; } drop -= item.Chance; } } bool EnemyBase::OnHandleCollision(std::shared_ptr other) { if (!GetState(ActorState::IsInvulnerable)) { if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { DecreaseHealth(shotBase->GetStrength(), shotBase); } // Collision must also be processed by the shot //return true; } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(5, tnt); return true; } else if (auto* pole = runtime_cast(other.get())) { if (_levelHandler->IsReforged()) { bool hit; switch (pole->GetFallDirection()) { case Solid::Pole::FallDirection::Left: hit = (_pos.X < pole->GetPos().X); break; case Solid::Pole::FallDirection::Right: hit = (_pos.X > pole->GetPos().X); break; default: hit = false; break; } if (hit) { DecreaseHealth(10, pole); return true; } } } else if (auto* pushableBox = runtime_cast(other.get())) { if (_levelHandler->IsReforged() && pushableBox->GetSpeed().Y > 0.0f && pushableBox->AABBInner.B < _pos.Y) { DecreaseHealth(10, pushableBox); return true; } } } return ActorBase::OnHandleCollision(std::move(other)); } bool EnemyBase::CanCauseDamage(ActorBase* collider) { return !IsInvulnerable() && CanCollideWithShots; } void EnemyBase::OnHealthChanged(ActorBase* collider) { StartBlinking(); } bool EnemyBase::OnPerish(ActorBase* collider) { AddScoreToCollider(collider); return ActorBase::OnPerish(collider); } void EnemyBase::StartBlinking() { if (_frozenTimeLeft > 0.0f) { return; } if (_blinkingTimeout <= 0.0f) { _renderer.Initialize(ActorRendererType::WhiteMask); } _blinkingTimeout = 6.0f; } void EnemyBase::HandleBlinking(float timeMult) { if (_blinkingTimeout > 0.0f) { _blinkingTimeout -= timeMult; if (_blinkingTimeout <= 0.0f) { _renderer.Initialize(ActorRendererType::Default); } } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/EnemyBase.h000066400000000000000000000025071512772601700264000ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" #include "../../Direction.h" namespace Jazz2::Actors::Enemies { /** @brief Base class of an enemy */ class EnemyBase : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: EnemyBase(); /** @brief Whether the enemy should collide with player shots */ bool CanCollideWithShots; bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; /** @brief Whether the enemy can hurt player */ bool CanHurtPlayer() const { return (_canHurtPlayer && _frozenTimeLeft <= 0.0f); } /** @brief Whether the enemy is currently frozen */ bool IsFrozen() const { return (_frozenTimeLeft > 0.0f); } protected: #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring bool _canHurtPlayer; std::uint32_t _scoreValue; #endif void OnUpdate(float timeMult) override; void OnHealthChanged(ActorBase* collider) override; bool OnPerish(ActorBase* collider) override; void AddScoreToCollider(ActorBase* collider); virtual void SetHealthByDifficulty(std::int32_t health); void PlaceOnGround(); bool CanMoveToPosition(float x, float y); void TryGenerateRandomDrop(); void StartBlinking(); void HandleBlinking(float timeMult); private: float _blinkingTimeout; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/FatChick.cpp000066400000000000000000000050111512772601700265300ustar00rootroot00000000000000#include "FatChick.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { FatChick::FatChick() : _isAttacking(false), _stuck(false) { } void FatChick::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/FatChick"_s); } Task FatChick::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(3); _scoreValue = 300; async_await RequestMetadataAsync("Enemy/FatChick"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; PlaceOnGround(); async_return true; } void FatChick::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } for (auto& player : _levelHandler->GetPlayers()) { if (player->GetHealth() <= 0) { continue; } Vector2f targetPos = player->GetPos(); float length = (_pos - targetPos).Length(); if (length > 20.0f && length < 60.0f) { SetFacingLeft(_pos.X > targetPos.X); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; break; } } if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _stuck = true; } } else { _stuck = false; } } if (!_isAttacking) { bool foundPlayer = false; _levelHandler->GetCollidingPlayers(AABBInner + Vector2f(_speed.X * 28.0f, 0.0f), [&foundPlayer](Actors::ActorBase* actor) { foundPlayer = true; return false; }); if (foundPlayer) { Attack(); } } } void FatChick::OnUpdateHitbox() { UpdateHitbox(20, 24); } bool FatChick::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } void FatChick::Attack() { // TODO: Play sound in the middle of transition // TODO: Apply force in the middle of transition PlaySfx("Attack"_s, 0.8f, 0.6f); SetTransition(AnimState::TransitionAttack, false, [this]() { _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _isAttacking = false; }); _speed.X = 0.0f; _isAttacking = true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/FatChick.h000066400000000000000000000011211512772601700261730ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Fat chick */ class FatChick : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: FatChick(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 0.9f; bool _isAttacking; bool _stuck; void Attack(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Fencer.cpp000066400000000000000000000037761512772601700262760ustar00rootroot00000000000000#include "Fencer.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Fencer::Fencer() : _stateTime(0.0f) { } void Fencer::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Fencer"_s); } Task Fencer::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(3); _scoreValue = 400; async_await RequestMetadataAsync("Enemy/Fencer"_s); SetFacingLeft(true); SetAnimation(AnimState::Idle); PlaceOnGround(); async_return true; } void Fencer::OnUpdate(float timeMult) { if (_frozenTimeLeft <= 0.0f) { Vector2f targetPos; if (FindNearestPlayer(targetPos)) { SetFacingLeft(_pos.X > targetPos.X); if (_stateTime <= 0.0f) { if (Random().NextFloat() < 0.3f) { _speed.X = (IsFacingLeft() ? -1 : 1) * 1.8f; } else { _speed.X = (IsFacingLeft() ? -1 : 1) * -1.2f; } MoveInstantly(Vector2f(0.0f, -4.0f), MoveType::Relative); _speed.Y = (_levelHandler->IsReforged() ? -3.0f : -2.0f); _internalForceY = -0.5f; PlaySfx("Attack"_s); SetTransition(AnimState::TransitionAttack, false, [this]() { _speed.X = 0.0f; }); _stateTime = Random().NextFloat(160.0f, 300.0f); } else { _stateTime -= timeMult; } } } EnemyBase::OnUpdate(timeMult); } bool Fencer::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } bool Fencer::FindNearestPlayer(Vector2f& targetPos) { constexpr float VisionDistance = 100.0f; for (auto& player : _levelHandler->GetPlayers()) { if (player->GetHealth() <= 0) { continue; } targetPos = player->GetPos(); if ((_pos - targetPos).Length() < VisionDistance) { return true; } } targetPos = Vector2f::Zero; return false; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Fencer.h000066400000000000000000000010071512772601700257240ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Fencer */ class Fencer : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Fencer(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: float _stateTime; bool FindNearestPlayer(Vector2f& targetPos); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Fish.cpp000066400000000000000000000072441512772601700257570ustar00rootroot00000000000000#include "Fish.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Fish::Fish() : _state(StateIdle), _idleTime(0.0f), _attackCooldown(60.0f) { } void Fish::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Fish"_s); } Task Fish::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::ApplyGravitation, false); if (!_levelHandler->IsReforged()) { SetState(ActorState::CollideWithTileset, false); } SetHealthByDifficulty(1); _scoreValue = 100; _originPos = _pos; async_await RequestMetadataAsync("Enemy/Fish"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); async_return true; } void Fish::OnUpdate(float timeMult) { float waterLevel = _levelHandler->GetWaterLevel(); bool inWater = (_pos.Y > waterLevel - 160.0f); if (!inWater) { // Don't move if not in water return; } EnemyBase::OnUpdate(timeMult); if (_pos.Y < waterLevel + 8.0f) { MoveInstantly(Vector2f(_pos.X, waterLevel + 8.0f), MoveType::Absolute | MoveType::Force); } if (_frozenTimeLeft > 0.0f) { return; } SetState(ActorState::CanJump, false); if (_state == StateAttacking) { _speed.X += _direction.X * 0.11f * timeMult; _speed.Y += _direction.Y * 0.11f * timeMult; } else if (_state == StateBraking) { _speed.X = lerp(_speed.X, _speed.X * 0.96f, timeMult); _speed.Y = lerp(_speed.Y, _speed.Y * 0.96f, timeMult); if (std::abs(_speed.X) < 0.01f && std::abs(_speed.Y) < 0.01f) { _state = StateReturning; } } else if (_state == StateReturning) { Vector2f diff = _originPos - _pos; if (diff.Length() < 8.0f) { _state = StateIdle; _attackCooldown = 240.0f; } else { constexpr float ReturnSpeed = 3.0f; _speed = diff.Normalized() * ReturnSpeed; SetFacingLeft(_speed.X < 0.0f); } } else { if (_attackCooldown < 0.0f) { _attackCooldown = 60.0f; Vector2f targetPos; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } targetPos = player->GetPos(); _direction = targetPos - _pos; float length = _direction.Length(); if (length < 320.0f) { _direction.Normalize(); _speed.X = 0.0f; _speed.Y = 0.0f; SetFacingLeft(_direction.X < 0.0f); _state = StateAttacking; SetTransition(AnimState::TransitionAttack, false, [this]() { _state = StateBraking; }); break; } } } else if (_idleTime < 0.0f) { float x = Random().NextFloat(-1.4f, 1.4f); float y = Random().NextFloat(-2.0f, 2.0f); _speed.X = (_speed.X + x) * 0.2f; _speed.Y = (_speed.Y + y) * 0.2f; _idleTime = 20.0f; } else { _idleTime -= timeMult; } _attackCooldown -= timeMult; } } void Fish::OnHitWall(float timeMult) { EnemyBase::OnHitWall(timeMult); _speed.X = 0.0f; _speed.Y = 0.0f; } void Fish::OnHitFloor(float timeMult) { EnemyBase::OnHitFloor(timeMult); _speed.X = 0.0f; _speed.Y = 0.0f; } void Fish::OnHitCeiling(float timeMult) { EnemyBase::OnHitCeiling(timeMult); _speed.X = 0.0f; _speed.Y = 0.0f; } bool Fish::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::SmallDark); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Fish.h000066400000000000000000000015751512772601700254250ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Fish */ class Fish : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Fish(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitFloor(float timeMult) override; void OnHitCeiling(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr std::int32_t StateIdle = 0; static constexpr std::int32_t StateAttacking = 1; static constexpr std::int32_t StateBraking = 2; static constexpr std::int32_t StateReturning = 3; std::int32_t _state; float _idleTime; float _attackCooldown; Vector2f _direction; Vector2f _originPos; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Helmut.cpp000066400000000000000000000043601512772601700263200ustar00rootroot00000000000000#include "Helmut.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Helmut::Helmut() : _idling(false), _stateTime(0.0f), _stuck(false), _turnCooldown(0.0f) { } void Helmut::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Helmut"_s); } Task Helmut::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(1); _scoreValue = 100; async_await RequestMetadataAsync("Enemy/Helmut"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; PlaceOnGround(); async_return true; } void Helmut::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_idling) { if (_stateTime <= 0.0f) { _idling = false; SetFacingLeft(!IsFacingLeft()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _stateTime = Random().NextFloat(280.0f, 360.0f); } } else { if (_stateTime <= 0.0f) { _speed.X = 0; _idling = true; SetAnimation(AnimState::Idle); _stateTime = Random().NextFloat(70.0f, 190.0f); } else if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4.0f, 0.0f)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else if (_turnCooldown <= 0.0f) { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _turnCooldown = 90.0f; _stuck = true; } else { _speed.X = 0; _idling = true; SetAnimation(AnimState::Idle); _stateTime = Random().NextFloat(90.0f, 190.0f) - _turnCooldown; _turnCooldown = 0.0f; } } else { _stuck = false; } } } _stateTime -= timeMult; _turnCooldown -= timeMult; } void Helmut::OnUpdateHitbox() { UpdateHitbox(28, 26); } bool Helmut::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Helmut.h000066400000000000000000000011361512772601700257630ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Helmut */ class Helmut : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Helmut(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 0.9f; bool _idling; float _stateTime; bool _stuck; float _turnCooldown; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/LabRat.cpp000066400000000000000000000075531512772601700262360ustar00rootroot00000000000000#include "LabRat.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { LabRat::LabRat() : _isAttacking(false), _canAttack(true), _idling(false), _canIdle(false), _stateTime(0.0f), _attackTime(0.0f), _turnCooldown(0.0f) { } void LabRat::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/LabRat"_s); } Task LabRat::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(1); _scoreValue = 200; SetState(ActorState::CollideWithTilesetReduced, true); async_await RequestMetadataAsync("Enemy/LabRat"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; _stateTime = Random().NextFloat(180.0f, 300.0f); _attackTime = Random().NextFloat(300.0f, 400.0f); PlaceOnGround(); async_return true; } void LabRat::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_idling) { Idle(timeMult); } else { Walking(timeMult); } _stateTime -= timeMult; _turnCooldown -= timeMult; } void LabRat::OnUpdateHitbox() { UpdateHitbox(30, 30); } bool LabRat::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } void LabRat::Idle(float timeMult) { if (_stateTime <= 0.0f) { _idling = false; SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; _speed.Y = 0.0f; _stateTime = Random().NextFloat(420, 540); } else { _stateTime -= timeMult; if (Random().NextFloat() < 0.008f * timeMult) { PlaySfx("Idle"_s, 0.2f); } } } void LabRat::Walking(float timeMult) { if (!_isAttacking) { if (GetState(ActorState::CanJump) && !CanMoveToPosition(_speed.X * 4.0f, 0.0f)) { if (_turnCooldown <= 0.0f) { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _turnCooldown = 120.0f; } else { _speed.X = 0; _idling = true; SetAnimation(AnimState::Idle); _canIdle = false; _stateTime = Random().NextFloat(260.0f, 320.0f) - _turnCooldown; _turnCooldown = 0.0f; } } if (_canAttack) { if (std::abs(_speed.Y) < std::numeric_limits::epsilon()) { AABBf aabb = AABBInner; if (IsFacingLeft()) { aabb.L -= 128; } else { aabb.R += 128; } bool playerFound = false; _levelHandler->GetCollidingPlayers(aabb, [&playerFound](ActorBase*) { playerFound = true; return false; }); if (playerFound) { Attack(); } } } else { if (_attackTime <= 0.0f) { _canAttack = true; _attackTime = 180.0f; } else { _attackTime -= timeMult; } } if (Random().NextFloat() < 0.004f * timeMult) { PlaySfx("Noise"_s, 0.2f); } if (_canIdle) { if (_stateTime <= 0.0f) { _speed.X = 0; _idling = true; SetAnimation(AnimState::Idle); _canIdle = false; _stateTime = Random().NextFloat(260.0f, 320.0f); } } else { if (_stateTime <= 0.0f) { _canIdle = true; _stateTime = Random().NextFloat(60.0f, 120.0f); } } } } void LabRat::Attack() { SetTransition(AnimState::TransitionAttack, false, [this]() { _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _isAttacking = false; _canAttack = false; _attackTime = 180.0f; }); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * 2.0f; MoveInstantly(Vector2f(0.0f, -1.0f), MoveType::Relative); _speed.Y = (_levelHandler->IsReforged() ? -3.0f : -2.0f); _internalForceY = -0.5f; _isAttacking = true; SetState(ActorState::CanJump, false); PlaySfx("Attack"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/LabRat.h000066400000000000000000000013551512772601700256750ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Lab rat */ class LabRat : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: LabRat(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 1.0f; bool _isAttacking; bool _canAttack; bool _idling; bool _canIdle; float _stateTime; float _attackTime; float _turnCooldown; void Idle(float timeMult); void Walking(float timeMult); void Attack(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Lizard.cpp000066400000000000000000000064341512772601700263130ustar00rootroot00000000000000#include "Lizard.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Enemies { Lizard::Lizard() : _stuck(false), _isFalling(false) { } void Lizard::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { case 0: default: PreloadMetadataAsync("Enemy/Lizard"_s); break; case 1: // Xmas PreloadMetadataAsync("Enemy/LizardXmas"_s); break; } } Task Lizard::OnActivatedAsync(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; _isFalling = (details.Params[1] != 0); bool isFacingLeft = (_isFalling ? details.Params[2] != 0 : Random().NextBool()); SetHealthByDifficulty(_isFalling ? 6 : 1); _scoreValue = 100; SetState(ActorState::CollideWithTilesetReduced, true); switch (theme) { case 0: default: async_await RequestMetadataAsync("Enemy/Lizard"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Enemy/LizardXmas"_s); break; } SetFacingLeft(isFacingLeft); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; if (_isFalling) { // Lizard lost its copter, check if spawn position is // empty, because walking Lizard has bigger hitbox OnUpdateHitbox(); TileCollisionParams params = { TileDestructType::None, _speed.Y >= 0.0f }; if (!_levelHandler->IsPositionEmpty(this, AABBInner, params)) { // Lizard was probably spawned into a wall, try to move it // from the wall by 4px steps (max. 12px) in both directions const float Adjust = 4.0f; for (int i = 1; i < 4; i++) { if (MoveInstantly(Vector2(Adjust * i, 0.0f), MoveType::Relative) || MoveInstantly(Vector2(-Adjust * i, 0.0f), MoveType::Relative)) { // Empty spot found break; } } } } else { PlaceOnGround(); } async_return true; } void Lizard::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f || _isFalling) { return; } if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4.0f, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _stuck = true; } } else { _stuck = false; } } if (Random().NextFloat() < 0.002f * timeMult) { PlaySfx("Noise"_s, 0.4f); } } void Lizard::OnUpdateHitbox() { UpdateHitbox(30, 30); } void Lizard::OnHitFloor(float timeMult) { EnemyBase::OnHitFloor(timeMult); // TODO: Partial workaround for stuck enemies SetState(ActorState::CanJump, true); if (_isFalling) { _isFalling = false; SetHealthByDifficulty(1); } } void Lizard::OnHitWall(float timeMult) { EnemyBase::OnHitWall(timeMult); // TODO: Partial workaround for stuck enemies SetState(ActorState::CanJump, true); } bool Lizard::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Lizard.h000066400000000000000000000012151512772601700257500ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Lizard */ class Lizard : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Lizard(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 1.0f; bool _stuck; bool _isFalling; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/LizardFloat.cpp000066400000000000000000000120651512772601700272760ustar00rootroot00000000000000#include "LizardFloat.h" #include "Lizard.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Environment/Bomb.h" #include "../Environment/Copter.h" #include "../Player.h" #include "../Solid/PushableBox.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/Base/FrameTimer.h" #include namespace Jazz2::Actors::Enemies { LizardFloat::LizardFloat() : _attackTime(200.0f), _moveTime(100.0f) { } void LizardFloat::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { case 0: default: PreloadMetadataAsync("Enemy/LizardFloat"_s); break; case 1: // Xmas PreloadMetadataAsync("Enemy/LizardFloatXmas"_s); break; } } Task LizardFloat::OnActivatedAsync(const ActorActivationDetails& details) { _theme = details.Params[0]; _copterDuration = details.Params[1]; // TODO: flyAway parameter //bool flyAway = (details.Params[2] != 0); SetState(ActorState::ApplyGravitation, false); SetState(ActorState::CollideWithTileset, _levelHandler->IsReforged()); SetHealthByDifficulty(1); _scoreValue = 200; switch (_theme) { case 0: default: async_await RequestMetadataAsync("Enemy/LizardFloat"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Enemy/LizardFloatXmas"_s); break; } SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); _copter = std::make_shared(); uint8_t copterParams[1]; copterParams[0] = 1; _copter->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), copterParams )); _levelHandler->AddActor(_copter); async_return true; } bool LizardFloat::OnTileDeactivated() { if (_copter != nullptr) { _copter->DecreaseHealth(INT32_MAX); _copter = nullptr; } return true; } void LizardFloat::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_copter != nullptr) { _copter->MoveInstantly(Vector2f(_pos.X, _pos.Y + 4.0f), MoveType::Absolute | MoveType::Force); } if (_frozenTimeLeft > 0.0f) { return; } SetState(ActorState::CanJump, false); bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { float distance = (targetPos - _pos).Length(); if (distance < 280.0f && _attackTime <= 0.0f) { SetTransition(AnimState::TransitionAttack, false, [this]() { std::shared_ptr bomb = std::make_shared(); uint8_t bombParams[2]; bombParams[0] = (uint8_t)(_theme + 1); bombParams[1] = (IsFacingLeft() ? 1 : 0); bomb->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -30 : 30), (std::int32_t)_pos.Y - 10, _renderer.layer() + 2), bombParams )); _levelHandler->AddActor(bomb); SetTransition(AnimState::TransitionAttackEnd, false); }); _attackTime = Random().NextFloat(120, 240); } if (distance < 360.0f && _moveTime <= 0.0f) { Vector2f diff = (targetPos - _pos).Normalized(); Vector2f speed = (Vector2f(_speed.X, _speed.Y) + diff * 0.4f).Normalized(); _speed.X = speed.X * DefaultSpeed; _speed.Y = speed.Y * DefaultSpeed; SetFacingLeft(_speed.X < 0.0f); _moveTime = 8.0f; } _attackTime -= timeMult; } _moveTime -= timeMult; } bool LizardFloat::OnPerish(ActorBase* collider) { if (_copter != nullptr) { if (_copterDuration > 0) { _copter->Unmount(_copterDuration * FrameTimer::FramesPerSecond); } else { _copter->DecreaseHealth(INT32_MAX); } _copter = nullptr; } bool shouldDestroy = (_frozenTimeLeft > 0.0f); if (auto* player = runtime_cast(collider)) { if (player->GetSpecialMove() != Player::SpecialMoveType::None) { shouldDestroy = true; } } else if (runtime_cast(collider) || runtime_cast(collider)) { shouldDestroy = true; } if (shouldDestroy) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); } else { std::shared_ptr lizard = std::make_shared(); std::uint8_t lizardParams[3]; lizardParams[0] = _theme; lizardParams[1] = 1; lizardParams[2] = (IsFacingLeft() ? 1 : 0); lizard->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), lizardParams )); _levelHandler->AddActor(lizard); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::SmokeGray); } return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/LizardFloat.h000066400000000000000000000013501512772601700267360ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Environment { class Copter; } namespace Jazz2::Actors::Enemies { /** @brief Floating lizard */ class LizardFloat : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: LizardFloat(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnTileDeactivated() override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 1.4f; float _attackTime; float _moveTime; uint8_t _theme; uint8_t _copterDuration; std::shared_ptr _copter; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/MadderHatter.cpp000066400000000000000000000113231512772601700274230ustar00rootroot00000000000000#include "MadderHatter.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { MadderHatter::MadderHatter() : _attackTime(0.0f), _stuck(false) { } void MadderHatter::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/MadderHatter"_s); } Task MadderHatter::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(3); _scoreValue = 200; SetState(ActorState::CollideWithTilesetReduced, true); async_await RequestMetadataAsync("Enemy/MadderHatter"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -DefaultSpeed : DefaultSpeed); PlaceOnGround(); async_return true; } void MadderHatter::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_currentTransition == nullptr) { if (_attackTime <= 0.0f) { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((newPos - _pos).Length() <= 200.0f) { SetFacingLeft(newPos.X < _pos.X); _speed.X = 0.0f; SetAnimation((AnimState)1073741824); SetTransition((AnimState)1073741824, false, [this]() { PlaySfx("Spit"_s); std::shared_ptr bulletSpit = std::make_shared(); uint8_t bulletSpitParams[1]; bulletSpitParams[0] = (IsFacingLeft() ? 1 : 0); bulletSpit->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -42 : 42), (std::int32_t)_pos.Y - 6, _renderer.layer() + 2), bulletSpitParams )); _levelHandler->AddActor(bulletSpit); SetAnimation(AnimState::Walk); SetTransition((AnimState)1073741825, false, [this]() { _attackTime = Random().NextFloat(120.0f, 160.0f); _speed.X = (IsFacingLeft() ? -DefaultSpeed : DefaultSpeed); }); }); break; } } } else { _attackTime -= timeMult; } if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -DefaultSpeed : DefaultSpeed); _stuck = true; } } else { _stuck = false; } } } } void MadderHatter::OnUpdateHitbox() { UpdateHitbox(30, 30); } bool MadderHatter::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); if (_frozenTimeLeft <= 0.0f) { CreateSpriteDebris((AnimState)2, 1); // Cup CreateSpriteDebris((AnimState)3, 1); // Hat } TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } Task MadderHatter::BulletSpit::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen, false); CanCollideWithShots = false; SetFacingLeft(details.Params[0] != 0); _speed.X = (IsFacingLeft() ? -6.0f : 6.0f); if (_levelHandler->IsReforged()) { _timeLeft = 60; _externalForce.Y = -0.4f; } else { _timeLeft = 30; SetState(ActorState::ApplyGravitation, false); } _health = INT32_MAX; async_await RequestMetadataAsync("Enemy/MadderHatter"_s); SetAnimation((AnimState)1073741826); async_return true; } void MadderHatter::BulletSpit::OnUpdate(float timeMult) { _timeLeft -= timeMult; if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); return; } EnemyBase::OnUpdate(timeMult); float angle = atan2f(_speed.Y, _speed.X); if (IsFacingLeft()) { angle -= fRadAngle180; } _renderer.setRotation(angle); } void MadderHatter::BulletSpit::OnUpdateHitbox() { UpdateHitbox(8, 8); } bool MadderHatter::BulletSpit::OnHandleCollision(std::shared_ptr other) { return false; } bool MadderHatter::BulletSpit::OnPerish(ActorBase* collider) { if (!_levelHandler->IsReforged()) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Small); } return EnemyBase::OnPerish(collider); } void MadderHatter::BulletSpit::OnHitFloor(float timeMult) { DecreaseHealth(INT32_MAX); } void MadderHatter::BulletSpit::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void MadderHatter::BulletSpit::OnHitCeiling(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/MadderHatter.h000066400000000000000000000024711512772601700270740ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Madder hatter */ class MadderHatter : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: MadderHatter(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 0.7f; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class BulletSpit : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitCeiling(float timeMult) override; private: float _timeLeft; }; #endif float _attackTime; bool _stuck; void Idle(float timeMult); void Walking(float timeMult); void Attack(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Monkey.cpp000066400000000000000000000124521512772601700263250ustar00rootroot00000000000000#include "Monkey.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Enemies { Monkey::Monkey() : _isWalking(false), _stuck(false) { } void Monkey::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Monkey"_s); } Task Monkey::OnActivatedAsync(const ActorActivationDetails& details) { _isWalking = (details.Params[0] != 0); SetHealthByDifficulty(3); _scoreValue = 200; async_await RequestMetadataAsync("Enemy/Monkey"_s); if (_isWalking) { SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; PlaceOnGround(); } else { SetAnimation(AnimState::Jump); } async_return true; } void Monkey::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (!_isWalking || _frozenTimeLeft > 0.0f) { return; } if (GetState(ActorState::CanJump) && std::abs(_speed.X) > 0) { if (!CanMoveToPosition(_speed.X * 4, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _stuck = true; } } else { _stuck = false; } } } void Monkey::OnUpdateHitbox() { UpdateHitbox(30, 30); } void Monkey::OnAnimationFinished() { EnemyBase::OnAnimationFinished(); if (_currentTransition == nullptr) { bool found = false; Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_pos - newPos).Length() < (_pos - targetPos).Length()) { targetPos = newPos; found = true; } } if (found) { Vector2f diff = (targetPos - _pos); float radiusDistance = diff.Length(); std::uint32_t distance = (std::uint32_t)std::abs(diff.X); if (distance > 16.0f && radiusDistance < 280.0f) { if (_isWalking) { _speed.X = 0.0f; SetTransition((AnimState)1073741825, false, [this, targetPos, distance]() { SetFacingLeft(targetPos.X < _pos.X); SetTransition((AnimState)1073741826, false, [this, distance]() { std::shared_ptr banana = std::make_shared(); uint8_t bananaParams[3]; bananaParams[0] = (IsFacingLeft() ? 1 : 0); bananaParams[1] = distance & 0xff; bananaParams[2] = (distance >> 8) & 0xff; banana->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -8 : 8), (std::int32_t)_pos.Y - 8, _renderer.layer() + 2), bananaParams )); _levelHandler->AddActor(banana); SetTransition((AnimState)1073741827, false, [this]() { SetTransition((AnimState)1073741824, false, [this]() { SetFacingLeft(Random().NextBool()); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; }); }); }); }); } else { SetFacingLeft(targetPos.X < _pos.X); SetTransition((AnimState)1073741826, false, [this, distance]() { std::shared_ptr banana = std::make_shared(); uint8_t bananaParams[3]; bananaParams[0] = (IsFacingLeft() ? 1 : 0); bananaParams[1] = distance & 0xff; bananaParams[2] = (distance >> 8) & 0xff; banana->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X + (IsFacingLeft() ? -42 : 42), (std::int32_t)_pos.Y - 8, _renderer.layer() + 2), bananaParams )); _levelHandler->AddActor(banana); SetTransition((AnimState)1073741827, false); }); } } } } } bool Monkey::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } Task Monkey::Banana::OnActivatedAsync(const ActorActivationDetails& details) { _health = 2; SetFacingLeft(details.Params[0] != 0); std::uint32_t distance = details.Params[1] | (details.Params[2] << 8); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * distance / 32; _speed.Y = -3.0f; async_await RequestMetadataAsync("Enemy/Monkey"_s); SetAnimation((AnimState)1073741828); _soundThrow = PlaySfx("BananaThrow"_s); async_return true; } void Monkey::Banana::OnUpdateHitbox() { UpdateHitbox(4, 4); } bool Monkey::Banana::OnPerish(ActorBase* collider) { _soundThrow = nullptr; _speed.X = 0.0f; _speed.Y = 0.0f; SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); SetTransition((AnimState)1073741829, false, [this, collider]() { EnemyBase::OnPerish(collider); }); PlaySfx("BananaSplat"_s, 0.6f); return false; } void Monkey::Banana::OnHitFloor(float timeMult) { DecreaseHealth(INT32_MAX); } void Monkey::Banana::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void Monkey::Banana::OnHitCeiling(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Monkey.h000066400000000000000000000022331512772601700257660ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Monkey */ class Monkey : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Monkey(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnAnimationFinished() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 1.5f; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class Banana : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; void OnHitFloor(float timeMult) override; void OnHitWall(float timeMult) override; void OnHitCeiling(float timeMult) override; private: std::shared_ptr _soundThrow; }; #endif bool _isWalking; bool _stuck; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Rapier.cpp000066400000000000000000000063511512772601700263060ustar00rootroot00000000000000#include "Rapier.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" #include namespace Jazz2::Actors::Enemies { Rapier::Rapier() : _anglePhase(0.0f), _attackTime(80.0f), _attacking(false), _noiseCooldown(Random().FastFloat(200.0f, 400.0f)) { } void Rapier::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Rapier"_s); } Task Rapier::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::ApplyGravitation, false); SetHealthByDifficulty(2); _scoreValue = 300; _originPos = _lastPos = _targetPos = _pos; async_await RequestMetadataAsync("Enemy/Rapier"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); async_return true; } void Rapier::OnUpdate(float timeMult) { OnUpdateHitbox(); HandleBlinking(timeMult); UpdateFrozenState(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_currentTransition == nullptr) { if (_attackTime > 0.0f) { _attackTime -= timeMult; } else { if (_attacking) { SetAnimation(AnimState::Idle); SetTransition((AnimState)1073741826, false, [this]() { _targetPos = _originPos; _attackTime = Random().NextFloat(200.0f, 260.0f); _attacking = false; }); } else { AttackNearestPlayer(); } } if (_noiseCooldown > 0.0f) { _noiseCooldown -= timeMult; } else { _noiseCooldown = Random().FastFloat(300.0f, 600.0f); if (Random().NextFloat() < 0.5f) { PlaySfx("Noise"_s, 0.7f); } } } _anglePhase += timeMult * 0.02f; float speedX = ((_targetPos.X - _lastPos.X) * timeMult / 26.0f + _lastSpeed.X * 1.4f) / 2.4f; float speedY = ((_targetPos.Y - _lastPos.Y) * timeMult / 26.0f + _lastSpeed.Y * 1.4f) / 2.4f; _lastPos.X += speedX; _lastPos.Y += speedY; _lastSpeed = Vector2f(speedX, speedY); bool willFaceLeft = (_speed.X < 0.0f); if (IsFacingLeft() != willFaceLeft) { SetTransition(AnimState::TransitionTurn, false, [this, willFaceLeft]() { SetFacingLeft(willFaceLeft); }); } MoveInstantly(_lastPos + Vector2f(cosf(_anglePhase) * 10.0f, sinf(_anglePhase * 2.0f) * 10.0f), MoveType::Absolute | MoveType::Force); } bool Rapier::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Dissolve, Vector2f::Zero); PlaySfx("Die"_s); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } void Rapier::AttackNearestPlayer() { bool found = false; Vector2f foundPos = Vector2f(FLT_MAX, FLT_MAX); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if ((_lastPos - newPos).SqrLength() < (_lastPos - foundPos).SqrLength()) { foundPos = newPos; found = true; } } Vector2f diff = (foundPos - _lastPos); if (found && diff.Length() <= 200.0f) { SetAnimation((AnimState)1073741824); SetTransition((AnimState)1073741825, false, [this, foundPos]() { _targetPos = foundPos; _attackTime = 80.0f; _attacking = true; PlaySfx("Attack"_s, 0.7f); }); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Rapier.h000066400000000000000000000011601512772601700257440ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Rapier */ class Rapier : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Rapier(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: Vector2f _originPos, _lastPos, _targetPos, _lastSpeed; float _anglePhase; float _attackTime; bool _attacking; float _noiseCooldown; void AttackNearestPlayer(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Raven.cpp000066400000000000000000000061461512772601700261410ustar00rootroot00000000000000#include "Raven.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Algorithms.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Raven::Raven() : _anglePhase(0.0f), _attackTime(160.0f), _attacking(false) { } void Raven::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Raven"_s); } Task Raven::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); SetHealthByDifficulty(2); _scoreValue = 300; _originPos = _lastPos = _targetPos = _pos; async_await RequestMetadataAsync("Enemy/Raven"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Idle); async_return true; } void Raven::OnUpdate(float timeMult) { OnUpdateHitbox(); HandleBlinking(timeMult); UpdateFrozenState(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_currentTransition == nullptr) { if (_attackTime > 0.0f) { _attackTime -= timeMult; } else { if (_attacking) { SetAnimation(AnimState::Idle); _targetPos = _originPos; _attackTime = 120.0f; _attacking = false; } else { AttackNearestPlayer(); } } } _anglePhase += timeMult * 0.04f; if ((_targetPos - _lastPos).Length() > 5.0f) { Vector2f dir = (_targetPos - _lastPos).Normalized(); Vector2f speed = { lerpByTime(_lastSpeed.X, _attacking ? 4.0f : 2.0f, 0.1f, timeMult), lerpByTime(_lastSpeed.Y, _attacking ? 4.0f : 2.0f, 0.1f, timeMult) }; _lastPos.X += dir.X * speed.X * timeMult; _lastPos.Y += dir.Y * speed.Y * timeMult; _lastSpeed = speed; bool willFaceLeft = (speed.X < 0.0f); if (IsFacingLeft() != willFaceLeft) { SetTransition(AnimState::TransitionTurn, false, [this, willFaceLeft]() { SetFacingLeft(willFaceLeft); }); } } MoveInstantly(_lastPos + Vector2f(0.0f, sinf(_anglePhase) * 6.0f), MoveType::Absolute | MoveType::Force); } bool Raven::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } void Raven::AttackNearestPlayer() { bool found = false; Vector2f foundPos = Vector2f::Zero; float targetDistance = 300.0f; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); float distance = (_lastPos - newPos).Length(); if (distance < targetDistance) { foundPos = newPos; targetDistance = distance; found = true; } } if (found) { SetAnimation(AnimState::TransitionAttack); // Can't fly into the water float waterLevel = _levelHandler->GetWaterLevel() - 12.0f; if (foundPos.Y > waterLevel - 8.0f) { foundPos.Y = waterLevel - 8.0f; } _targetPos = foundPos; _targetPos.Y -= 30.0f; _attackTime = 80.0f; _attacking = true; PlaySfx("Attack"_s, 0.7f, Random().NextFloat(1.4f, 1.8f)); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Raven.h000066400000000000000000000011251512772601700255760ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Raven */ class Raven : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Raven(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: Vector2f _originPos, _lastPos, _targetPos, _lastSpeed; float _anglePhase; float _attackTime; bool _attacking; void AttackNearestPlayer(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Skeleton.cpp000066400000000000000000000036701512772601700266510ustar00rootroot00000000000000#include "Skeleton.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Skeleton::Skeleton() : _stuck(false) { } void Skeleton::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Skeleton"_s); } Task Skeleton::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(3); _scoreValue = 200; SetState(ActorState::CollideWithTilesetReduced, true); async_await RequestMetadataAsync("Enemy/Skeleton"_s); SetFacingLeft(Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; PlaceOnGround(); async_return true; } void Skeleton::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; _stuck = true; } } else { _stuck = false; } } } void Skeleton::OnUpdateHitbox() { UpdateHitbox(30, 30); } void Skeleton::OnHealthChanged(ActorBase* collider) { CreateSpriteDebris((AnimState)2, Random().Next(1, 3)); // Bone EnemyBase::OnHealthChanged(collider); } bool Skeleton::OnPerish(ActorBase* collider) { // TODO: Sound of bones // TODO: Use CreateDeathDebris(collider); instead? CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); if (_frozenTimeLeft <= 0.0f) { CreateSpriteDebris((AnimState)2, Random().Next(9, 12)); // Bone CreateSpriteDebris((AnimState)2, 1); // Skull } TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Skeleton.h000066400000000000000000000011371512772601700263120ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Skeleton */ class Skeleton : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Skeleton(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnHealthChanged(ActorBase* collider) override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 0.7f; bool _stuck; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Sparks.cpp000066400000000000000000000041611512772601700263240ustar00rootroot00000000000000#include "Sparks.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Sparks::Sparks() { } void Sparks::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Sparks"_s); } Task Sparks::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::ApplyGravitation, false); SetHealthByDifficulty(1); _scoreValue = 100; async_await RequestMetadataAsync("Enemy/Sparks"_s); SetFacingLeft(true); SetAnimation(AnimState::Idle); async_return true; } void Sparks::OnUpdate(float timeMult) { OnUpdateHitbox(); HandleBlinking(timeMult); UpdateFrozenState(timeMult); if (_frozenTimeLeft > 0.0f) { return; } MoveInstantly(_speed * timeMult, MoveType::Relative | MoveType::Force); Vector2f targetPos; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0 || player->IsInvulnerable()) { continue; } targetPos = player->GetPos(); Vector2f direction = (targetPos - _pos); float length = direction.Length(); if (length < 180.0f && targetPos.Y < _levelHandler->GetWaterLevel()) { if (length > 100.0f) { direction.Normalize(); float maxSpeed = DefaultSpeed; switch (_levelHandler->GetDifficulty()) { case GameDifficulty::Normal: maxSpeed += 1.0f; case GameDifficulty::Hard: maxSpeed += 2.0f; } _speed.X = lerpByTime(_speed.X, direction.X * maxSpeed, 0.04f, timeMult); _speed.Y = lerpByTime(_speed.Y, direction.Y * maxSpeed, 0.04f, timeMult); SetFacingLeft(_speed.X >= 0.0f); } return; } } _speed.X = lerpByTime(_speed.X, 0.0f, 0.04f, timeMult); _speed.Y = lerpByTime(_speed.Y, 0.0f, 0.04f, timeMult); } bool Sparks::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Sparks.h000066400000000000000000000007611512772601700257730ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Sparks */ class Sparks : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Sparks(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 2.0f; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Sucker.cpp000066400000000000000000000050471512772601700263210ustar00rootroot00000000000000#include "Sucker.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Sucker::Sucker() : _cycle(0), _cycleTimer(0.0f), _stuck(false) { } void Sucker::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Sucker"_s); } Task Sucker::OnActivatedAsync(const ActorActivationDetails& details) { Direction parentLastHitDir = (Direction)details.Params[0]; SetHealthByDifficulty(1); _scoreValue = 100; _maxHealth = 4; async_await RequestMetadataAsync("Enemy/Sucker"_s); if (parentLastHitDir != Direction::None) { SetFacingLeft(parentLastHitDir == Direction::Left); _health = 1; SetState(ActorState::ApplyGravitation, false); SetAnimation(AnimState::Walk); SetTransition((AnimState)1073741824, false, [this]() { _speed.X = 0; SetAnimation(AnimState::Walk); SetState(ActorState::ApplyGravitation, true); }); if (parentLastHitDir == Direction::Left || parentLastHitDir == Direction::Right) { _speed.X = (parentLastHitDir == Direction::Left ? -3.0f : 3.0f); } PlaySfx("Deflate"_s); } else { SetAnimation(AnimState::Walk); _health = 4; PlaceOnGround(); } async_return true; } void Sucker::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (_currentTransition == nullptr && std::abs(_speed.X) > 0 && GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X *= -1; _stuck = true; } } else { _stuck = false; } } if (_currentTransition == nullptr && _frozenTimeLeft <= 0.0f) { if (_cycleTimer < 0.0f) { _cycle++; if (_cycle == 12) { _cycle = 0; } if (_cycle == 0) { PlaySfx("Walk1"_s, 0.2f); } else if (_cycle == 6) { PlaySfx("Walk2"_s, 0.2f); } else if (_cycle == 2 || _cycle == 7) { PlaySfx("Walk3"_s, 0.2f); } if ((_cycle >= 4 && _cycle < 7) || _cycle >= 9) { _speed.X = 0.6f * (IsFacingLeft() ? -1 : 1); } else { _speed.X = 0; } _cycleTimer = 5.0f; } else { _cycleTimer -= timeMult; } } } bool Sucker::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Sucker.h000066400000000000000000000007761512772601700257720ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Sucker */ class Sucker : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Sucker(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: std::int32_t _cycle; float _cycleTimer; bool _stuck; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/SuckerFloat.cpp000066400000000000000000000047641512772601700273140ustar00rootroot00000000000000#include "SuckerFloat.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "Sucker.h" #include "../Player.h" #include "../Solid/PushableBox.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { SuckerFloat::SuckerFloat() : _phase(0.0f) { } void SuckerFloat::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/SuckerFloat"_s); PreloadMetadataAsync("Enemy/Sucker"_s); } Task SuckerFloat::OnActivatedAsync(const ActorActivationDetails& details) { _originPos = Vector2i(details.Pos.X, details.Pos.Y); SetState(ActorState::ApplyGravitation, false); SetHealthByDifficulty(1); _scoreValue = 200; async_await RequestMetadataAsync("Enemy/SuckerFloat"_s); SetAnimation(AnimState::Idle); async_return true; } void SuckerFloat::OnUpdate(float timeMult) { if (_frozenTimeLeft <= 0.0f) { _phase = fmodf(_phase + 0.05f * timeMult, fTwoPi); MoveInstantly(Vector2f(_originPos.X + 10 * cosf(_phase), _originPos.Y + 10 * sinf(_phase)), MoveType::Absolute | MoveType::Force); SetFacingLeft(_phase < fPiOver2 || _phase > 3 * fPiOver2); } EnemyBase::OnUpdate(timeMult); } bool SuckerFloat::OnPerish(ActorBase* collider) { bool shouldDestroy = (_frozenTimeLeft > 0.0f); if (auto* player = runtime_cast(collider)) { if (player->GetSpecialMove() != Player::SpecialMoveType::None) { shouldDestroy = true; } } else if (runtime_cast(collider) || runtime_cast(collider)) { shouldDestroy = true; } if (shouldDestroy) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); } else { Vector2f shotSpeed; if (runtime_cast(collider)) { shotSpeed = _pos - collider->GetPos(); } else { shotSpeed = collider->GetSpeed(); } Direction dir; if (std::abs(shotSpeed.X) > 0.2f) { dir = (shotSpeed.X > 0.0f ? Direction::Right : Direction::Left); } else { dir = (shotSpeed.Y > 0.0f ? Direction::Down : Direction::Up); } std::shared_ptr sucker = std::make_shared(); std::uint8_t suckerParams[1] = { (std::uint8_t)dir }; sucker->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), suckerParams )); _levelHandler->AddActor(sucker); } return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/SuckerFloat.h000066400000000000000000000007751512772601700267570ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Floating sucker */ class SuckerFloat : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: SuckerFloat(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: float _phase; Vector2i _originPos; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Turtle.cpp000066400000000000000000000152011512772601700263350ustar00rootroot00000000000000#include "Turtle.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "TurtleShell.h" #include "../Explosion.h" #include "../Player.h" #include "../Solid/PushableBox.h" #include "../Weapons/ShieldFireShot.h" #include "../Weapons/Thunderbolt.h" #include "../Weapons/ToasterShot.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Enemies { static constexpr AnimState WithdrawStart = (AnimState)20; static constexpr AnimState WithdrawStartFast = (AnimState)21; static constexpr AnimState WithdrawInProgress = (AnimState)22; static constexpr AnimState WithdrawEnd = (AnimState)23; Turtle::Turtle() : _isAttacking(false), _isTurning(false), _isWithdrawn(false), _isDodging(false), _dodgeCooldown(0.0f) { } void Turtle::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { case 0: default: PreloadMetadataAsync("Enemy/Turtle"_s); PreloadMetadataAsync("Enemy/TurtleShell"_s); break; case 1: // Xmas PreloadMetadataAsync("Enemy/TurtleXmas"_s); PreloadMetadataAsync("Enemy/TurtleShellXmas"_s); break; } } Task Turtle::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(1); _scoreValue = 100; SetState(ActorState::CollideWithTilesetReduced, true); _theme = details.Params[0]; switch (_theme) { case 0: default: async_await RequestMetadataAsync("Enemy/Turtle"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Enemy/TurtleXmas"_s); break; } SetFacingLeft(nCine::Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; PlaceOnGround(); async_return true; } void Turtle::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (GetState(ActorState::CanJump) && _currentTransition == nullptr) { if (ShouldDodge()) { if (!_isDodging) { _isDodging = true; _dodgeCooldown = Random().Next(80.0f, 160.0f); _canHurtPlayer = false; _speed.X = 0; SetAnimation(WithdrawInProgress); SetTransition(WithdrawStartFast, false); PlaySfx("Withdraw"_s, 0.2f); } } else if (_isDodging) { _dodgeCooldown -= timeMult; if (_dodgeCooldown <= 0.0f) { _isDodging = false; SetAnimation(AnimState::Walk); SetTransition(WithdrawEnd, false, [this]() { _canHurtPlayer = true; _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; }); PlaySfx("WithdrawEnd"_s, 0.2f); } } if (std::abs(_speed.X) > 0.0f && !CanMoveToPosition(_speed.X * 4, 0)) { _isTurning = true; _canHurtPlayer = false; _speed.X = 0; SetTransition(WithdrawStart, false, [this]() { HandleTurn(true); }); PlaySfx("Withdraw"_s, 0.2f); } } if (_isAttacking) { // Turtles attack only with animation, so check collisions every frame SetState(ActorState::IsDirty, true); } else if (!_isTurning && !_isWithdrawn && !_isDodging && !_currentTransition) { AABBf aabb = AABBInner + Vector2f(_speed.X * 32, 0); TileCollisionParams params = { TileDestructType::None, false }; if (_levelHandler->TileMap()->IsTileEmpty(aabb, params)) { _levelHandler->GetCollidingPlayers(aabb + Vector2f(_speed.X * 32, 0), [this](ActorBase* player) -> bool { if (player->GetHealth() > 0 && !player->IsInvulnerable()) { Attack(); return false; } return true; }); } } } void Turtle::OnUpdateHitbox() { UpdateHitbox(24, 24); } bool Turtle::OnPerish(ActorBase* collider) { // Animation should be paused only if enemy is frozen bool shouldDestroy = (_frozenTimeLeft > 0.0f); if (auto* player = runtime_cast(collider)) { if (player->GetSpecialMove() != Player::SpecialMoveType::None) { shouldDestroy = true; } } else if (runtime_cast(collider) || runtime_cast(collider) || runtime_cast(collider) || runtime_cast(collider)) { shouldDestroy = true; } if (shouldDestroy) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); // Add score also for turtle shell _scoreValue += 100; TryGenerateRandomDrop(); } else { float shellSpeedY; if (_pos.Y > _levelHandler->GetWaterLevel()) { shellSpeedY = -0.65f; } else if (_levelHandler->IsReforged()) { shellSpeedY = -1.1f; } else { shellSpeedY = -0.98f; } std::shared_ptr shell = std::make_shared(); uint8_t shellParams[9]; *(float*)&shellParams[0] = _speed.X * 1.1f; *(float*)&shellParams[4] = shellSpeedY; shellParams[8] = _theme; shell->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), shellParams )); _levelHandler->AddActor(shell); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::SmokeGray); } return EnemyBase::OnPerish(collider); } void Turtle::HandleTurn(bool isFirstPhase) { if (_isTurning) { if (isFirstPhase) { SetFacingLeft(!IsFacingLeft()); SetTransition(WithdrawEnd, false, [this]() { HandleTurn(false); }); PlaySfx("WithdrawEnd"_s, 0.2f); _isWithdrawn = true; } else { _canHurtPlayer = true; _isWithdrawn = false; _isTurning = false; _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; } } } void Turtle::Attack() { _speed.X = 0; _isAttacking = true; PlaySfx("Attack"_s); SetTransition(AnimState::TransitionAttack, false, [this]() { _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; _isAttacking = false; // TODO: Bad timing PlaySfx("Attack2"_s); }); } bool Turtle::ShouldDodge() const { constexpr float Distance = 96.0f; bool shouldWithdraw = false; if (_levelHandler->GetDifficulty() != GameDifficulty::Easy) { AABBf withdrawAabb = AABB; withdrawAabb.T = withdrawAabb.B - 30.0f; withdrawAabb.L -= Distance; withdrawAabb.R += Distance; _levelHandler->FindCollisionActorsByAABB(this, withdrawAabb, [this, &shouldWithdraw](ActorBase* actor) { if (auto* shot = runtime_cast(actor)) { float xSpeed = shot->GetSpeed().X; float x = shot->GetPos().X; float xSelf = _pos.X; // Check if the shot is moving towards the boss if (std::abs(xSpeed) > 0.0f && std::signbit(xSelf - x) == std::signbit(xSpeed)) { shouldWithdraw = true; return false; } } return true; }); } return shouldWithdraw; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Turtle.h000066400000000000000000000013471512772601700260100ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Turtle */ class Turtle : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Turtle(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 1.0f; std::uint8_t _theme; bool _isAttacking; bool _isTurning; bool _isWithdrawn; bool _isDodging; float _dodgeCooldown; void HandleTurn(bool isFirstPhase); void Attack(); bool ShouldDodge() const; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/TurtleShell.cpp000066400000000000000000000140351512772601700273310ustar00rootroot00000000000000#include "TurtleShell.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Solid/AmmoCrate.h" #include "../Solid/CrateContainer.h" #include "../Solid/GemCrate.h" #include "../Weapons/ShotBase.h" #include "../Weapons/FreezerShot.h" #include "../Weapons/ShieldFireShot.h" #include "../Weapons/Thunderbolt.h" #include "../Weapons/ToasterShot.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Enemies { TurtleShell::TurtleShell() : _lastAngle(0.0f) { } void TurtleShell::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[8]; switch (theme) { case 0: default: PreloadMetadataAsync("Enemy/TurtleShell"_s); break; case 1: // Xmas PreloadMetadataAsync("Enemy/TurtleShellXmas"_s); break; case 2: // Tough (Boss) PreloadMetadataAsync("Boss/TurtleBossShell"_s); break; } } Task TurtleShell::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(1); _scoreValue = 100; SetState(ActorState::CollideWithTilesetReduced | ActorState::SkipPerPixelCollisions, true); if (_levelHandler->IsReforged()) { SetState(ActorState::CollideWithSolidObjects | ActorState::CollideWithSolidObjectsBelow, true); } _speed.X = *(float*)&details.Params[0]; _externalForce.Y = *(float*)&details.Params[4]; uint8_t theme = details.Params[8]; switch (theme) { case 0: default: async_await RequestMetadataAsync("Enemy/TurtleShell"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Enemy/TurtleShellXmas"_s); break; case 2: // Tough (Boss) async_await RequestMetadataAsync("Boss/TurtleBossShell"_s); break; } SetAnimation(AnimState::Default); _canHurtPlayer = false; _friction = _levelHandler->GetGravity() * 0.04f; _elasticity = 0.5f; _health = 8; if (std::abs(_speed.X) > 0.0f || std::abs(_externalForce.Y) > 0.0f) { PlaySfx("Fly"_s); StartBlinking(); } async_return true; } void TurtleShell::OnUpdate(float timeMult) { _speed.X = lerpByTime(_speed.X, 0.0f, _friction, timeMult); double posYBefore = _pos.Y; TileCollisionParams params = { TileDestructType::Weapon | TileDestructType::Special, _speed.Y >= 0.0f, WeaponType::Blaster, 1 }; TryStandardMovement(timeMult, params); OnUpdateHitbox(); UpdateFrozenState(timeMult); HandleBlinking(timeMult); if (posYBefore - _pos.Y > 0.5 && std::abs(_speed.Y) < 1) { _speed.X = std::max(std::abs(_speed.X) - 10.0f * _friction, 0.0f) * (_speed.X < 0.0f ? -1.0f : 1.0f); } if (_levelHandler->IsReforged()) { // Enable tilting only if Reforged _lastAngle = lerp(_lastAngle, _speed.X * 0.06f, timeMult * 0.2f); _renderer.setRotation(_lastAngle); } } void TurtleShell::OnUpdateHitbox() { UpdateHitbox(30, 16); } bool TurtleShell::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } bool TurtleShell::OnHandleCollision(std::shared_ptr other) { EnemyBase::OnHandleCollision(other); if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { if (runtime_cast(shotBase)) { return false; } if (auto* toasterShot = runtime_cast(shotBase)) { DecreaseHealth(INT32_MAX, toasterShot); return true; } if (auto* shieldFireShot = runtime_cast(shotBase)) { DecreaseHealth(INT32_MAX, shieldFireShot); return true; } float shotSpeed; if (runtime_cast(shotBase)) { shotSpeed = (_pos.X < shotBase->GetPos().X ? -8.0f : 8.0f); } else { shotSpeed = shotBase->GetSpeed().X; } _speed.X = std::max(4.0f, std::abs(shotSpeed)) * (shotSpeed < 0.0f ? -0.6f : 0.6f); PlaySfx("Fly"_s); } } else if (auto* shell = runtime_cast(other.get())) { auto otherSpeed = shell->GetSpeed(); if (std::abs(otherSpeed.Y - _speed.Y) > 1.0f && otherSpeed.Y > 0.0f) { DecreaseHealth(10, this); } else if (std::abs(_speed.X) > std::abs(otherSpeed.X)) { // Handle this only in the faster of the two //pos.X = collider.Transform.Pos.X + (speedX >= 0 ? -1f : 1f) * (currentAnimation.Base.FrameDimensions.X + 1); float totalSpeed = std::abs(_speed.X) + std::abs(otherSpeed.X); shell->_speed.X = totalSpeed / 2 * (_speed.X < 0.0f ? -1.0f : 1.0f); _speed.X = totalSpeed / 2.0f * (_speed.X < 0.0f ? 1.0f : -1.0f); shell->DecreaseHealth(1, this); PlaySfx("ImpactShell"_s, 0.8f); return true; } } else if (auto* enemyBase = runtime_cast(other.get())) { if (enemyBase->CanCollideWithShots) { float absSpeed = std::abs(_speed.X); if (absSpeed > 2.0f) { _speed.X = std::max(absSpeed, 2.0f) * (_speed.X < 0.0f ? 1.0f : -1.0f); if (!enemyBase->GetState(ActorState::IsInvulnerable)) { enemyBase->DecreaseHealth(1, this); return true; } } } } else if (auto* crateContainer = runtime_cast(other.get())) { float absSpeed = std::abs(_speed.X); if (absSpeed > 2.0f) { _speed.X = std::max(absSpeed, 2.0f) * (_speed.X >= 0.0f ? -1.0f : 1.0f); crateContainer->DecreaseHealth(1, this); return true; } } else if (auto* ammoCrate = runtime_cast(other.get())) { float absSpeed = std::abs(_speed.X); if (absSpeed > 2.0f) { _speed.X = std::max(absSpeed, 2.0f) * (_speed.X >= 0.0f ? -1.0f : 1.0f); ammoCrate->DecreaseHealth(1, this); return true; } } else if (auto* gemCrate = runtime_cast(other.get())) { float absSpeed = std::abs(_speed.X); if (absSpeed > 2.0f) { _speed.X = std::max(absSpeed, 2.0f) * (_speed.X >= 0.0f ? -1.0f : 1.0f); gemCrate->DecreaseHealth(1, this); return true; } } return false; } void TurtleShell::OnHitFloor(float timeMult) { if (std::abs(_speed.Y) > 1.0f) { PlaySfx("ImpactGround"_s); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/TurtleShell.h000066400000000000000000000011721512772601700267740ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Turtle shell */ class TurtleShell : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: TurtleShell(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; bool OnHandleCollision(std::shared_ptr other) override; void OnHitFloor(float timeMult) override; private: float _lastAngle; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/TurtleTough.cpp000066400000000000000000000036111512772601700273460ustar00rootroot00000000000000#include "TurtleTough.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../Solid/PushableBox.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { TurtleTough::TurtleTough() : _stuck(false) { } void TurtleTough::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/TurtleTough"_s); } Task TurtleTough::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(4); _scoreValue = 500; SetState(ActorState::CollideWithTilesetReduced, true); async_await RequestMetadataAsync("Enemy/TurtleTough"_s); SetFacingLeft(nCine::Random().NextBool()); SetAnimation(AnimState::Walk); _speed.X = (IsFacingLeft() ? -1 : 1) * DefaultSpeed; PlaceOnGround(); async_return true; } void TurtleTough::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } if (GetState(ActorState::CanJump)) { if (!CanMoveToPosition(_speed.X * 4.0f, 0)) { if (_stuck) { MoveInstantly(Vector2f(0.0f, -2.0f), MoveType::Relative | MoveType::Force); } else { SetFacingLeft(!IsFacingLeft()); _speed.X = (IsFacingLeft() ? -1.0f : 1.0f) * DefaultSpeed; _stuck = true; } } else { _stuck = false; } } } void TurtleTough::OnUpdateHitbox() { UpdateHitbox(30, 40); } bool TurtleTough::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); if (!runtime_cast(collider)) { // Show explosion only if it was not killed by pushable box Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::Large); } TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/TurtleTough.h000066400000000000000000000010631512772601700270120ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Tough turtle */ class TurtleTough : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: TurtleTough(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: static constexpr float DefaultSpeed = 1.0f; bool _stuck; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/TurtleTube.cpp000066400000000000000000000042041512772601700271560ustar00rootroot00000000000000#include "TurtleTube.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { TurtleTube::TurtleTube() : _onWater(false), _phase(0.0f) { } void TurtleTube::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/TurtleTube"_s); } Task TurtleTube::OnActivatedAsync(const ActorActivationDetails& details) { SetHealthByDifficulty(2); _scoreValue = 200; async_await RequestMetadataAsync("Enemy/TurtleTube"_s); SetAnimation(AnimState::Idle); float adjustedWaterLevel = _levelHandler->GetWaterLevel() + WaterDifference; if (adjustedWaterLevel <= _pos.Y) { // Water is above the enemy, it's floating on the water _pos.Y = adjustedWaterLevel; SetState(ActorState::ApplyGravitation, false); _onWater = true; } else { // Water is below the enemy, apply gravitation and pause the animation _renderer.AnimPaused = true; } async_return true; } void TurtleTube::OnUpdate(float timeMult) { EnemyBase::OnUpdate(timeMult); float adjustedWaterLevel = _levelHandler->GetWaterLevel() + WaterDifference; if (_onWater) { // Floating on the water _speed.X = sinf(_phase); _phase += timeMult * 0.02f; if (adjustedWaterLevel < _pos.Y) { // Water is above the enemy, return the enemy on the surface _pos.Y = adjustedWaterLevel; } else if (adjustedWaterLevel > _pos.Y) { // Water is below the enemy, apply gravitation and pause the animation _speed.X = 0.0f; SetState(ActorState::ApplyGravitation, true); _onWater = false; } } else { if (adjustedWaterLevel <= _pos.Y) { // Water is above the enemy, return the enemy on the surface _pos.Y = adjustedWaterLevel; SetState(ActorState::ApplyGravitation, false); _onWater = true; _renderer.AnimPaused = false; } else { _renderer.AnimPaused = true; } } } bool TurtleTube::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); TryGenerateRandomDrop(); return EnemyBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/TurtleTube.h000066400000000000000000000010461512772601700266240ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Tube turtle */ class TurtleTube : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: TurtleTube(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; bool OnPerish(ActorBase* collider) override; private: static constexpr float WaterDifference = -16.0f; bool _onWater; float _phase; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Witch.cpp000066400000000000000000000121071512772601700261360ustar00rootroot00000000000000#include "Witch.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Enemies { Witch::Witch() : _attackTime(0.0f), _playerHit(false) { } void Witch::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/Witch"_s); PreloadMetadataAsync("Interactive/PlayerFrog"_s); } Task Witch::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::ApplyGravitation, false); SetHealthByDifficulty(30); _scoreValue = 1000; async_await RequestMetadataAsync("Enemy/Witch"_s); SetAnimation(AnimState::Idle); async_return true; } void Witch::OnUpdate(float timeMult) { OnUpdateHitbox(); HandleBlinking(timeMult); UpdateFrozenState(timeMult); if (_frozenTimeLeft > 0.0f) { return; } MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); if (_playerHit) { if (_attackTime > 0.0f) { _attackTime -= timeMult; } else { EnemyBase::OnPerish(nullptr); } return; } if (_attackTime > 0.0f) { _attackTime -= timeMult; } Vector2f targetPos; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { targetPos = player->GetPos(); // Fly above the player targetPos.Y -= 100.0f; Vector2f direction = (_pos - targetPos); float length = direction.Length(); if (_attackTime <= 0.0f && length < 260.0f) { _attackTime = 450.0f; PlaySfx("MagicFire"_s); SetTransition(AnimState::TransitionAttack, true, [this]() { Vector2f bulletPos = Vector2f(_pos.X + (IsFacingLeft() ? -24.0f : 24.0f), _pos.Y); std::shared_ptr magicBullet = std::make_shared(this); magicBullet->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)bulletPos.X, (std::int32_t)bulletPos.Y, _renderer.layer() + 1) )); _levelHandler->AddActor(magicBullet); Explosion::Create(_levelHandler, Vector3i((std::int32_t)bulletPos.X, (std::int32_t)bulletPos.Y, _renderer.layer() + 2), Explosion::Type::TinyDark); }); } else if (length > 20.0f && length < 500.0f) { direction.Normalize(); _speed.X = (direction.X * DefaultSpeed + _speed.X) * 0.5f; _speed.Y = (direction.Y * DefaultSpeed + _speed.Y) * 0.5f; SetFacingLeft(_speed.X < 0.0f); return; } } _speed.X = 0.0f; _speed.Y = 0.0f; } void Witch::OnUpdateHitbox() { UpdateHitbox(30, 30); } bool Witch::OnPerish(ActorBase* collider) { // It must be done here, because the player may not exist after animation callback AddScoreToCollider(collider); _levelHandler->PlayCommonSfx("Splat"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); SetTransition(AnimState::TransitionDeath, false, [this, collider]() { EnemyBase::OnPerish(collider); }); CreateParticleDebrisOnPerish(collider); return false; } bool Witch::OnTileDeactivated() { // Witch cannot be deactivated return false; } void Witch::OnPlayerHit() { _playerHit = true; _attackTime = 400.0f; _speed.X = (IsFacingLeft() ? -9.0f : 9.0f); _speed.Y = -0.8f; PlaySfx("Laugh"_s); } Task Witch::MagicBullet::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::IsInvulnerable | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); _health = INT32_MAX; async_await RequestMetadataAsync("Enemy/Witch"_s); SetAnimation((AnimState)1073741828); async_return true; } void Witch::MagicBullet::OnUpdate(float timeMult) { MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); OnUpdateHitbox(); if (_time <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _time -= timeMult; FollowNearestPlayer(); } } void Witch::MagicBullet::OnUpdateHitbox() { UpdateHitbox(10, 10); } bool Witch::MagicBullet::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX); _owner->OnPlayerHit(); player->MorphTo(PlayerType::Frog); return true; } return false; } void Witch::MagicBullet::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.7f; light.Brightness = 0.4f; light.RadiusNear = 0.0f; light.RadiusFar = 30.0f; } void Witch::MagicBullet::FollowNearestPlayer() { bool found = false; Vector2f targetPos; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetHealth() <= 0) { continue; } Vector2f newPos = player->GetPos(); if (!found || (_pos - newPos).SqrLength() < (_pos - targetPos).SqrLength()) { targetPos = newPos; found = true; } } if (found) { Vector2f diff = (targetPos - _pos).Normalized(); Vector2f speed = (_speed + diff * 0.8f).Normalized(); _speed = speed * 5.0f; _renderer.setRotation(atan2f(_speed.Y, _speed.X)); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Enemies/Witch.h000066400000000000000000000024231512772601700256030ustar00rootroot00000000000000#pragma once #include "EnemyBase.h" namespace Jazz2::Actors::Enemies { /** @brief Witch */ class Witch : public EnemyBase { DEATH_RUNTIME_OBJECT(EnemyBase); public: Witch(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; bool OnTileDeactivated() override; private: static constexpr float DefaultSpeed = -4.0f; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class MagicBullet : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: MagicBullet(Witch* owner) : _owner(owner), _time(380.0f) { } bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; private: Witch* _owner; float _time; void FollowNearestPlayer(); }; #endif float _attackTime; bool _playerHit; void OnPlayerHit(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/000077500000000000000000000000001512772601700252725ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/AirboardGenerator.cpp000066400000000000000000000026051512772601700313730ustar00rootroot00000000000000#include "AirboardGenerator.h" #include "../../ILevelHandler.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Environment { AirboardGenerator::AirboardGenerator() : _timeLeft(0.0f), _active(false) { } Task AirboardGenerator::OnActivatedAsync(const ActorActivationDetails& details) { _delay = details.Params[0]; _active = true; async_await RequestMetadataAsync("Object/Airboard"_s); SetAnimation(AnimState::Default); async_return true; } void AirboardGenerator::OnUpdate(float timeMult) { if (!_active) { if (_timeLeft <= 0.0f) { _active = true; _renderer.setDrawEnabled(true); } _timeLeft -= timeMult; } } bool AirboardGenerator::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { if (_active && player->SetModifier(Player::Modifier::Airboard)) { _active = false; _renderer.setDrawEnabled(false); _timeLeft = _delay * FrameTimer::FramesPerSecond; Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), Explosion::Type::Generator); } return true; } return ActorBase::OnHandleCollision(std::move(other)); } void AirboardGenerator::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/Airboard"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/AirboardGenerator.h000066400000000000000000000010751512772601700310400ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Airboard generator */ class AirboardGenerator : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: AirboardGenerator(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: std::uint8_t _delay; float _timeLeft; bool _active; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/AmbientBubbles.cpp000066400000000000000000000052531512772601700306610ustar00rootroot00000000000000#include "AmbientBubbles.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Environment { AmbientBubbles::AmbientBubbles() : _cooldown(0.0f), _bubblesLeft(0), _delay(0.0f) { } void AmbientBubbles::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Common/AmbientBubbles"_s); } Task AmbientBubbles::OnActivatedAsync(const ActorActivationDetails& details) { _speed = details.Params[0]; SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Common/AmbientBubbles"_s); async_return true; } void AmbientBubbles::OnUpdate(float timeMult) { _cooldown -= timeMult; if (_cooldown <= 0.0f) { SpawnBubbles(_bubblesLeft); _bubblesLeft = _speed; _cooldown = BaseTime; } else if (_bubblesLeft > 0) { _delay -= timeMult; if (_delay <= 0.0f) { SpawnBubbles(1); _bubblesLeft--; _delay = BaseTime / _speed; } } } void AmbientBubbles::SpawnBubbles(std::int32_t count) { if (count <= 0) { return; } auto tilemap = _levelHandler->TileMap(); if (tilemap != nullptr) { auto* res = _metadata->FindAnimation(AnimState::Default); // AmbientBubbles if (res != nullptr && res->Base->TextureDiffuse != nullptr) { Vector2i texSize = res->Base->TextureDiffuse->GetSize(); Vector2i size = res->Base->FrameDimensions; Vector2i frameConf = res->Base->FrameConfiguration; for (int i = 0; i < count; i++) { float scale = Random().NextFloat(0.3f, 1.0f); float speedX = Random().NextFloat(-0.5f, 0.5f) * scale; float speedY = Random().NextFloat(-3.0f, -2.0f) * scale; float accel = Random().NextFloat(-0.008f, -0.001f) * scale; int frame = res->FrameOffset + Random().Next(0, res->FrameCount); Tiles::TileMap::DestructibleDebris debris = { }; debris.Pos = _pos; debris.Depth = _renderer.layer(); debris.Size = Vector2f((float)size.X, (float)size.Y); debris.Speed = Vector2f(speedX, speedY); debris.Acceleration = Vector2f(0.0f, accel); debris.Scale = scale; debris.Alpha = 0.9f; debris.AlphaSpeed = -0.009f; debris.Time = 110.0f; debris.TexScaleX = (size.X / float(texSize.X)); debris.TexBiasX = ((float)(frame % frameConf.X) / frameConf.X); debris.TexScaleY = (size.Y / float(texSize.Y)); debris.TexBiasY = ((float)(frame / frameConf.X) / frameConf.Y); debris.DiffuseTexture = res->Base->TextureDiffuse.get(); tilemap->CreateDebris(debris); } } } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/AmbientBubbles.h000066400000000000000000000011341512772601700303200ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Ambient bubbles */ class AmbientBubbles : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: AmbientBubbles(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: static constexpr float BaseTime = 20.0f; uint8_t _speed; float _cooldown; std::int32_t _bubblesLeft; float _delay; void SpawnBubbles(std::int32_t count); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/AmbientSound.cpp000066400000000000000000000023151512772601700303670ustar00rootroot00000000000000#include "AmbientSound.h" #include "../../ILevelHandler.h" #include "../Player.h" namespace Jazz2::Actors::Environment { AmbientSound::AmbientSound() { } AmbientSound::~AmbientSound() { // TODO: Fade-out if (_sound != nullptr) { _sound->stop(); _sound = nullptr; } } void AmbientSound::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Common/AmbientSound"_s); } Task AmbientSound::OnActivatedAsync(const ActorActivationDetails& details) { // TODO: Implement Fade:1|Sine:1 _sfx = details.Params[0]; _gain = 0.2f * (details.Params[1] / 255.0f); SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Common/AmbientSound"_s); switch (_sfx) { case 0: _sound = PlaySfx("AmbientWind"_s, _gain); break; case 1: _sound = PlaySfx("AmbientFire"_s, _gain); break; case 2: _sound = PlaySfx("AmbientScienceNoise"_s, _gain); break; } // TODO: Fade-in if (_sound != nullptr) { _sound->setLooping(true); } async_return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/AmbientSound.h000066400000000000000000000007501512772601700300350ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Ambient sound */ class AmbientSound : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: AmbientSound(); ~AmbientSound(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; private: std::shared_ptr _sound; std::uint8_t _sfx; float _gain; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Bird.cpp000066400000000000000000000135061512772601700266630ustar00rootroot00000000000000#include "Bird.h" #include "../../ILevelHandler.h" #include "../Player.h" #include "../Enemies/EnemyBase.h" #include "../Weapons/BlasterShot.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Environment { Bird::Bird() : _owner(nullptr), _fireCooldown(60.0f), _attackTime(0.0f) { } void Bird::Preload(const ActorActivationDetails& details) { std::uint8_t type = details.Params[0]; switch (type) { case 0: // Chuck (red) PreloadMetadataAsync("Object/BirdChuck"_s); break; case 1: // Birdy (yellow) PreloadMetadataAsync("Object/BirdBirdy"_s); break; } } Task Bird::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); _type = details.Params[0]; std::uint8_t playerIndex = details.Params[1]; auto players = _levelHandler->GetPlayers(); for (auto* player : _levelHandler->GetPlayers()) { if (player->GetPlayerIndex() == playerIndex) { _owner = player; break; } } switch (_type) { case 0: // Chuck (red) async_await RequestMetadataAsync("Object/BirdChuck"_s); break; case 1: // Birdy (yellow) async_await RequestMetadataAsync("Object/BirdBirdy"_s); break; } SetAnimation(AnimState::Idle); async_return true; } void Bird::OnUpdate(float timeMult) { if (_owner == nullptr) { // Fly away if (_fireCooldown <= 0.0f) { DecreaseHealth(INT32_MAX); } else { _fireCooldown -= timeMult; MoveInstantly(Vector2f((IsFacingLeft() ? -8.0f : 8.0f) * timeMult, -1.0f * timeMult), MoveType::Relative | MoveType::Force); _renderer.setScale(_renderer.scale() + 0.014f * timeMult); } return; } if (_type == 1 && _attackTime > 0.0f) { // Attack MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); _attackTime -= timeMult; if (_attackTime <= 0.0f) { SetAnimation(AnimState::Idle); SetState(ActorState::CollideWithOtherActors, false); _renderer.setRotation(0.0f); } } else { // Follow player Vector2f targetPos = _owner->GetPos(); bool playerFacingLeft = _owner->IsFacingLeft(); if (playerFacingLeft) { targetPos.X += 55.0f; } else { targetPos.X -= 55.0f; } targetPos.Y -= 50.0f; if (IsFacingLeft() != playerFacingLeft) { SetFacingLeft(playerFacingLeft); _fireCooldown = 40.0f; } targetPos.X = lerp(_pos.X, targetPos.X, 0.02f * timeMult); targetPos.Y = lerp(_pos.Y, targetPos.Y, 0.02f * timeMult); MoveInstantly(targetPos, MoveType::Absolute | MoveType::Force); } // Fire if (_fireCooldown <= 0.0f) { TryFire(); } else { _fireCooldown -= timeMult; } } void Bird::OnAnimationFinished() { ActorBase::OnAnimationFinished(); PlaySfx("Fly"_s, 0.3f); } bool Bird::OnHandleCollision(std::shared_ptr other) { if (_attackTime > 0.0f && !other->IsInvulnerable()) { if (auto* enemy = runtime_cast(other.get())) { enemy->DecreaseHealth(1, this); SetAnimation(AnimState::Idle); _attackTime = 0.0f; SetState(ActorState::CollideWithOtherActors, false); _renderer.setRotation(0.0f); return true; } } return ActorBase::OnHandleCollision(std::move(other)); } void Bird::FlyAway() { _owner = nullptr; _fireCooldown = 300.0f; if (_attackTime > 0.0f) { SetAnimation(AnimState::Idle); _attackTime = 0.0f; SetState(ActorState::CollideWithOtherActors, false); _renderer.setRotation(0.0f); } } void Bird::TryFire() { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 260.0f, [this](ActorBase* actor) { if (auto* enemy = runtime_cast(actor)) { Vector2f newPos = enemy->GetPos(); if (IsFacingLeft() ? (newPos.X > _pos.X) : (newPos.X < _pos.X)) { return true; } switch (_type) { case 0: { // Chuck (red) SetState(ActorState::CollideWithTileset, true); TileCollisionParams params = { TileDestructType::None, true }; if (_levelHandler->IsPositionEmpty(this, AABBf(_pos.X - 2.0f, _pos.Y - 2.0f, _pos.X + 2.0f, _pos.Y + 2.0f), params)) { uint8_t shotParams[1] = { 0 }; std::shared_ptr sharedOwner = _owner->shared_from_this(); std::shared_ptr shot1 = std::make_shared(); shot1->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), shotParams )); shot1->OnFire(sharedOwner, _pos, _speed, 0.0f, IsFacingLeft()); _levelHandler->AddActor(shot1); std::shared_ptr shot2 = std::make_shared(); shot2->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2), shotParams )); shot2->OnFire(sharedOwner, _pos, _speed, IsFacingLeft() ? -0.18f : 0.18f, IsFacingLeft()); _levelHandler->AddActor(shot2); PlaySfx("Fire"_s, 0.5f); _fireCooldown = 32.0f; } SetState(ActorState::CollideWithTileset, false); break; } case 1: { // Birdy (yellow) SetAnimation(AnimState::Shoot); Vector2f attackSpeed = (newPos - _pos).Normalize(); _speed.X = attackSpeed.X * 6.0f; _speed.Y = attackSpeed.Y * 6.0f; float angle = atan2f(_speed.Y, _speed.X); if (IsFacingLeft()) { angle += fPi; } _renderer.setRotation(angle); float distance = (newPos - _pos).Length(); _attackTime = distance * 0.2f; _fireCooldown = 140.0f; SetState(ActorState::CollideWithOtherActors, true); break; } } } return true; }); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Bird.h000066400000000000000000000013111512772601700263170ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors { class Player; } namespace Jazz2::Actors::Environment { /** @brief Bird */ class Bird : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Bird(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); /** @brief Leaves the owner */ void FlyAway(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnAnimationFinished() override; private: std::uint8_t _type; Player* _owner; float _fireCooldown; float _attackTime; void TryFire(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/BirdCage.cpp000066400000000000000000000065531512772601700274470ustar00rootroot00000000000000#include "BirdCage.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Explosion.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" namespace Jazz2::Actors::Environment { BirdCage::BirdCage() : _activated(false) { } void BirdCage::Preload(const ActorActivationDetails& details) { uint8_t type = details.Params[0]; switch (type) { case 0: // Chuck (red) PreloadMetadataAsync("Object/BirdCageChuck"_s); PreloadMetadataAsync("Object/BirdChuck"_s); break; case 1: // Birdy (yellow) PreloadMetadataAsync("Object/BirdCageBirdy"_s); PreloadMetadataAsync("Object/BirdBirdy"_s); break; } } Task BirdCage::OnActivatedAsync(const ActorActivationDetails& details) { _renderer.setLayer(_renderer.layer() - 16); _type = details.Params[0]; _activated = (details.Params[1] != 0); SetState(ActorState::CollideWithSolidObjects | ActorState::IsSolidObject, !_activated); switch (_type) { case 0: // Chuck (red) async_await RequestMetadataAsync("Object/BirdCageChuck"_s); break; case 1: // Birdy (yellow) async_await RequestMetadataAsync("Object/BirdCageBirdy"_s); break; } SetAnimation(_activated ? AnimState::Activated : AnimState::Idle); async_return true; } bool BirdCage::OnHandleCollision(std::shared_ptr other) { if (!_activated) { if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { auto owner = shotBase->GetOwner(); if (owner != nullptr && TryApplyToPlayer(owner)) { shotBase->DecreaseHealth(1); return true; } } } else if (auto* tnt = runtime_cast(other.get())) { auto owner = tnt->GetOwner(); if (owner != nullptr && TryApplyToPlayer(owner)) { return true; } } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects() && TryApplyToPlayer(player)) { return true; } } } return ActorBase::OnHandleCollision(std::move(other)); } bool BirdCage::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } bool BirdCage::TryApplyToPlayer(Player* player) { if (!player->SpawnBird(_type, _pos)) { return false; } _activated = true; SetState(ActorState::CollideWithSolidObjects | ActorState::IsSolidObject, false); SetAnimation(AnimState::Activated); PlaySfx("Break"_s); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X - 12.0f), (std::int32_t)(_pos.Y - 6.0f), _renderer.layer() + 90), Explosion::Type::SmokeBrown); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X - 8.0f), (std::int32_t)(_pos.Y + 28.0f), _renderer.layer() + 90), Explosion::Type::SmokeBrown); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + 12.0f), (std::int32_t)(_pos.Y + 10.0f), _renderer.layer() + 90), Explosion::Type::SmokeBrown); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)(_pos.Y + 12.0f), _renderer.layer() + 110), Explosion::Type::SmokePoof); // Deactivate event in map auto eventMap = _levelHandler->EventMap(); if (eventMap != nullptr) { uint8_t eventParams[16] = { _type, 1 }; eventMap->StoreTileEvent(_originTile.X, _originTile.Y, EventType::BirdCage, ActorState::None, eventParams); } return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/BirdCage.h000066400000000000000000000011301512772601700270760ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Environment { /** @brief Bird cage */ class BirdCage : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: BirdCage(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; private: std::uint8_t _type; bool _activated; bool TryApplyToPlayer(Player* player); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Bomb.cpp000066400000000000000000000037411512772601700266620ustar00rootroot00000000000000#include "Bomb.h" #include "../../ILevelHandler.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Environment { Bomb::Bomb() : _timeLeft(Random().NextFloat(40.0f, 90.0f)) { } void Bomb::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { case 0: PreloadMetadataAsync("Object/Bomb"_s); break; case 1: PreloadMetadataAsync("Enemy/LizardFloat"_s); break; case 2: PreloadMetadataAsync("Enemy/LizardFloatXmas"_s); break; } } Task Bomb::OnActivatedAsync(const ActorActivationDetails& details) { std::uint8_t theme = details.Params[0]; SetFacingLeft(details.Params[1] != 0); _health = INT32_MAX; _elasticity = 0.3f; switch (theme) { case 0: async_await RequestMetadataAsync("Object/Bomb"_s); break; case 1: async_await RequestMetadataAsync("Enemy/LizardFloat"_s); break; case 2: async_await RequestMetadataAsync("Enemy/LizardFloatXmas"_s); break; } SetAnimation((AnimState)2); async_return true; } void Bomb::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); if (_frozenTimeLeft > 0.0f) { return; } _timeLeft -= timeMult; if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } } void Bomb::OnUpdateHitbox() { UpdateHitbox(6, 6); } bool Bomb::OnPerish(ActorBase* collider) { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 40.0f, [this](ActorBase* actor) { if (auto* player = runtime_cast(actor)) { if (!player->HasSugarRush()) { bool pushLeft = (_pos.X > player->GetPos().X); player->TakeDamage(1, pushLeft ? -8.0f : 8.0f); } } return true; }); // Explosion.Large is the same as Explosion.Bomb Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), Explosion::Type::Large); _levelHandler->PlayCommonSfx("Bomb"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); return ActorBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Bomb.h000066400000000000000000000007711512772601700263270ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Bomb */ class Bomb : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Bomb(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: float _timeLeft; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/BonusWarp.cpp000066400000000000000000000030341512772601700277160ustar00rootroot00000000000000#include "BonusWarp.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" namespace Jazz2::Actors::Environment { BonusWarp::BonusWarp() : _warpTarget(UINT8_MAX), _cost(UINT8_MAX), _setLaps(false), _fast(false) { } void BonusWarp::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/BonusWarp"_s); } Task BonusWarp::OnActivatedAsync(const ActorActivationDetails& details) { _warpTarget = details.Params[0]; _fast = (details.Params[1] != 0); _setLaps = details.Params[2] != 0; _cost = details.Params[3]; // TODO: Show rabbit for non-listed number of coins (use JJ2+ anim set 8) //_showAnim = details.Params[4] != 0; SetState(ActorState::CanBeFrozen, false); _renderer.setLayer(_renderer.layer() - 20); async_await RequestMetadataAsync("Object/BonusWarp"_s); switch (_cost) { case 10: case 20: case 50: case 100: SetAnimation((AnimState)_cost); break; default: // TODO: Show rabbit + coins needed, if (showAnim) SetAnimation(AnimState::Default); break; } async_return true; } void BonusWarp::OnUpdateHitbox() { UpdateHitbox(28, 28); } void BonusWarp::Activate(Player* player) { auto events = _levelHandler->EventMap(); if (events == nullptr) { return; } Vector2f targetPos = events->GetWarpTarget(_warpTarget); if (targetPos.X < 0.0f || targetPos.Y < 0.0f) { // Warp target not found return; } player->WarpToPosition(targetPos, _fast ? WarpFlags::Fast : WarpFlags::Default); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/BonusWarp.h000066400000000000000000000012451512772601700273650ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" #include "../Player.h" namespace Jazz2::Actors::Environment { /** @brief Bonus warp */ class BonusWarp : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: BonusWarp(); static void Preload(const ActorActivationDetails& details); /** @brief Activates the warp */ void Activate(Player* player); /** @brief Returns cost of the warp in coins */ std::uint16_t GetCost() const { return _cost; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; private: std::uint8_t _warpTarget, _cost; bool _setLaps; bool _fast; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Checkpoint.cpp000066400000000000000000000040731512772601700300710ustar00rootroot00000000000000#include "Checkpoint.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" namespace Jazz2::Actors::Environment { Checkpoint::Checkpoint() : _activated(false) { } void Checkpoint::Preload(const ActorActivationDetails& details) { std::uint8_t theme = details.Params[0]; switch (theme) { case 0: default: PreloadMetadataAsync("Object/Checkpoint"_s); break; case 1: // Xmas PreloadMetadataAsync("Object/CheckpointXmas"_s); break; } } Task Checkpoint::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CanBeFrozen, false); _theme = details.Params[0]; _activated = (details.Params[1] != 0); switch (_theme) { case 0: default: async_await RequestMetadataAsync("Object/Checkpoint"_s); break; case 1: // Xmas async_await RequestMetadataAsync("Object/CheckpointXmas"_s); break; } _renderer.setLayer(_renderer.layer() - 40); SetAnimation((AnimState)(_activated ? 1 : 0)); if (GetState(ActorState::ApplyGravitation)) { OnUpdateHitbox(); // Apply instant gravitation std::int32_t i = 10; while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative)) { // Nothing to do... } while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 1.0f), MoveType::Relative)) { // Nothing to do... } } async_return true; } void Checkpoint::OnUpdateHitbox() { UpdateHitbox(20, 20); } bool Checkpoint::OnHandleCollision(std::shared_ptr other) { if (_activated) { return true; } if (auto* player = runtime_cast(other.get())) { _activated = true; SetAnimation((AnimState)1); SetTransition(AnimState::TransitionActivate, false); PlaySfx("TransitionActivate"_s); // Deactivate event in map std::uint8_t playerParams[16] = { _theme, 1 }; _levelHandler->EventMap()->StoreTileEvent(_originTile.X, _originTile.Y, EventType::Checkpoint, ActorState::None, playerParams); _levelHandler->SetCheckpoint(player, Vector2f(_pos.X, _pos.Y)); return true; } return false; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Checkpoint.h000066400000000000000000000010171512772601700275310ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Checkpoint */ class Checkpoint : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Checkpoint(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; private: std::uint8_t _theme; bool _activated; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Copter.cpp000066400000000000000000000050161512772601700272340ustar00rootroot00000000000000#include "Copter.h" #include "../../ILevelHandler.h" #include "../Player.h" namespace Jazz2::Actors::Environment { Copter::Copter() : _phase(0.0f), _state(State::Free) { } Copter::~Copter() { #if defined(WITH_AUDIO) if (_noise != nullptr) { _noise->stop(); _noise = nullptr; } #endif } Task Copter::OnActivatedAsync(const ActorActivationDetails& details) { _state = (details.Params[0] != 0 ? State::Mounted : State::Free); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Enemy/LizardFloat"_s); SetAnimation((AnimState)1); _originPos = _pos; async_return true; } void Copter::OnUpdate(float timeMult) { switch (_state) { case State::Free: { _phase += timeMult * 0.05f; MoveInstantly(_originPos + Vector2f(0.0f, sinf(_phase) * 4.0f), MoveType::Absolute); OnUpdateHitbox(); break; } case State::Unmounted: { ActorBase::OnUpdate(timeMult); _speed.Y = _levelHandler->GetGravity() * -0.5f; _renderer.setAlphaF(_renderer.alpha() - (0.004f * timeMult)); _phase -= timeMult; if (_phase <= 0.0f) { DecreaseHealth(INT32_MAX); } break; } } #if defined(WITH_AUDIO) if (_noise != nullptr) { float newGain = _noise->gain() - (_noiseDec * timeMult); if (newGain <= 0.0f) { _noise->stop(); _noise = nullptr; } else { _noise->setGain(newGain); _noise->setPosition(Vector3f(_pos.X, _pos.Y, 0.8f)); } } #endif } void Copter::OnDetach(ActorBase* parent) { DecreaseHealth(INT32_MAX); } void Copter::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Enemy/LizardFloat"_s); } bool Copter::OnHandleCollision(std::shared_ptr other) { if (_state == State::Free || _state == State::Unmounted) { if (auto* player = runtime_cast(other.get())) { if (player->GetModifier() == Player::Modifier::None && player->SetModifier(Player::Modifier::LizardCopter, shared_from_this())) { _state = State::Mounted; _renderer.setAlphaF(1.0f); PlaySfx("CopterPre"_s); return true; } } } return ActorBase::OnHandleCollision(std::move(other)); } void Copter::Unmount(float timeLeft) { if (_state == State::Mounted) { _state = State::Unmounted; _phase = timeLeft; #if defined(WITH_AUDIO) _noise = PlaySfx("Copter"_s, 0.8f, 0.8f); if (_noise != nullptr) { _noise->setLooping(true); _noiseDec = _noise->gain() * 0.005f; } #endif SetState(ActorState::ApplyGravitation, true); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Copter.h000066400000000000000000000015001512772601700266730ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Copter */ class Copter : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Copter(); ~Copter(); static void Preload(const ActorActivationDetails& details); bool OnHandleCollision(std::shared_ptr other) override; /** @brief Unmounts from the assigned actor */ void Unmount(float timeLeft); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnDetach(ActorBase* parent) override; private: enum class State { Free, Unmounted, Mounted }; Vector2f _originPos; float _phase; State _state; #if defined(WITH_AUDIO) std::shared_ptr _noise; #endif float _noiseDec; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/EndOfLevel.cpp000066400000000000000000000012521512772601700277610ustar00rootroot00000000000000#include "EndOfLevel.h" #include "../../ILevelHandler.h" namespace Jazz2::Actors::Environment { EndOfLevel::EndOfLevel() : _exitType(ExitType::None) { } void EndOfLevel::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/SignEol"_s); } Task EndOfLevel::OnActivatedAsync(const ActorActivationDetails& details) { _exitType = (ExitType)details.Params[0]; SetState(ActorState::CanBeFrozen, false); _renderer.setLayer(_renderer.layer() - 40); async_await RequestMetadataAsync("Object/SignEol"_s); SetAnimation(AnimState::Default); async_return true; } void EndOfLevel::OnUpdateHitbox() { UpdateHitbox(24, 24); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/EndOfLevel.h000066400000000000000000000007301512772601700274260ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" #include "../../ExitType.h" namespace Jazz2::Actors::Environment { /** @brief End of level sign */ class EndOfLevel : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: EndOfLevel(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; private: ExitType _exitType; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Eva.cpp000066400000000000000000000025231512772601700265130ustar00rootroot00000000000000#include "Eva.h" #include "../../ILevelHandler.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Environment { Eva::Eva() : _animationTime(0.0f) { } Task Eva::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/Eva"_s); SetAnimation(AnimState::Idle); async_return true; } void Eva::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); if (_currentTransition == nullptr) { if (_animationTime <= 0.0f) { SetTransition(AnimState::TransitionIdleBored, true); _animationTime = Random().NextFloat(160.0f, 200.0f); } else { _animationTime -= timeMult; } } } bool Eva::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { if (player->GetPlayerType() == PlayerType::Frog && player->DisableControllable(160.0f)) { SetTransition(AnimState::TransitionAttack, false, [this, player]() { player->MorphRevert(); PlaySfx("Kiss"_s, 0.8f); SetTransition(AnimState::TransitionAttackEnd, false); }); } return true; } return ActorBase::OnHandleCollision(std::move(other)); } void Eva::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/Eva"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Eva.h000066400000000000000000000007601512772601700261610ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Eva */ class Eva : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Eva(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: float _animationTime; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/IceBlock.cpp000066400000000000000000000045711512772601700274600ustar00rootroot00000000000000#include "IceBlock.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Environment { IceBlock::IceBlock() : _timeLeft(200.0f) { } void IceBlock::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/IceBlock"_s); } Task IceBlock::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/IceBlock"_s); SetAnimation(AnimState::Default); _renderer.Initialize(ActorRendererType::FrozenMask); async_return true; } void IceBlock::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); auto tileMap = _levelHandler->TileMap(); if (tileMap != nullptr) { if (tileMap->IsTileEmpty(_originTile.X, _originTile.Y)) { DecreaseHealth(INT32_MAX); return; } } _timeLeft -= timeMult; if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); if (tileMap != nullptr) { if (tileMap->IsTileEmpty(_originTile.X - 1, _originTile.Y)) { for (int i = 0; i < 5; i++) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X - 24, (std::int32_t)_pos.Y - 16 + Random().Fast(0, 32), _renderer.layer() + 10), Explosion::Type::IceShrapnel); } } if (tileMap->IsTileEmpty(_originTile.X + 1, _originTile.Y)) { for (int i = 0; i < 5; i++) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X + 24, (std::int32_t)_pos.Y - 16 + Random().Fast(0, 32), _renderer.layer() + 10), Explosion::Type::IceShrapnel); } } if (tileMap->IsTileEmpty(_originTile.X, _originTile.Y + 1)) { for (int i = 0; i < 5; i++) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X - 16 + Random().Fast(0, 32), (int)_pos.Y + 24, _renderer.layer() + 10), Explosion::Type::IceShrapnel); } } } Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 90), Explosion::Type::SmokeWhite); _levelHandler->PlayCommonSfx("IceBreak"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); } } void IceBlock::ResetTimeLeft() { if (_timeLeft > 0.0f) { _timeLeft = 200.0f; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/IceBlock.h000066400000000000000000000007161512772601700271220ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Ice block */ class IceBlock : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: IceBlock(); void ResetTimeLeft(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: float _timeLeft; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Moth.cpp000066400000000000000000000036201512772601700267060ustar00rootroot00000000000000#include "Moth.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Environment { Moth::Moth() : _timer(0.0f), _direction(0) { } Task Moth::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CanBeFrozen, false); _renderer.setLayer(_renderer.layer() + 20); uint8_t theme = details.Params[0]; async_await RequestMetadataAsync("Object/Moth"_s); SetAnimation((AnimState)theme); _renderer.AnimPaused = true; async_return true; } void Moth::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); if (_timer > 0.0f) { if (GetState(ActorState::CanJump)) { _timer = 0.0f; } else { _timer -= timeMult; _externalForce.X = lerpByTime(_externalForce.X, sinf((100.0f - _timer) / 6.0f) * 4.0f * _direction, 0.6f, timeMult); _externalForce.Y = lerpByTime(_externalForce.Y, -0.00005f * _timer * _timer, 0.6f, timeMult); SetFacingLeft(_speed.X < 0.0f); } } else if (GetState(ActorState::CanJump)) { _speed.X = 0.0f; _externalForce.Y = 0.0f; _externalForce.X = 0.0f; _renderer.AnimTime = 0.0f; _renderer.AnimPaused = true; } else if (GetState(ActorState::ApplyGravitation)) { _externalForce.Y = _levelHandler->GetGravity() * -0.8f; } } bool Moth::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { if (_timer <= 50.0f) { _timer = 100.0f - _timer * 0.2f; SetState(ActorState::CanJump, false); _direction = (Random().NextBool() ? -1 : 1); _speed.X = Random().NextFloat(-1.4f, 0.0f) * _direction; _speed.Y = Random().NextFloat(-0.4f, 0.0f); _renderer.AnimPaused = false; } return true; } return false; } void Moth::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/Moth"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Moth.h000066400000000000000000000010061512772601700263470ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Moth */ class Moth : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Moth(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; private: float _timer; std::int32_t _direction; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/RollingRock.cpp000066400000000000000000000141661512772601700302330ustar00rootroot00000000000000#include "RollingRock.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Environment { RollingRock::RollingRock() : _delayLeft(40.0f), _triggered(false), _soundCooldown(0.0f) { } void RollingRock::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/RollingRock"_s); } Task RollingRock::OnActivatedAsync(const ActorActivationDetails& details) { _renderer.setLayer(_renderer.layer() + 10); _id = details.Params[0]; _triggerSpeedX = (float)*(int8_t*)&details.Params[1]; _triggerSpeedY = (float)*(int8_t*)&details.Params[2]; SetState(ActorState::CollideWithSolidObjects | ActorState::CollideWithSolidObjectsBelow | ActorState::IsSolidObject | ActorState::IsInvulnerable, true); SetState(ActorState::ApplyGravitation, false); _elasticity = 0.4f; _canHurtPlayer = false; async_await RequestMetadataAsync("Object/RollingRock"_s); SetAnimation(AnimState::Idle); _renderer.setRotation(_pos.X + _pos.Y); async_return true; } void RollingRock::OnUpdate(float timeMult) { UpdateFrozenState(timeMult); if (_soundCooldown > 0.0f) { _soundCooldown -= timeMult; } if (_frozenTimeLeft > 0.0f) { return; } if (GetState(ActorState::ApplyGravitation)) { // Rock is triggered float currentGravity = _levelHandler->GetGravity(); _speed.X = std::clamp(_speed.X + _externalForce.X * timeMult, -3.0f, 3.0f) * powf(0.6f, timeMult); bool up = IsPositionEmpty(_pos.X, _pos.Y - 33.0f); bool down = IsPositionEmpty(_pos.X - 4.0f, _pos.Y + 36.0f) && IsPositionEmpty(_pos.X + 4.0f, _pos.Y + 36.0f); bool left = IsPositionEmpty(_pos.X - 36.0f, _pos.Y) && IsPositionEmpty(_pos.X - 19.0f, _pos.Y - 19.0f) && IsPositionEmpty(_pos.X - 4.0f, _pos.Y + 33.0f); bool right = IsPositionEmpty(_pos.X + 36.0f, _pos.Y) && IsPositionEmpty(_pos.X + 19.0f, _pos.Y + 19.0f) && IsPositionEmpty(_pos.X + 4.0f, _pos.Y + 33.0f); if (down) { if (_pos.Y > _levelHandler->GetWaterLevel()) { _speed.Y += currentGravity * 0.25f * timeMult; } else { _speed.Y += currentGravity * timeMult; } } //int hits = 0; if ((_speed.X < 0.0f && !left) || (_speed.X > 0.0f && !right)) { // Bounce on X if (_soundCooldown <= 0.0f && std::abs(_speed.X) > 2.0f) { _soundCooldown = 140.0f; PlaySfx("Hit"_s, 0.6f, 0.4f); } _speed.X = _speed.X * -0.5f; _externalForce.X = _externalForce.X * -0.5f; //hits |= 0x1; } if ((_speed.Y < 0.0f && !up) || (_speed.Y > 0.0f && !down)) { // Bounce on Y if (_soundCooldown <= 0.0f && std::abs(_speed.Y) > 2.0f) { _soundCooldown = 140.0f; PlaySfx("Hit"_s, 0.6f, 0.4f); } _speed.Y = _speed.Y * -0.5f; //hits |= 0x2; } MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force); if (std::abs(_speed.X) < 0.2f && !down) { _speed = Vector2f::Zero; _externalForce = Vector2f::Zero; SetState(ActorState::ApplyGravitation, false); _canHurtPlayer = false; } _renderer.setRotation(_renderer.rotation() + _speed.X * 0.02f * timeMult); } // Trigger if (_triggered && _delayLeft > 0.0f) { _delayLeft -= timeMult; if (_delayLeft <= 0.0f) { SetState(ActorState::ApplyGravitation, true); _canHurtPlayer = true; _speed.X = _triggerSpeedX; _speed.Y = _triggerSpeedY; _externalForce.X = (_triggerSpeedX < 0.0f ? -4.0f : 4.0f); if (_soundCooldown <= 0.0f) { _soundCooldown = 140.0f; PlaySfx("Hit"_s, 0.6f, 0.4f); } } } } void RollingRock::OnUpdateHitbox() { UpdateHitbox(50, 50); } bool RollingRock::OnHandleCollision(std::shared_ptr other) { if (auto* rollingRock = runtime_cast(other.get())) { float dx = (rollingRock->_pos.X - _pos.X); float dy = (rollingRock->_pos.Y - _pos.Y); float distance = Vector2f(dx, dy).Length(); if (distance < 74.0f) { float relSpeeds = (std::abs(_speed.X) + std::abs(rollingRock->_speed.X)) * 0.5f; if (dx > 0.0f) { _speed.X = -relSpeeds; _externalForce.X = -std::abs(_externalForce.X); } else { _speed.X = relSpeeds; _externalForce.X = std::abs(_externalForce.X); } if (dx < 0.0f) { rollingRock->_speed.X = -relSpeeds; rollingRock->_externalForce.X = -std::abs(rollingRock->_externalForce.X); } else { rollingRock->_speed.X = relSpeeds; rollingRock->_externalForce.X = std::abs(rollingRock->_externalForce.X); } if (relSpeeds > 1.0f) { _triggered = true; } SetState(ActorState::CanBeFrozen, true); } return true; } else if (auto* player = runtime_cast(other.get())) { if (_triggered) { float dx = (player->GetPos().X - _pos.X); float dy = (player->GetPos().Y - _pos.Y); float distance = Vector2f(dx, dy).Length(); if (distance < 50.0f || dy > 0.0f) { float relSpeeds = (std::abs(_speed.X) + std::abs(player->GetSpeed().X)) * 0.5f; if (relSpeeds < 1.0f) { relSpeeds = (60.0f - distance) * 0.2f; } if (dx > 0.0f) { _speed.X = -2.0f; _externalForce.X = -std::abs(_externalForce.X); } else { _speed.X = 2.0f; _externalForce.X = std::abs(_externalForce.X); } if (dy > 0.0f) { _externalForce.Y -= 2.0f; } SetState(ActorState::ApplyGravitation, true); } } } return EnemyBase::OnHandleCollision(std::move(other)); } void RollingRock::OnTriggeredEvent(EventType eventType, std::uint8_t* eventParams) { if (!_triggered && eventType == EventType::RollingRockTrigger && eventParams[0] == _id) { _triggered = true; _levelHandler->ShakeCameraViewNear(_pos, 60.0f); // TODO: Shake amplitude /*float distanceToPlayer; if (distanceToPlayer < 400.0f) { float amplitude = std::min((400.0f - distanceToPlayer) / (256.0f * 8.0f), 40.0f); _levelHandler->ShakeCameraView(amplitude); }*/ } } bool RollingRock::IsPositionEmpty(float x, float y) { TileCollisionParams params = { TileDestructType::None, _speed.Y >= 0.0f }; return _levelHandler->IsPositionEmpty(this, AABBf(x - 1.0f, y - 1.0f, x + 1.0f, y + 1.0f), params); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/RollingRock.h000066400000000000000000000014451512772601700276740ustar00rootroot00000000000000#pragma once #include "../Enemies/EnemyBase.h" namespace Jazz2::Actors::Environment { /** @brief Rolling rock */ class RollingRock : public Enemies::EnemyBase { DEATH_RUNTIME_OBJECT(Enemies::EnemyBase); public: RollingRock(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnHandleCollision(std::shared_ptr other) override; void OnTriggeredEvent(EventType eventType, std::uint8_t* eventParams) override; private: std::uint8_t _id; float _triggerSpeedX, _triggerSpeedY; float _delayLeft; bool _triggered; float _soundCooldown; bool IsPositionEmpty(float x, float y); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Spring.cpp000066400000000000000000000135631512772601700272500ustar00rootroot00000000000000#include "Spring.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Weapons/ShieldFireShot.h" #include "../Weapons/ToasterShot.h" #include "../Weapons/Thunderbolt.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Environment { Spring::Spring() : _cooldown(0.0f) { } void Spring::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/Spring"_s); } Vector2f Spring::Activate() { if (_state == State::Frozen || _cooldown > 0.0f || _frozenTimeLeft > 0.0f) { return Vector2f::Zero; } _cooldown = (_delay > 0 ? _delay : 6.0f); SetTransition(_currentAnimation->State | (AnimState)0x200, false); switch (_orientation) { case Orientation::Bottom: PlaySfx("Vertical"_s); return Vector2f(0, -_strength); case Orientation::Top: PlaySfx("VerticalReversed"_s); return Vector2f(0, _strength); case Orientation::Right: case Orientation::Left: PlaySfx("Horizontal"_s); return Vector2f(_strength * (_orientation == Orientation::Right ? 1 : -1), 0); default: return Vector2f::Zero; } } Task Spring::OnActivatedAsync(const ActorActivationDetails& details) { _type = details.Params[0]; _orientation = (Orientation)details.Params[1]; KeepSpeedX = ((details.Params[2] & 0x04) != 0); KeepSpeedY = ((details.Params[2] & 0x08) != 0); bool applyGravitation = (details.Params[2] & 0x01) != 0; // Jazz² Resurrection only _state = ((details.Params[2] & 0x02) != 0 ? State::Frozen : State::Default); _delay = details.Params[3]; SetState(ActorState::SkipPerPixelCollisions, true); async_await RequestMetadataAsync("Object/Spring"_s); Vector2f tileCorner = Vector2f((std::int32_t)(_pos.X / Tiles::TileSet::DefaultTileSize) * Tiles::TileSet::DefaultTileSize, (std::int32_t)(_pos.Y / Tiles::TileSet::DefaultTileSize) * Tiles::TileSet::DefaultTileSize); if (_orientation > Orientation::Left) { // JJ2 horizontal springs held no data about which way they were facing. // For compatibility, correct orientation is evaluated during runtime. AABBf aabb = AABBf(_pos.X + 6.0f, _pos.Y - 2.0f, _pos.X + 26.0f, _pos.Y); TileCollisionParams params = { TileDestructType::None, true }; _orientation = (_levelHandler->TileMap()->IsTileEmpty(aabb, params) != (_orientation == (Orientation)5) ? Orientation::Right : Orientation::Left); } std::int32_t orientationBit = 0; switch (_orientation) { case Orientation::Bottom: MoveInstantly(Vector2f(tileCorner.X + 16, tileCorner.Y + 8), MoveType::Absolute | MoveType::Force); break; case Orientation::Right: MoveInstantly(Vector2f(tileCorner.X + 16, tileCorner.Y + 16), MoveType::Absolute | MoveType::Force); orientationBit = 1; break; case Orientation::Top: MoveInstantly(Vector2f(tileCorner.X + 16, tileCorner.Y + 8), MoveType::Absolute | MoveType::Force); orientationBit = 2; break; case Orientation::Left: MoveInstantly(Vector2f(tileCorner.X + 16, tileCorner.Y + 16), MoveType::Absolute | MoveType::Force); orientationBit = 1; SetFacingLeft(true); break; } SetState(ActorState::ApplyGravitation, applyGravitation); // Red starts at 1 in "Object/Spring" SetAnimation((AnimState)(((_type + 1) << 10) | (orientationBit << 12))); _renderer.setLayer(_renderer.layer() - 8); if (_orientation == Orientation::Right || _orientation == Orientation::Left) { // Horizontal springs all seem to have the same strength. // This constant strength gives about the correct amount of horizontal push. _strength = 9.5f; } else { // Vertical springs should work as follows: // Red spring lifts the player 9 tiles, green 14, and blue 19. // Vertical strength currently works differently from horizontal, that explains // the otherwise inexplicable difference of scale between the two types. switch (_type) { case 0: // Red _strength = 1.25f; break; case 1: // Green _strength = (_levelHandler->IsReforged() ? 1.50f : 1.52f); break; case 2: // Blue _strength = (_levelHandler->IsReforged() ? 1.68f : 1.72f); break; } } if (GetState(ActorState::ApplyGravitation)) { OnUpdateHitbox(); // Apply instant gravitation std::int32_t i = 10; while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative)) { // Nothing to do... } while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 1.0f), MoveType::Relative)) { // Nothing to do... } } if (_state == State::Frozen) { _renderer.Initialize(ActorRendererType::FrozenMask); SetState(ActorState::CanBeFrozen, false); } async_return true; } void Spring::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); if (_cooldown > 0.0f) { _cooldown -= timeMult; } if (_state == State::Heated) { // Cannot be directly in `Spring::OnHandleCollision()` due to bug in `BaseSprite::updateRenderCommand()`, // it would be called before `BaseSprite::updateRenderCommand()` but after `SceneNode::transform()` _renderer.Initialize(ActorRendererType::Default); _state = State::Default; } } void Spring::OnUpdateHitbox() { switch (_orientation) { case Orientation::Right: AABBInner = AABBf(_pos.X - 8, _pos.Y - 10, _pos.X, _pos.Y + 10); break; case Orientation::Left: AABBInner = AABBf(_pos.X, _pos.Y - 10, _pos.X + 8, _pos.Y + 10); break; default: case Orientation::Bottom: case Orientation::Top: AABBInner = AABBf(_pos.X - 10, _pos.Y, _pos.X + 10, _pos.Y + 8); break; } } bool Spring::OnHandleCollision(std::shared_ptr other) { if (_state == State::Frozen) { ActorBase* actorBase = other.get(); if (runtime_cast(actorBase) || runtime_cast(actorBase) || runtime_cast(actorBase)) { _state = State::Heated; SetState(ActorState::CanBeFrozen, true); } } return ActorBase::OnHandleCollision(std::move(other)); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/Spring.h000066400000000000000000000017441512772601700267130ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Spring */ class Spring : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Spring(); /** @brief Whether player horizontal speed should be kept */ bool KeepSpeedX; /** @brief Whether player vertical speed should be kept */ bool KeepSpeedY; bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); /** @brief Activates the spring */ Vector2f Activate(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; private: enum class State { Default, Frozen, Heated }; enum class Orientation : uint8_t { Bottom, Right, Top, Left }; uint8_t _type; Orientation _orientation; float _strength; uint8_t _delay; State _state; float _cooldown; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/SteamNote.cpp000066400000000000000000000031161512772601700276760ustar00rootroot00000000000000#include "SteamNote.h" #include "../../ILevelHandler.h" namespace Jazz2::Actors::Environment { SteamNote::SteamNote() : _cooldown(0.0f) { } void SteamNote::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/SteamNote"_s); } Task SteamNote::OnActivatedAsync(const ActorActivationDetails& details) { _pos.X -= 10.0f; SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/SteamNote"_s); SetAnimation(AnimState::Default); PlaySfx("Appear"_s, 0.4f); // It's incorrectly positioned one tile up in "share2.j2l", so move it to correct position OnUpdateHitbox(); bool moved = false; std::int32_t i = 10; while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative)) { moved = true; } while (i-- > 0 && MoveInstantly(Vector2f(0.0f, 1.0f), MoveType::Relative)) { moved = true; } if (moved) { MoveInstantly(Vector2f(0.0f, 18.0f), MoveType::Relative | MoveType::Force); } async_return true; } void SteamNote::OnUpdate(float timeMult) { if (_cooldown > 0.0f) { _cooldown -= timeMult; if (_cooldown <= 0.0f) { _renderer.AnimTime = 0.0f; _renderer.AnimPaused = false; _renderer.setDrawEnabled(true); PlaySfx("Appear"_s, 0.4f); } } } void SteamNote::OnUpdateHitbox() { UpdateHitbox(6, 6); } void SteamNote::OnAnimationFinished() { _renderer.AnimPaused = true; _renderer.setDrawEnabled(false); _cooldown = 80.0f; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/SteamNote.h000066400000000000000000000010011512772601700273320ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Steam note */ class SteamNote : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: SteamNote(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnAnimationFinished() override; private: float _cooldown; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/SwingingVine.cpp000066400000000000000000000120671512772601700304130ustar00rootroot00000000000000#include "SwingingVine.h" #include "../../ContentResolver.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Actors::Environment { SwingingVine::SwingingVine() : _angle(0.0f), _phase(0.0f), _justTurned(false) { } SwingingVine::~SwingingVine() { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { player->CancelCarryingObject(this); } } Task SwingingVine::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/SwingingVine"_s); SetAnimation(AnimState::Default); _phase = _originTile.X + _originTile.Y * 0.5f; _renderer.AnimPaused = true; auto& resolver = ContentResolver::Get(); if (!resolver.IsHeadless()) { if (_currentAnimation != nullptr) { _currentAnimation->Base->TextureDiffuse->SetWrap(SamplerWrapping::Repeat); } for (std::int32_t i = 0; i < ChunkCount; i++) { _chunks[i] = std::make_unique(RenderCommand::Type::Sprite); _chunks[i]->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); _chunks[i]->GetMaterial().SetBlendingEnabled(true); _chunks[i]->GetMaterial().ReserveUniformsDataMemory(); _chunks[i]->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); _chunks[i]->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); auto* textureUniform = _chunks[i]->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } async_return true; } void SwingingVine::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); float currentPhase = _phase + 0.04f * _levelHandler->GetElapsedFrames(); for (std::int32_t i = 0; i < ChunkCount; i++) { _angle = sinf(currentPhase - i * (0.64f / ChunkCount)) * 1.2f + fPiOver2; float distance = ChunkSize * powf(i, 0.95f); _chunkPos[i].X = _pos.X + cosf(_angle) * distance; _chunkPos[i].Y = _pos.Y + sinf(_angle) * distance; } auto& lastChunk = _chunkPos[ChunkCount - 1]; AABBInner = AABBf(lastChunk.X - 10.0f, lastChunk.Y - 10.0f, lastChunk.X + 10.0f, lastChunk.Y + 10.0f); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetCarryingObject() == this) { float chunkAngle = sinf(currentPhase - ChunkCount * 0.08f) * 0.6f; Vector2f prevPos = player->GetPos(); Vector2 newPos = lastChunk + Vector2(chunkAngle * -22.0f, 20.0f + std::abs(chunkAngle) * -10.0f); player->MoveInstantly(newPos, MoveType::Absolute); if (_justTurned) { // TODO: `SwingingVine::OnUpdate()` is called after `Player::OnUpdate()`, so this must be called one frame later player->SetFacingLeft(!player->IsFacingLeft()); _justTurned = false; } else if (player->IsFacingLeft()) { if (newPos.X > prevPos.X) { player->_renderer.AnimTime = 0.0f; _justTurned = true; } } else { if (newPos.X < prevPos.X) { player->_renderer.AnimTime = 0.0f; _justTurned = true; } } player->_renderer.setRotation(chunkAngle); } } SetState(ActorState::IsDirty, true); } void SwingingVine::OnUpdateHitbox() { } bool SwingingVine::OnDraw(RenderQueue& renderQueue) { if (_currentAnimation != nullptr) { auto& resBase = _currentAnimation->Base; Vector2i texSize = resBase->TextureDiffuse->GetSize(); float currentPhase = _phase + 0.04f * _levelHandler->GetElapsedFrames(); for (std::int32_t i = 0; i < ChunkCount; i++) { auto command = _chunks[i].get(); float chunkTexSize = ChunkSize / texSize.Y; float chunkAngle = sinf(currentPhase - i * 0.08f) * 1.2f; auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, chunkTexSize, chunkTexSize * i); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(texSize.X, ChunkSize); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); Matrix4x4f worldMatrix = Matrix4x4f::Translation(_chunkPos[i].X - texSize.X / 2, _chunkPos[i].Y - ChunkSize / 2, 0.0f); worldMatrix.RotateZ(chunkAngle); command->SetTransformation(worldMatrix); command->SetLayer(_renderer.layer()); command->GetMaterial().SetTexture(*resBase->TextureDiffuse); renderQueue.AddCommand(command); } } return true; } bool SwingingVine::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { if (player->_springCooldown <= 0.0f) { player->UpdateCarryingObject(this, SuspendType::SwingingVine); } return true; } return false; } void SwingingVine::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/SwingingVine"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Environment/SwingingVine.h000066400000000000000000000015231512772601700300530ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Environment { /** @brief Swinging vine */ class SwingingVine : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: SwingingVine(); ~SwingingVine(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnDraw(RenderQueue& renderQueue) override; private: static constexpr std::int32_t ChunkCount = 16; static constexpr float ChunkSize = 136.0f / ChunkCount; float _angle; float _phase; bool _justTurned; Vector2f _chunkPos[ChunkCount]; std::unique_ptr _chunks[ChunkCount]; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Explosion.cpp000066400000000000000000000125011512772601700254510ustar00rootroot00000000000000#include "Explosion.h" #include "../ILevelHandler.h" #include "../../nCine/Base/Random.h" namespace Jazz2::Actors { Explosion::Explosion() : _lightBrightness(0.0f), _lightIntensity(0.0f), _lightRadiusNear(0.0f), _lightRadiusFar(0.0f), _scale(1.0f), _time(0.0f) { } void Explosion::Create(ILevelHandler* levelHandler, const Vector3i& pos, Type type, float scale) { std::shared_ptr explosion = std::make_shared(); std::uint8_t explosionParams[8]; *(std::uint16_t*)&explosionParams[0] = (uint16_t)type; // 2-3: unused *(float*)&explosionParams[4] = scale; explosion->OnActivated(ActorActivationDetails( levelHandler, pos, explosionParams )); levelHandler->AddActor(explosion); } Task Explosion::OnActivatedAsync(const ActorActivationDetails& details) { _type = (Type)*(std::uint16_t*)&details.Params[0]; _scale = *(float*)&details.Params[4]; SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Common/Explosions"_s); // IceShrapnels are randomized below if (_type != Type::IceShrapnel) { SetAnimation((AnimState)_type); } switch (_type) { case Type::Large: { _lightIntensity = 0.8f; _lightBrightness = 0.9f; _lightRadiusFar = 55.0f * _scale; break; } case Type::Pepper: { _lightIntensity = 0.5f; _lightBrightness = 0.2f; _lightRadiusNear = 7.0f * _scale; _lightRadiusFar = 14.0f * _scale; break; } case Type::RF: { _lightIntensity = 0.2f; _lightBrightness = 0.2f; _lightRadiusFar = 50.0f * _scale; break; } case Type::RFUpgraded: { _lightIntensity = 0.2f; _lightBrightness = 0.2f; _lightRadiusFar = 18.0f * _scale; break; } case Type::IceShrapnel: { SetAnimation((AnimState)((std::uint32_t)Type::IceShrapnel + Random().Fast(0, 4))); SetState(ActorState::CollideWithTileset | ActorState::ApplyGravitation | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ForceDisableCollisions, false); _renderer.Initialize(ActorRendererType::FrozenMask); _renderer.CurrentFrame = _renderer.FirstFrame + Random().Fast(0, _renderer.FrameCount); _renderer.setAlphaF(1.0f); float speedX = Random().FastFloat(0.5f, 2.0f); _renderer.AnimDuration /= speedX * 0.7f; _speed = Vector2f(speedX * _scale * (Random().NextBool() ? -1.0f : 1.0f), _scale * Random().FastFloat(-4.0f, -1.0f)); _pos += _speed * _scale * _scale; _elasticity = 0.2f; SetFacingLeft(_speed.X < 0.0f); break; } case Type::Generator: { // Apply random orientation _renderer.setRotation(Random().NextFloat(0.0f, 4.0f * fPiOver2)); SetFacingLeft(Random().NextFloat() < 0.5f); break; } } _renderer.setScale(_scale); async_return true; } void Explosion::OnUpdate(float timeMult) { _time += timeMult; switch (_type) { case Type::Large: { _lightRadiusFar -= timeMult * _scale * 5.0f; break; } case Type::Pepper: { _lightIntensity -= timeMult * 0.05f; break; } case Type::RF: { float phase1 = _time * 0.16f; float phase2 = (phase1 < fRadAngle270 ? sinf(phase1) : -1.0f); _lightIntensity = 0.2f + phase2; _lightBrightness = 0.2f + phase2; _lightRadiusFar = 50.0f * _scale * (1.0f + phase2); break; } case Type::RFUpgraded: { float phase1 = _time * 0.18f; float phase2 = (phase1 < fRadAngle270 ? sinf(phase1) : -1.0f); _lightIntensity = 0.1f + phase2 * 0.1f; _lightBrightness = 0.2f + phase2 * 0.4f; _lightRadiusNear = (2.0f * _scale) + (phase1 * 1.4f); _lightRadiusFar = (16.0f * _scale) + (phase1 * 5.0f); break; } case Type::IceShrapnel: { ActorBase::OnUpdate(timeMult); float newAlpha = _renderer.alpha() - (0.014f * timeMult); if (newAlpha > 0.0f) { _renderer.setAlphaF(newAlpha); _renderer.setScale(std::min(_scale * newAlpha * 2.0f, _scale)); } else { DecreaseHealth(INT32_MAX); } break; } } } void Explosion::OnUpdateHitbox() { UpdateHitbox(2, 2); } void Explosion::OnEmitLights(SmallVectorImpl& lights) { if (_lightRadiusFar > 0.0f) { auto& light1 = lights.emplace_back(); light1.Pos = _pos; light1.Intensity = _lightIntensity; light1.Brightness = _lightBrightness; light1.RadiusNear = _lightRadiusNear; light1.RadiusFar = _lightRadiusFar; } if (_type == Type::RFUpgraded) { // RFUpgraded explosion uses ring light float distance = 20.0f + (_time * 6.0f); std::int32_t SegmentCount = (std::int32_t)(distance * 0.5f); for (std::int32_t i = 0; i < SegmentCount; i++) { float angle = i * fTwoPi / SegmentCount; auto& light = lights.emplace_back(); light.Pos = _pos + Vector2f(cosf(angle) * distance, sinf(angle) * distance); light.Intensity = _lightIntensity; light.Brightness = _lightBrightness; light.RadiusNear = _lightRadiusNear; light.RadiusFar = _lightRadiusFar; } } else if (_type == Type::Pepper) { auto& light2 = lights.emplace_back(); light2.Pos = _pos; light2.Intensity = 0.1f; light2.RadiusNear = 0.0f; light2.RadiusFar = 160.0f * _scale; } } void Explosion::OnAnimationFinished() { if (_type != Type::IceShrapnel) { DecreaseHealth(INT32_MAX); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Explosion.h000066400000000000000000000017711512772601700251250ustar00rootroot00000000000000#pragma once #include "ActorBase.h" namespace Jazz2::Actors { /** @brief Explosion effects */ class Explosion : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: /** @brief Explosion effect type */ enum class Type { Tiny, TinyBlue, TinyDark, Small, SmallDark, Large, SmokeBrown, SmokeGray, SmokeWhite, SmokePoof, WaterSplash, Pepper, RF, RFUpgraded, Generator, IceShrapnel, }; Explosion(); static void Create(ILevelHandler* levelHandler, const Vector3i& pos, Type type, float scale = 1.0f); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; void OnAnimationFinished() override; private: Type _type; float _lightBrightness; float _lightIntensity; float _lightRadiusNear; float _lightRadiusFar; float _scale; float _time; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/000077500000000000000000000000001512772601700245335ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/FlickerLight.cpp000066400000000000000000000044451512772601700276150ustar00rootroot00000000000000#include "FlickerLight.h" #include "../../ILevelHandler.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Lighting { FlickerLight::FlickerLight() { } Task FlickerLight::OnActivatedAsync(const ActorActivationDetails& details) { _intensity = details.Params[0] / 255.0f; _brightness = details.Params[1] / 255.0f; _radiusNear = (float)*(uint16_t*)&details.Params[2]; _radiusFar = (float)*(uint16_t*)&details.Params[4]; _phase = 0.6f; SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); for (std::int32_t i = 0; i < LightPartCount; i++) { float radius = Random().FastFloat(_radiusFar * 0.5f, _radiusFar * 0.8f); float angle = Random().FastFloat(0.0f, fTwoPi); float distance = Random().FastFloat(0.0f, _radiusFar * 0.6f); LightPart& part = _parts.emplace_back(); part.Radius = radius; part.Pos = Vector2f(_pos.X + cosf(angle) * distance, _pos.Y + sinf(angle) * distance); part.Phase = (float)i / LightPartCount; } async_return true; } void FlickerLight::OnUpdate(float timeMult) { _phase = lerp(_phase, Random().FastFloat(), 0.04f * timeMult); for (auto& part : _parts) { part.Phase += timeMult * 0.02f; if (part.Phase >= 1.0f) { float radius = Random().FastFloat(_radiusFar * 0.5f, _radiusFar * 0.8f); float angle = Random().FastFloat(0.0f, fTwoPi); float distance = Random().FastFloat(0.0f, _radiusFar * 0.6f); part.Radius = radius; part.Pos = Vector2f(_pos.X + cosf(angle) * distance, _pos.Y + sinf(angle) * distance); part.Phase = 0.0f; } } } void FlickerLight::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = _intensity * _phase * 0.2f; light.Brightness = _brightness * 0.4f; light.RadiusNear = _radiusNear; light.RadiusFar = lerp(_radiusNear, _radiusFar, _phase); for (auto& part : _parts) { float phase = sinf(part.Phase * fPi); auto& light2 = lights.emplace_back(); light2.Pos = part.Pos; light2.Intensity = _intensity * phase * 0.6f; light2.Brightness = _brightness * phase * 0.4f; light2.RadiusNear = 0.0f; light2.RadiusFar = part.Radius; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/FlickerLight.h000066400000000000000000000016021512772601700272520ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Lighting { /** @brief Flickering light */ class FlickerLight : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: FlickerLight(); static void Preload(const ActorActivationDetails& details) {} protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct LightPart { Vector2f Pos; float Radius; float Phase; }; #endif static constexpr std::int32_t LightPartCount = 16; float _intensity; float _brightness; float _radiusNear; float _radiusFar; float _phase; SmallVector _parts; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/PulsatingRadialLight.cpp000066400000000000000000000025761512772601700313240ustar00rootroot00000000000000#include "PulsatingRadialLight.h" #include "../../ILevelHandler.h" #include "../../../nCine/Application.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Lighting { PulsatingRadialLight::PulsatingRadialLight() { } Task PulsatingRadialLight::OnActivatedAsync(const ActorActivationDetails& details) { _intensity = details.Params[0] / 255.0f; _brightness = details.Params[1] / 255.0f; _radiusNear1 = (float)*(std::uint16_t*)&details.Params[2]; _radiusNear2 = (float)*(std::uint16_t*)&details.Params[4]; _radiusFar = (float)*(std::uint16_t*)&details.Params[6]; _speed = details.Params[8] * 0.0072f; std::uint8_t sync = details.Params[9]; _phase = sync * fPiOver2 + _speed * _levelHandler->GetElapsedFrames(); SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); async_return true; } void PulsatingRadialLight::OnUpdate(float timeMult) { _phase += _speed * timeMult; } void PulsatingRadialLight::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = _intensity; light.Brightness = _brightness; light.RadiusNear = _radiusNear1 + sinf(_phase) * _radiusNear2; light.RadiusFar = light.RadiusNear + _radiusFar; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/PulsatingRadialLight.h000066400000000000000000000012241512772601700307560ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Lighting { /** @brief Pulsating radial light */ class PulsatingRadialLight : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: PulsatingRadialLight(); static void Preload(const ActorActivationDetails& details) {} protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; private: float _intensity; float _brightness; float _radiusNear1; float _radiusNear2; float _radiusFar; float _speed; float _phase; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/StaticRadialLight.cpp000066400000000000000000000017711512772601700306010ustar00rootroot00000000000000#include "StaticRadialLight.h" #include "../../ILevelHandler.h" namespace Jazz2::Actors::Lighting { StaticRadialLight::StaticRadialLight() { } Task StaticRadialLight::OnActivatedAsync(const ActorActivationDetails& details) { _intensity = details.Params[0] / 255.0f; _brightness = details.Params[1] / 255.0f; _radiusNear = (float)*(uint16_t*)&details.Params[2]; _radiusFar = (float)*(uint16_t*)&details.Params[4]; SetState(ActorState::ForceDisableCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); async_return true; } void StaticRadialLight::OnUpdate(float timeMult) { // Nothing to do... } void StaticRadialLight::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = _intensity; light.Brightness = _brightness; light.RadiusNear = _radiusNear; light.RadiusFar = _radiusFar; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Lighting/StaticRadialLight.h000066400000000000000000000011241512772601700302360ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Lighting { /** @brief Static radial light */ class StaticRadialLight : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: StaticRadialLight(); static void Preload(const ActorActivationDetails& details) {} protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; private: float _intensity; float _brightness; float _radiusNear; float _radiusFar; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/000077500000000000000000000000001512772601700252755ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/LocalPlayerOnServer.cpp000066400000000000000000000013101512772601700316670ustar00rootroot00000000000000#include "LocalPlayerOnServer.h" #if defined(WITH_MULTIPLAYER) #include "../../PreferencesCache.h" namespace Jazz2::Actors::Multiplayer { LocalPlayerOnServer::LocalPlayerOnServer(std::shared_ptr peerDesc) { _peerDesc = std::move(peerDesc); _peerDesc->Player = this; } void LocalPlayerOnServer::SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) { if (reason == SetCurrentWeaponReason::AddAmmo && !PreferencesCache::SwitchToNewWeapon) { return; } PlayerOnServer::SetCurrentWeapon(weaponType, reason); } void LocalPlayerOnServer::EmitWeaponFlare() { PlayerOnServer::EmitWeaponFlare(); // TODO: Broadcast flare of local players too } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/LocalPlayerOnServer.h000066400000000000000000000010521512772601700313370ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "PlayerOnServer.h" namespace Jazz2::Actors::Multiplayer { /** @brief Local player in online session */ class LocalPlayerOnServer : public PlayerOnServer { DEATH_RUNTIME_OBJECT(PlayerOnServer); public: LocalPlayerOnServer(std::shared_ptr peerDesc); protected: void SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) override; /** @brief Emits weapon flare */ void EmitWeaponFlare() override; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/MpPlayer.cpp000066400000000000000000000004501512772601700275310ustar00rootroot00000000000000#include "MpPlayer.h" #if defined(WITH_MULTIPLAYER) namespace Jazz2::Actors::Multiplayer { std::shared_ptr MpPlayer::GetPeerDescriptor() { return _peerDesc; } std::shared_ptr MpPlayer::GetPeerDescriptor() const { return _peerDesc; } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/MpPlayer.h000066400000000000000000000013671512772601700272060ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../Player.h" #include "../../Multiplayer/PeerDescriptor.h" using namespace Jazz2::Multiplayer; namespace Jazz2::Multiplayer { class MpLevelHandler; } namespace Jazz2::Actors::Multiplayer { /** @brief Player in online session */ class MpPlayer : public Player { DEATH_RUNTIME_OBJECT(Player); friend class Jazz2::Multiplayer::MpLevelHandler; public: /** @brief Returns session peer descriptor */ std::shared_ptr GetPeerDescriptor(); /** @overload */ std::shared_ptr GetPeerDescriptor() const; protected: #ifndef DOXYGEN_GENERATING_OUTPUT std::shared_ptr _peerDesc; #endif }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/PlayerOnServer.cpp000066400000000000000000000062731512772601700307310ustar00rootroot00000000000000#include "PlayerOnServer.h" #if defined(WITH_MULTIPLAYER) #include "../Weapons/ShotBase.h" #include "../Weapons/FreezerShot.h" #include "../../Multiplayer/MpLevelHandler.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Multiplayer { PlayerOnServer::PlayerOnServer() : _lastAttackerTimeout(0.0f), _canTakeDamage(true), _justWarped(false) { } void PlayerOnServer::OnUpdate(float timeMult) { MpPlayer::OnUpdate(timeMult); if (_lastAttackerTimeout > 0.0f) { _lastAttackerTimeout -= timeMult; if (_lastAttackerTimeout <= 0.0f) { _lastAttacker = nullptr; } } } bool PlayerOnServer::OnHandleCollision(std::shared_ptr other) { // TODO: Check player special move here if (auto* weaponOwner = MpLevelHandler::GetWeaponOwner(other.get())) { auto* otherPlayerOnServer = static_cast(weaponOwner); if (_health > 0 && GetPeerDescriptor()->Team != otherPlayerOnServer->GetPeerDescriptor()->Team) { bool otherIsPlayer = false; if (auto* anotherPlayer = runtime_cast(other.get())) { bool isAttacking = IsAttacking(); if (!isAttacking && !anotherPlayer->IsAttacking()) { return true; } if (isAttacking) { return false; } otherIsPlayer = true; } _lastAttacker = otherPlayerOnServer->shared_from_this(); _lastAttackerTimeout = 300.0f; // Decrease remaining shield time by 5 secs if (_activeShieldTime > (5.0f * FrameTimer::FramesPerSecond)) { _activeShieldTime -= (5.0f * FrameTimer::FramesPerSecond); } else if (auto* freezerShot = runtime_cast(other.get())) { Freeze(3.0f * FrameTimer::FramesPerSecond); } else { TakeDamage(1, 4.0f * (_pos.X > other->GetPos().X ? 1.0f : -1.0f)); } if (!otherIsPlayer) { other->DecreaseHealth(INT32_MAX); } return true; } } return Player::OnHandleCollision(std::move(other)); } bool PlayerOnServer::CanCauseDamage(ActorBase* collider) { if (_canTakeDamage && _health > 0) { if (auto* weaponOwner = MpLevelHandler::GetWeaponOwner(collider)) { return (static_cast(weaponOwner)->GetPeerDescriptor()->Team != GetPeerDescriptor()->Team); } } return false; } bool PlayerOnServer::TakeDamage(std::int32_t amount, float pushForce, bool ignoreInvulnerable) { if (!_canTakeDamage || !MpPlayer::TakeDamage(amount, pushForce, ignoreInvulnerable)) { return false; } static_cast(_levelHandler)->HandlePlayerTakeDamage(this, amount, pushForce); return true; } bool PlayerOnServer::AddLives(std::int32_t count) { // TODO: OneUpCollectible are currently blocked in multiplayer return false; } bool PlayerOnServer::MorphTo(PlayerType type) { if (!MpPlayer::MorphTo(type)) { return false; } static_cast(_levelHandler)->HandlePlayerMorphTo(this, type); return true; } bool PlayerOnServer::IsAttacking() const { if (_currentSpecialMove == SpecialMoveType::Buttstomp && _currentTransition != nullptr && _sugarRushLeft <= 0.0f) { return false; } return (_currentSpecialMove != SpecialMoveType::None || _sugarRushLeft > 0.0f); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/PlayerOnServer.h000066400000000000000000000016421512772601700303710ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "MpPlayer.h" using namespace Jazz2::Multiplayer; namespace Jazz2::Actors::Multiplayer { /** @brief Player on the server in online session */ class PlayerOnServer : public MpPlayer { DEATH_RUNTIME_OBJECT(MpPlayer); friend class Jazz2::Multiplayer::MpLevelHandler; public: PlayerOnServer(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; bool TakeDamage(std::int32_t amount, float pushForce = 0.0f, bool ignoreInvulnerable = false) override; bool AddLives(std::int32_t count) override; bool MorphTo(PlayerType type) override; protected: std::shared_ptr _lastAttacker; float _lastAttackerTimeout; bool _canTakeDamage; bool _justWarped; void OnUpdate(float timeMult) override; bool IsAttacking() const; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/RemotablePlayer.cpp000066400000000000000000000066361512772601700311030ustar00rootroot00000000000000#include "RemotablePlayer.h" #if defined(WITH_MULTIPLAYER) #include "../../Multiplayer/MpLevelHandler.h" #include "../../../nCine/Base/Clock.h" namespace Jazz2::Actors::Multiplayer { RemotablePlayer::RemotablePlayer(std::shared_ptr peerDesc) : ChangingWeaponFromServer(false), RespawnPending(false), _warpPending(false) { _peerDesc = std::move(peerDesc); _peerDesc->Player = this; } Task RemotablePlayer::OnActivatedAsync(const ActorActivationDetails& details) { async_return async_await Player::OnActivatedAsync(details); } bool RemotablePlayer::OnPerish(ActorBase* collider) { return Player::OnPerish(collider); } void RemotablePlayer::OnUpdate(float timeMult) { Player::OnUpdate(timeMult); if (_levelExiting != LevelExitingState::None) { OnLevelChanging(nullptr, ExitType::None); } } void RemotablePlayer::OnWaterSplash(Vector2f pos, bool inwards) { // Already created and broadcasted by the server } bool RemotablePlayer::FireCurrentWeapon(WeaponType weaponType) { if (_weaponCooldown > 0.0f) { return (weaponType != WeaponType::TNT); } return true; } void RemotablePlayer::SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) { if (_currentWeapon == weaponType) { return; } Player::SetCurrentWeapon(weaponType, reason); if (!ChangingWeaponFromServer) { static_cast(_levelHandler)->HandlePlayerWeaponChanged(this, reason); } } void RemotablePlayer::WarpIn(ExitType exitType) { if (exitType != (ExitType)0xFF) { OnLevelChanging(this, exitType); return; } EndDamagingMove(); SetState(ActorState::IsInvulnerable, true); SetState(ActorState::ApplyGravitation, false); SetAnimation(_currentAnimation->State & ~(AnimState::Uppercut | AnimState::Buttstomp)); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; _pushFramesLeft = 0.0f; _warpPending = true; // For warping from the water _renderer.setRotation(0.0f); PlayPlayerSfx("WarpIn"_s); SetPlayerTransition(_isFreefall ? AnimState::TransitionWarpInFreefall : AnimState::TransitionWarpIn, false, true, SpecialMoveType::None, [this]() { if (_warpPending) { _renderer.setDrawEnabled(false); } }); } void RemotablePlayer::MoveRemotely(Vector2f pos, Vector2f speed) { Vector2f posPrev = _pos; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); _speed = speed; if (_warpPending) { _warpPending = false; _trailLastPos = _pos; PlayPlayerSfx("WarpOut"_s); _levelHandler->HandlePlayerWarped(this, posPrev, WarpFlags::Default); _renderer.setDrawEnabled(true); _isFreefall |= CanFreefall(); SetPlayerTransition(_isFreefall ? AnimState::TransitionWarpOutFreefall : AnimState::TransitionWarpOut, false, true, SpecialMoveType::None, [this]() { SetState(ActorState::IsInvulnerable, false); SetState(ActorState::ApplyGravitation, true); _controllable = true; }); } else { _levelHandler->HandlePlayerWarped(this, posPrev, WarpFlags::Fast); } } bool RemotablePlayer::Respawn(Vector2f pos) { bool success = MpPlayer::Respawn(pos); if (!success) { // Player didn't have enough time to die completely, respawn it when HandlePlayerDied() will be called RespawnPos = pos; RespawnPending = true; return false; } return true; } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/RemotablePlayer.h000066400000000000000000000023471512772601700305430ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "MpPlayer.h" namespace Jazz2::Actors::Multiplayer { /** @brief Remotable player in online session */ class RemotablePlayer : public MpPlayer { DEATH_RUNTIME_OBJECT(MpPlayer); public: /** @brief Whether current weapon is being changed by the server */ bool ChangingWeaponFromServer; /** @brief Whether the player should be respawned */ bool RespawnPending; /** @brief The position at which the player should respawn */ Vector2f RespawnPos; RemotablePlayer(std::shared_ptr peerDesc); /** @brief Warps the player in */ void WarpIn(ExitType exitType); /** @brief Moves the player remotely */ void MoveRemotely(Vector2f pos, Vector2f speed); bool Respawn(Vector2f pos) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; void OnUpdate(float timeMult) override; void OnWaterSplash(Vector2f pos, bool inwards) override; bool FireCurrentWeapon(WeaponType weaponType) override; void SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) override; private: bool _warpPending; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/RemoteActor.cpp000066400000000000000000000114341512772601700302300ustar00rootroot00000000000000#include "RemoteActor.h" #if defined(WITH_MULTIPLAYER) #include "../../../nCine/Base/Clock.h" namespace Jazz2::Actors::Multiplayer { RemoteActor::RemoteActor() : _stateBufferPos(0), _lastAnim(AnimState::Idle), _isAttachedLocally(false) { } Task RemoteActor::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::PreserveOnRollback, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); Clock& c = nCine::clock(); std::uint64_t now = c.now() * 1000 / c.frequency(); for (std::int32_t i = 0; i < std::int32_t(arraySize(_stateBuffer)); i++) { _stateBuffer[i].Time = now - arraySize(_stateBuffer) + i; _stateBuffer[i].Pos = Vector2f(details.Pos.X, details.Pos.Y); } async_return true; } void RemoteActor::OnUpdate(float timeMult) { if (!_isAttachedLocally) { Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); std::int64_t renderTime = now - ServerDelay; std::int32_t nextIdx = _stateBufferPos - 1; if (nextIdx < 0) { nextIdx += std::int32_t(arraySize(_stateBuffer)); } if (renderTime <= _stateBuffer[nextIdx].Time) { std::int32_t prevIdx; while (true) { prevIdx = nextIdx - 1; if (prevIdx < 0) { prevIdx += std::int32_t(arraySize(_stateBuffer)); } if (prevIdx == _stateBufferPos || _stateBuffer[prevIdx].Time <= renderTime) { break; } nextIdx = prevIdx; } Vector2f pos; std::int64_t timeRange = (_stateBuffer[nextIdx].Time - _stateBuffer[prevIdx].Time); if (timeRange > 0) { float lerp = (float)(renderTime - _stateBuffer[prevIdx].Time) / timeRange; pos = _stateBuffer[prevIdx].Pos + (_stateBuffer[nextIdx].Pos - _stateBuffer[prevIdx].Pos) * lerp; } else { pos = _stateBuffer[nextIdx].Pos; } MoveInstantly(pos, MoveType::Absolute | MoveType::Force); } } ActorBase::OnUpdate(timeMult); } void RemoteActor::OnAttach(ActorBase* parent) { _isAttachedLocally = true; } void RemoteActor::OnDetach(ActorBase* parent) { _isAttachedLocally = false; } void RemoteActor::AssignMetadata(std::uint8_t flags, ActorState state, StringView path, AnimState anim, float rotation, float scaleX, float scaleY, ActorRendererType rendererType) { constexpr ActorState RemotedFlags = ActorState::Illuminated | ActorState::IsInvulnerable | ActorState::CollideWithOtherActors | ActorState::CollideWithSolidObjects | ActorState::IsSolidObject | ActorState::CollideWithTilesetReduced | ActorState::CollideWithSolidObjectsBelow | ActorState::ExcludeSimilar; RequestMetadata(path); SetAnimation(anim); SetState((GetState() & ~RemotedFlags) | (state & RemotedFlags)); _renderer.Initialize(rendererType); _renderer.setRotation(rotation); SyncMiscWithServer(flags); } void RemoteActor::SyncPositionWithServer(Vector2f pos) { Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); if (_renderer.isDrawEnabled()) { // Actor is still visible, enable interpolation _stateBuffer[_stateBufferPos].Time = now; _stateBuffer[_stateBufferPos].Pos = pos; } else { // Actor was hidden before, reset state buffer to disable interpolation std::int32_t stateBufferPrevPos = _stateBufferPos - 1; if (stateBufferPrevPos < 0) { stateBufferPrevPos += std::int32_t(arraySize(_stateBuffer)); } std::int64_t renderTime = now - ServerDelay; _stateBuffer[stateBufferPrevPos].Time = renderTime; _stateBuffer[stateBufferPrevPos].Pos = pos; _stateBuffer[_stateBufferPos].Time = renderTime; _stateBuffer[_stateBufferPos].Pos = pos; } _stateBufferPos++; if (_stateBufferPos >= std::int32_t(arraySize(_stateBuffer))) { _stateBufferPos = 0; } } void RemoteActor::SyncAnimationWithServer(AnimState anim, float rotation, float scaleX, float scaleY, Actors::ActorRendererType rendererType) { if (_lastAnim != anim) { _lastAnim = anim; SetAnimation(anim); } _renderer.setRotation(rotation); _renderer.setScale(scaleX, scaleY); _renderer.Initialize(rendererType); } void RemoteActor::SyncMiscWithServer(std::uint8_t flags) { _renderer.setDrawEnabled((flags & 0x04) != 0); _renderer.AnimPaused = (flags & 0x08) != 0; SetFacingLeft((flags & 0x10) != 0); _renderer.setFlippedY((flags & 0x20) != 0); bool justWarped = (flags & 0x40) != 0; if (justWarped) { Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); std::int32_t stateBufferPrevPos = _stateBufferPos - 1; if (stateBufferPrevPos < 0) { stateBufferPrevPos += std::int32_t(arraySize(_stateBuffer)); } Vector2f pos = _stateBuffer[stateBufferPrevPos].Pos; for (std::size_t i = 0; i < arraySize(_stateBuffer); i++) { _stateBuffer[i].Time = now; _stateBuffer[i].Pos = pos; } } } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/RemoteActor.h000066400000000000000000000022661512772601700277000ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../ActorBase.h" namespace Jazz2::Actors::Multiplayer { /** @brief Remote object in online session */ class RemoteActor : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: RemoteActor(); void AssignMetadata(std::uint8_t flags, ActorState state, StringView path, AnimState anim, float rotation, float scaleX, float scaleY, ActorRendererType rendererType); void SyncPositionWithServer(Vector2f pos); void SyncAnimationWithServer(AnimState anim, float rotation, float scaleX, float scaleY, Actors::ActorRendererType rendererType); void SyncMiscWithServer(std::uint8_t flags); protected: #ifndef DOXYGEN_GENERATING_OUTPUT struct StateFrame { std::int64_t Time; Vector2f Pos; }; static constexpr std::int64_t ServerDelay = 64; StateFrame _stateBuffer[8]; std::int32_t _stateBufferPos; AnimState _lastAnim; bool _isAttachedLocally; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnAttach(ActorBase* parent) override; void OnDetach(ActorBase* parent) override; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/RemotePlayerOnServer.cpp000066400000000000000000000223461512772601700321040ustar00rootroot00000000000000#include "RemotePlayerOnServer.h" #if defined(WITH_MULTIPLAYER) #include "../Weapons/ShotBase.h" #include "../../Multiplayer/MpLevelHandler.h" #include "../../../nCine/Base/Clock.h" namespace Jazz2::Actors::Multiplayer { RemotePlayerOnServer::RemotePlayerOnServer(std::shared_ptr peerDesc) : _stateBufferPos(0), Flags(PlayerFlags::None), PressedKeys(0), PressedKeysLast(0), UpdatedFrame(0) { _peerDesc = std::move(peerDesc); _peerDesc->Player = this; } Task RemotePlayerOnServer::OnActivatedAsync(const ActorActivationDetails& details) { Clock& c = nCine::clock(); std::uint64_t now = c.now() * 1000 / c.frequency(); for (std::int32_t i = 0; i < std::int32_t(arraySize(_stateBuffer)); i++) { _stateBuffer[i].Time = now - arraySize(_stateBuffer) + i; _stateBuffer[i].Pos = Vector2f(details.Pos.X, details.Pos.Y); } async_return async_await PlayerOnServer::OnActivatedAsync(details); } void RemotePlayerOnServer::OnUpdate(float timeMult) { Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); std::int64_t renderTime = now - ServerDelay; std::int32_t nextIdx = _stateBufferPos - 1; if (nextIdx < 0) { nextIdx += std::int32_t(arraySize(_stateBuffer)); } if (renderTime <= _stateBuffer[nextIdx].Time) { std::int32_t prevIdx; while (true) { prevIdx = nextIdx - 1; if (prevIdx < 0) { prevIdx += std::int32_t(arraySize(_stateBuffer)); } if (prevIdx == _stateBufferPos || _stateBuffer[prevIdx].Time <= renderTime) { break; } nextIdx = prevIdx; } std::int64_t timeRange = (_stateBuffer[nextIdx].Time - _stateBuffer[prevIdx].Time); if (timeRange > 0) { float lerp = (float)(renderTime - _stateBuffer[prevIdx].Time) / timeRange; _displayPos = _stateBuffer[prevIdx].Pos + (_stateBuffer[nextIdx].Pos - _stateBuffer[prevIdx].Pos) * lerp; } else { _displayPos = _stateBuffer[nextIdx].Pos; } } PlayerOnServer::OnUpdate(timeMult); _renderer.setPosition(_displayPos); } bool RemotePlayerOnServer::IsContinuousJumpAllowed() const { return (Flags & PlayerFlags::EnableContinuousJump) == PlayerFlags::EnableContinuousJump; } bool RemotePlayerOnServer::IsLedgeClimbAllowed() const { return (_peerDesc->EnableLedgeClimb && PlayerOnServer::IsLedgeClimbAllowed()); } bool RemotePlayerOnServer::OnHandleCollision(std::shared_ptr other) { // TODO: Remove this override return PlayerOnServer::OnHandleCollision(other); } bool RemotePlayerOnServer::OnLevelChanging(Actors::ActorBase* initiator, ExitType exitType) { LevelExitingState lastState = _levelExiting; bool success = PlayerOnServer::OnLevelChanging(initiator, exitType); if (lastState == LevelExitingState::None && _health > 0) { // Level changing just started, send the request to the player as WarpIn packet static_cast(_levelHandler)->HandlePlayerLevelChanging(this, exitType); } return success; } PlayerCarryOver RemotePlayerOnServer::PrepareLevelCarryOver() { // Skip all remote players return PlayerCarryOver{}; } void RemotePlayerOnServer::SyncWithServer(Vector2f pos, Vector2f speed, PlayerFlags flags) { if (_health <= 0) { // Don't sync dead players to avoid cheating return; } Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); Flags = flags; bool wasVisible = _renderer.isDrawEnabled(); // Visibility is not synced from the client, because it should be already almost in sync on the server //_renderer.setDrawEnabled((flags & PlayerFlags::IsVisible) == PlayerFlags::IsVisible); SetFacingLeft((flags & PlayerFlags::IsFacingLeft) == PlayerFlags::IsFacingLeft); _isActivelyPushing = (flags & PlayerFlags::IsActivelyPushing) == PlayerFlags::IsActivelyPushing; if (wasVisible) { // Actor is still visible, enable interpolation _stateBuffer[_stateBufferPos].Time = now; _stateBuffer[_stateBufferPos].Pos = pos; } else { // Actor was hidden before, reset state buffer to disable interpolation std::int32_t stateBufferPrevPos = _stateBufferPos - 1; if (stateBufferPrevPos < 0) { stateBufferPrevPos += std::int32_t(arraySize(_stateBuffer)); } std::int64_t renderTime = now - ServerDelay; _stateBuffer[stateBufferPrevPos].Time = renderTime; _stateBuffer[stateBufferPrevPos].Pos = pos; _stateBuffer[_stateBufferPos].Time = renderTime; _stateBuffer[_stateBufferPos].Pos = pos; } _stateBufferPos++; if (_stateBufferPos >= std::int32_t(arraySize(_stateBuffer))) { _stateBufferPos = 0; } // TODO: Set actual pos and speed to the newest value _pos = pos; _speed = speed; } void RemotePlayerOnServer::ForceResyncWithServer(Vector2f pos, Vector2f speed) { Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); _pos = pos; _speed = speed; for (std::size_t i = 0; i < arraySize(_stateBuffer); i++) { _stateBuffer[i].Time = now; _stateBuffer[i].Pos = pos; } } void RemotePlayerOnServer::OnHitSpring(Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY, bool& removeSpecialMove) { if (!static_cast(_levelHandler)->HandlePlayerSpring(this, pos, force, keepSpeedX, keepSpeedY)) { return; } PlayerOnServer::OnHitSpring(pos, force, keepSpeedX, keepSpeedY, removeSpecialMove); } void RemotePlayerOnServer::WarpToPosition(Vector2f pos, WarpFlags flags) { PlayerOnServer::WarpToPosition(pos, flags); static_cast(_levelHandler)->HandlePlayerBeforeWarp(this, pos, flags); } bool RemotePlayerOnServer::SetModifier(Modifier modifier, const std::shared_ptr& decor) { if (!PlayerOnServer::SetModifier(modifier, decor)) { return false; } static_cast(_levelHandler)->HandlePlayerSetModifier(this, modifier, decor); return true; } bool RemotePlayerOnServer::Freeze(float timeLeft) { if (!PlayerOnServer::Freeze(timeLeft)) { return false; } static_cast(_levelHandler)->HandlePlayerFreeze(this, timeLeft); return true; } void RemotePlayerOnServer::SetInvulnerability(float timeLeft, InvulnerableType type) { PlayerOnServer::SetInvulnerability(timeLeft, type); static_cast(_levelHandler)->HandlePlayerSetInvulnerability(this, timeLeft, type); } void RemotePlayerOnServer::AddScore(std::int32_t amount) { PlayerOnServer::AddScore(amount); static_cast(_levelHandler)->HandlePlayerSetScore(this, _score); } bool RemotePlayerOnServer::AddHealth(std::int32_t amount) { if (!PlayerOnServer::AddHealth(amount)) { return false; } static_cast(_levelHandler)->HandlePlayerSetHealth(this, _health); return true; } bool RemotePlayerOnServer::AddLives(std::int32_t count) { if (!PlayerOnServer::AddLives(count)) { return false; } static_cast(_levelHandler)->HandlePlayerSetLives(this, _lives); return true; } bool RemotePlayerOnServer::AddAmmo(WeaponType weaponType, std::int16_t count) { if (!PlayerOnServer::AddAmmo(weaponType, count)) { return false; } static_cast(_levelHandler)->HandlePlayerRefreshAmmo(this, weaponType); return true; } void RemotePlayerOnServer::AddWeaponUpgrade(WeaponType weaponType, std::uint8_t upgrade) { PlayerOnServer::AddWeaponUpgrade(weaponType, upgrade); static_cast(_levelHandler)->HandlePlayerRefreshWeaponUpgrades(this, weaponType); } bool RemotePlayerOnServer::SetDizzy(float timeLeft) { bool success = PlayerOnServer::SetDizzy(timeLeft); static_cast(_levelHandler)->HandlePlayerSetDizzy(this, timeLeft); return success; } bool RemotePlayerOnServer::SetShield(ShieldType shieldType, float timeLeft) { if (!PlayerOnServer::SetShield(shieldType, timeLeft)) { return false; } static_cast(_levelHandler)->HandlePlayerSetShield(this, shieldType, timeLeft); return true; } bool RemotePlayerOnServer::IncreaseShieldTime(float timeLeft) { if (!PlayerOnServer::IncreaseShieldTime(timeLeft)) { return false; } static_cast(_levelHandler)->HandlePlayerSetShield(this, _activeShield, _activeShieldTime); return true; } bool RemotePlayerOnServer::FireCurrentWeapon(WeaponType weaponType) { std::uint16_t prevAmmo = _weaponAmmo[(std::int32_t)weaponType]; bool success = PlayerOnServer::FireCurrentWeapon(weaponType); if (prevAmmo != _weaponAmmo[(std::int32_t)weaponType]) { static_cast(_levelHandler)->HandlePlayerRefreshAmmo(this, weaponType); } return success; } void RemotePlayerOnServer::EmitWeaponFlare() { PlayerOnServer::EmitWeaponFlare(); static_cast(_levelHandler)->HandlePlayerEmitWeaponFlare(this); } void RemotePlayerOnServer::SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) { PlayerOnServer::SetCurrentWeapon(weaponType, reason); static_cast(_levelHandler)->HandlePlayerWeaponChanged(this, reason); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Multiplayer/RemotePlayerOnServer.h000066400000000000000000000055701512772601700315510ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "PlayerOnServer.h" namespace Jazz2::Actors::Multiplayer { /** @brief Remote player in online session */ class RemotePlayerOnServer : public PlayerOnServer { DEATH_RUNTIME_OBJECT(PlayerOnServer); public: /** @brief State flags of the remote player */ enum class PlayerFlags { None = 0, SpecialMoveMask = 0x07, IsFacingLeft = 0x10, IsVisible = 0x20, IsActivelyPushing = 0x40, JustWarped = 0x100, EnableContinuousJump = 0x200 }; /** @brief State flags */ PlayerFlags Flags; /** @brief Pressed keys */ std::uint64_t PressedKeys; /** @brief Previously pressed keys */ std::uint64_t PressedKeysLast; /** @brief Last frame when pressed keys were updated */ std::uint32_t UpdatedFrame; DEATH_PRIVATE_ENUM_FLAGS(PlayerFlags); RemotePlayerOnServer(std::shared_ptr peerDesc); bool IsContinuousJumpAllowed() const override; bool IsLedgeClimbAllowed() const override; bool OnHandleCollision(std::shared_ptr other) override; bool OnLevelChanging(Actors::ActorBase* initiator, ExitType exitType) override; PlayerCarryOver PrepareLevelCarryOver() override; void WarpToPosition(Vector2f pos, WarpFlags flags) override; bool SetModifier(Modifier modifier, const std::shared_ptr& decor) override; bool Freeze(float timeLeft) override; void SetInvulnerability(float timeLeft, InvulnerableType type) override; void AddScore(std::int32_t amount) override; bool AddHealth(std::int32_t amount) override; bool AddLives(std::int32_t count) override; bool AddAmmo(WeaponType weaponType, std::int16_t count) override; void AddWeaponUpgrade(WeaponType weaponType, std::uint8_t upgrade) override; bool SetDizzy(float timeLeft) override; bool SetShield(ShieldType shieldType, float timeLeft) override; bool IncreaseShieldTime(float timeLeft) override; bool FireCurrentWeapon(WeaponType weaponType) override; void EmitWeaponFlare() override; void SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) override; /** @brief Synchronizes the player with server */ void SyncWithServer(Vector2f pos, Vector2f speed, PlayerFlags flags); /** @brief Forcefully resynchronizes the player with server (e.g., after respawning or warping) */ void ForceResyncWithServer(Vector2f pos, Vector2f speed); protected: #ifndef DOXYGEN_GENERATING_OUTPUT struct StateFrame { std::int64_t Time; Vector2f Pos; }; static constexpr std::int64_t ServerDelay = 64; StateFrame _stateBuffer[8]; std::int32_t _stateBufferPos; Vector2f _displayPos; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnHitSpring(Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY, bool& removeSpecialMove) override; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Player.cpp000066400000000000000000004374611512772601700247450ustar00rootroot00000000000000#include "Player.h" #include "../ContentResolver.h" #include "../ILevelHandler.h" #include "../Events/EventMap.h" #include "../Tiles/TileMap.h" #include "../PreferencesCache.h" #include "SolidObjectBase.h" #include "Explosion.h" #include "PlayerCorpse.h" #include "Environment/Bird.h" #include "Environment/BonusWarp.h" #include "Environment/Spring.h" #include "Enemies/EnemyBase.h" #include "Enemies/TurtleShell.h" #include "Weapons/BlasterShot.h" #include "Weapons/BouncerShot.h" #include "Weapons/ElectroShot.h" #include "Weapons/FreezerShot.h" #include "Weapons/PepperShot.h" #include "Weapons/RFShot.h" #include "Weapons/SeekerShot.h" #include "Weapons/ShieldFireShot.h" #include "Weapons/ShieldLightningShot.h" #include "Weapons/ShieldWaterShot.h" #include "Weapons/ToasterShot.h" #include "Weapons/TNT.h" #include "Weapons/Thunderbolt.h" #include "../../nCine/tracy.h" #include "../../nCine/Base/Random.h" #include "../../nCine/Base/FrameTimer.h" #include "../../nCine/Graphics/RenderQueue.h" #include #include using namespace Jazz2::Tiles; namespace Jazz2::Actors { // Player-specific animations static constexpr AnimState Shield = (AnimState)536870928; static constexpr AnimState ShieldFire = (AnimState)536870929; static constexpr AnimState ShieldLightning = (AnimState)536870931; static constexpr AnimState ShieldWater = (AnimState)536870930; static constexpr AnimState SugarRush = (AnimState)536870913; static constexpr AnimState WeaponFlare = (AnimState)536870950; static constexpr AnimState CompositeAnimMask = (AnimState)0xFFF83F60; static constexpr AnimState TransformFrogFromJazz = (AnimState)0x60000000; static constexpr AnimState TransformFrogFromSpaz = (AnimState)0x60000001; static constexpr AnimState TransformFrogFromLori = (AnimState)0x60000002; Player::Player() : _playerIndex(0), _playerType(PlayerType::Jazz), _playerTypeOriginal(PlayerType::Jazz), _isActivelyPushing(false), _wasActivelyPushing(false), _controllable(true), _controllableExternal(true), _controllableTimeout(0.0f), _lastExitType(ExitType::None), _wasUpPressed(false), _wasDownPressed(false), _wasJumpPressed(false), _wasFirePressed(false), _isRunPressed(false), _currentSpecialMove(SpecialMoveType::None), _isAttachedToPole(false), _canPushFurther(false), _copterFramesLeft(0.0f), _fireFramesLeft(0.0f), _pushFramesLeft(0.0f), _waterCooldownLeft(0.0f), _levelExiting(LevelExitingState::None), _isFreefall(false), _inWater(false), _isLifting(false), _isSpring(false), _inShallowWater(-1), _activeModifier(Modifier::None), _externalForceCooldown(0.0f), _springCooldown(0.0f), _inIdleTransition(false), _inLedgeTransition(false), _canDoubleJump(true), _carryingObject(nullptr), _lives(0), _coins(0), _coinsCheckpoint(0), _foodEaten(0), _foodEatenCheckpoint(0), _score(0), _checkpointLight(1.0f), _sugarRushLeft(0.0f), _sugarRushStarsTime(0.0f), _shieldSpawnTime(ShieldDisabled), _gems{}, _gemsCheckpoint{}, _gemsTotal{}, _gemsPitch(0), _gemsTimer(0.0f), _bonusWarpTimer(0.0f), _suspendType(SuspendType::None), _suspendTime(0.0f), _invulnerableTime(0.0f), _invulnerableBlinkTime(0.0f), _jumpTime(0.0f), _idleTime(0.0f), _hitFloorTime(5.0f), _keepRunningTime(0.0f), _lastPoleTime(0.0f), _inTubeTime(0.0f), _dizzyTime(0.0f), _activeShield(ShieldType::None), _activeShieldTime(0.0f), _weaponFlareTime(0.0f), _weaponCooldown(0.0f), _weaponAllowed(true), _weaponWheelState(WeaponWheelState::Hidden) { } Player::~Player() { if (_spawnedBird != nullptr) { _spawnedBird->FlyAway(); _spawnedBird = nullptr; } #if defined(WITH_AUDIO) if (_copterSound != nullptr) { _copterSound->stop(); _copterSound = nullptr; } if (_weaponSound != nullptr) { _weaponSound->stop(); _weaponSound = nullptr; } #endif } Task Player::OnActivatedAsync(const ActorActivationDetails& details) { _playerTypeOriginal = (PlayerType)details.Params[0]; _playerType = _playerTypeOriginal; _playerIndex = details.Params[1]; switch (_playerType) { case PlayerType::Jazz: async_await RequestMetadataAsync("Interactive/PlayerJazz"_s); break; case PlayerType::Spaz: async_await RequestMetadataAsync("Interactive/PlayerSpaz"_s); break; case PlayerType::Lori: async_await RequestMetadataAsync("Interactive/PlayerLori"_s); break; case PlayerType::Frog: async_await RequestMetadataAsync("Interactive/PlayerFrog"_s); break; } SetAnimation(AnimState::Fall); std::memset(_weaponAmmo, 0, sizeof(_weaponAmmo)); std::memset(_weaponAmmoCheckpoint, 0, sizeof(_weaponAmmoCheckpoint)); std::memset(_weaponUpgrades, 0, sizeof(_weaponUpgrades)); std::memset(_weaponUpgradesCheckpoint, 0, sizeof(_weaponUpgradesCheckpoint)); _weaponAmmo[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; _weaponAmmoCheckpoint[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; SetState(ActorState::PreserveOnRollback | ActorState::CollideWithTilesetReduced | ActorState::CollideWithSolidObjects | ActorState::IsSolidObject | ActorState::ExcludeSimilar, true); _health = 5; _maxHealth = _health; _currentWeapon = WeaponType::Blaster; _checkpointPos = Vector2f((float)details.Pos.X, (float)details.Pos.Y); _checkpointLight = _levelHandler->GetDefaultAmbientLight(); _trailLastPos = _checkpointPos; async_return true; } bool Player::CanJump() const { return (GetState(ActorState::CanJump) || _carryingObject != nullptr); } bool Player::CanBreakSolidObjects() const { if (_sugarRushLeft > 0.0f) { return true; } if (_currentSpecialMove == SpecialMoveType::Buttstomp && _currentTransition != nullptr) { // Buttstomp is probably in starting transition, do nothing yet return false; } return (_currentSpecialMove != SpecialMoveType::None); } bool Player::CanMoveVertically() const { return (_inWater || _activeModifier != Modifier::None); } bool Player::IsContinuousJumpAllowed() const { return PreferencesCache::EnableContinuousJump; } bool Player::IsLedgeClimbAllowed() const { return PreferencesCache::EnableLedgeClimb; } bool Player::OnTileDeactivated() { // Player cannot be deactivated return false; } void Player::OnUpdate(float timeMult) { ZoneScoped; #if defined(DEATH_DEBUG) if (PreferencesCache::AllowCheats && _levelHandler->PlayerActionPressed(this, PlayerAction::ChangeWeapon)) { float moveDistance = (_levelHandler->PlayerActionPressed(this, PlayerAction::Run) ? 400.0f : 100.0f); if (_levelHandler->PlayerActionHit(this, PlayerAction::Left)) { MoveInstantly(Vector2f(-moveDistance, 0.0f), MoveType::Relative | MoveType::Force); } if (_levelHandler->PlayerActionHit(this, PlayerAction::Right)) { MoveInstantly(Vector2f(moveDistance, 0.0f), MoveType::Relative | MoveType::Force); } if (_levelHandler->PlayerActionHit(this, PlayerAction::Up)) { MoveInstantly(Vector2f(0.0f, -moveDistance), MoveType::Relative | MoveType::Force); } if (_levelHandler->PlayerActionHit(this, PlayerAction::Down)) { MoveInstantly(Vector2f(0.0f, moveDistance), MoveType::Relative | MoveType::Force); } } #endif // Delayed spawning if (_lastExitType != ExitType::None) { _controllableTimeout -= timeMult; if (_controllableTimeout > 0.0f) { return; } bool isFrozen = ((_lastExitType & ExitType::Frozen) == ExitType::Frozen); _isFreefall = (isFrozen || CanFreefall()); SetPlayerTransition(_isFreefall ? AnimState::TransitionWarpOutFreefall : AnimState::TransitionWarpOut, false, true, SpecialMoveType::None, [this, isFrozen]() { SetState(ActorState::ApplyGravitation, true); if (isFrozen) { SetAnimation(AnimState::Freefall); Freeze(100.0f); } else { _controllable = true; // UpdateAnimation() was probably skipped in this step, because _controllable was false, so call it here UpdateAnimation(0.0f); } }); _renderer.setDrawEnabled(true); PlayPlayerSfx("WarpOut"_s, 1.0f / _levelHandler->GetPlayers().size()); _levelHandler->PlayerExecuteRumble(this, "Warp"_s); _lastExitType = ExitType::None; } bool canJumpPrev = CanJump(); OnUpdatePhysics(timeMult); UpdateAnimation(timeMult); CheckSuspendState(timeMult); CheckEndOfSpecialMoves(timeMult); OnHandleWater(); bool areaWeaponAllowed = true; std::int32_t areaWaterBlock = -1; OnHandleAreaEvents(timeMult, areaWeaponAllowed, areaWaterBlock); // Shallow Water if (areaWaterBlock != -1) { if (_inShallowWater == -1) { OnWaterSplash(Vector2f(_pos.X, (float)areaWaterBlock), true); } _inShallowWater = areaWaterBlock; } else if (_inShallowWater != -1) { OnWaterSplash(Vector2f(_pos.X, (float)_inShallowWater), false); _inShallowWater = -1; } OnUpdateTimers(timeMult); OnHandleMovement(timeMult, areaWeaponAllowed, canJumpPrev); // Handle weapon switching if (((_controllable && _controllableExternal) || !_levelHandler->IsReforged()) && _playerType != PlayerType::Frog) { bool isGamepad; if (_levelHandler->PlayerActionHit(this, PlayerAction::ChangeWeapon, true, isGamepad)) { if (!isGamepad || PreferencesCache::WeaponWheel == WeaponWheelStyle::Disabled) { SwitchToNextWeapon(); } } else { for (std::uint32_t i = 0; i <= (std::uint32_t)PlayerAction::SwitchToThunderbolt - (std::uint32_t)PlayerAction::SwitchToBlaster; i++) { if (_levelHandler->PlayerActionHit(this, (PlayerAction)(i + (std::uint32_t)PlayerAction::SwitchToBlaster))) { SwitchToWeaponByIndex(i); } } } } } void Player::OnUpdatePhysics(float timeMult) { // Force collisions every frame even if player doesn't move SetState(ActorState::IsDirty, true); // Process level bounds (if not warping) if (_currentTransition == nullptr || (_currentTransition->State != AnimState::TransitionWarpIn && _currentTransition->State != AnimState::TransitionWarpInFreefall && _currentTransition->State != AnimState::TransitionWarpOut && _currentTransition->State != AnimState::TransitionWarpOutFreefall)) { Vector2f lastPos = _pos; Recti levelBounds = _levelHandler->GetLevelBounds(); if (lastPos.X < levelBounds.X) { lastPos.X = (float)levelBounds.X; _pos = lastPos; } else if (lastPos.X > levelBounds.X + levelBounds.W) { lastPos.X = (float)(levelBounds.X + levelBounds.W); _pos = lastPos; } } // Reset vertical speed if the position is managed by carrying object if (_carryingObject != nullptr) { _speed.Y = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; } PushSolidObjects(timeMult); // Custom implementation of `ActorBase::OnUpdate(timeMult)` TileCollisionParams params = { TileDestructType::Collapse, _speed.Y >= 0.0f }; if (_currentSpecialMove == SpecialMoveType::Sidekick || _sugarRushLeft > 0.0f) { params.DestructType |= TileDestructType::Special; } else if (_currentSpecialMove == SpecialMoveType::Buttstomp || _currentSpecialMove == SpecialMoveType::Uppercut) { params.DestructType |= TileDestructType::Special | TileDestructType::VerticalMove; } if (std::abs(_speed.X) > std::numeric_limits::epsilon() || std::abs(_speed.Y) > std::numeric_limits::epsilon() || _sugarRushLeft > 0.0f) { params.DestructType |= TileDestructType::Speed; params.Speed = (_sugarRushLeft > 0.0f ? 64.0f : std::max(std::abs(_speed.X), std::abs(_speed.Y))); } if (timeMult * (std::abs(_speed.X + _externalForce.X) + std::abs(_speed.Y + _externalForce.Y)) > 20.0f) { TryStandardMovement(timeMult * 0.5f, params); TryStandardMovement(timeMult * 0.5f, params); } else { TryStandardMovement(timeMult, params); } if ((GetState() & ActorState::ApplyGravitation) != ActorState::ApplyGravitation) { _externalForce.Y = std::min(_externalForce.Y + 0.002f * timeMult, 0.0f); } if (params.TilesDestroyed > 0) { AddScore(params.TilesDestroyed * 50); _levelHandler->PlayerExecuteRumble(this, "BreakTile"_s); } OnUpdateHitbox(); //UpdateFrozenState(timeMult); if (_renderer.AnimPaused) { if (_frozenTimeLeft <= 0.0f) { _renderer.AnimPaused = false; _renderer.Initialize(ActorRendererType::Default); for (std::int32_t i = 0; i < 10; i++) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 10), Explosion::Type::IceShrapnel); } Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 90), Explosion::Type::SmokeWhite); _levelHandler->PlayCommonSfx("IceBreak"_s, Vector3f(_pos.X, _pos.Y, 0.0f)); _levelHandler->PlayerExecuteRumble(this, "Hurt"_s); } else { // Cannot be directly in `ActorBase::HandleFrozenStateChange()` due to bug in `BaseSprite::updateRenderCommand()`, // it would be called before `BaseSprite::updateRenderCommand()` but after `SceneNode::transform()` _renderer.Initialize(ActorRendererType::FrozenMask); _frozenTimeLeft -= timeMult; } } } void Player::OnUpdateTimers(float timeMult) { // Invulnerability if (_invulnerableTime > 0.0f) { _invulnerableTime -= timeMult; if (_invulnerableTime <= 0.0f) { SetState(ActorState::IsInvulnerable, false); _renderer.setDrawEnabled(true); _shieldSpawnTime = ShieldDisabled; } else if (_shieldSpawnTime > ShieldDisabled) { _shieldSpawnTime -= timeMult; if (_shieldSpawnTime <= 0.0f) { _shieldSpawnTime += 1.0f; auto* tilemap = _levelHandler->TileMap(); if (tilemap != nullptr) { auto* res = _metadata->FindAnimation(Shield); if (res != nullptr && res->Base->TextureDiffuse != nullptr) { Vector2i texSize = res->Base->TextureDiffuse->GetSize(); Vector2i size = res->Base->FrameDimensions; Tiles::TileMap::DestructibleDebris debris = { }; debris.Pos = _pos; debris.Depth = _renderer.layer() - 2; debris.Size = Vector2f((float)size.X, (float)size.Y); debris.Speed = Vector2f::Zero; debris.Acceleration = Vector2f::Zero; debris.Scale = 0.9f; debris.ScaleSpeed = -0.014f; debris.Alpha = 0.7f; debris.AlphaSpeed = -0.016f; debris.Time = 160.0f; debris.TexScaleX = (size.X / float(texSize.X)); debris.TexBiasX = 0.0f; debris.TexScaleY = (size.Y / float(texSize.Y)); debris.TexBiasY = 0.0f; debris.DiffuseTexture = res->Base->TextureDiffuse.get(); debris.Flags = Tiles::TileMap::DebrisFlags::AdditiveBlending; tilemap->CreateDebris(debris); } } } } else if (_invulnerableBlinkTime >= 0.0f && (_currentTransition == nullptr || _currentTransition->State != AnimState::Hurt)) { _invulnerableBlinkTime -= timeMult; if (_invulnerableBlinkTime <= 0.0f) { _renderer.setDrawEnabled(!_renderer.isDrawEnabled()); _invulnerableBlinkTime = 3.0f; } } else { _renderer.setDrawEnabled(true); } } if (_controllableTimeout > 0.0f) { _controllableTimeout -= timeMult; if (_controllableTimeout <= 0.0f) { _controllable = true; if (_isAttachedToPole) { // Something went wrong, detach and try to continue // To prevent stucking for (std::int32_t i = -1; i > -6; i--) { if (MoveInstantly(Vector2f(_speed.X, (float)i), MoveType::Relative)) { break; } } SetState(ActorState::ApplyGravitation, true); _isAttachedToPole = false; _wasActivelyPushing = false; _controllableTimeout = 4.0f; _lastPoleTime = 10.0f; } } else { _controllable = false; } } if (_jumpTime > 0.0f) { _jumpTime -= timeMult; } if (_externalForceCooldown > 0.0f) { _externalForceCooldown -= timeMult; } if (_springCooldown > 0.0f) { _springCooldown -= timeMult; } if (_weaponCooldown > 0.0f) { _weaponCooldown -= timeMult; } if (_bonusWarpTimer > 0.0f) { _bonusWarpTimer -= timeMult; } if (_lastPoleTime > 0.0f) { _lastPoleTime -= timeMult; } if (_gemsTimer > 0.0f) { _gemsTimer -= timeMult; if (_gemsTimer <= 0.0f) { _gemsPitch = 0; } } if (_waterCooldownLeft > 0.0f) { _waterCooldownLeft -= timeMult; } // Weapons if (_fireFramesLeft > 0.0f) { _fireFramesLeft -= timeMult; if (_fireFramesLeft <= 0.0f) { // Play post-fire animation if ((_currentAnimation->State & (AnimState::Walk | AnimState::Run | AnimState::Dash | AnimState::Buttstomp | AnimState::Swim | AnimState::Airboard | AnimState::Lift | AnimState::Spring)) == AnimState::Idle && (_currentTransition == nullptr || (_currentTransition->State != AnimState::TransitionRunToIdle && _currentTransition->State != AnimState::TransitionDashToIdle)) && !_isAttachedToPole) { if ((_currentAnimation->State & AnimState::Hook) == AnimState::Hook) { SetTransition(AnimState::TransitionHookShootToHook, false); } else if ((_currentAnimation->State & AnimState::Copter) == AnimState::Copter) { SetAnimation(AnimState::Copter); SetTransition(AnimState::TransitionCopterShootToCopter, false); } else if ((_currentAnimation->State & AnimState::Fall) == AnimState::Fall) { SetTransition(AnimState::TransitionFallShootToFall, false); } else if ((_currentAnimation->State & AnimState::Crouch) == AnimState::Crouch) { SetAnimation(AnimState::Crouch, true); } else { SetTransition(AnimState::TransitionShootToIdle, false); } } } } if (_weaponFlareTime > 0.0f) { _weaponFlareTime -= timeMult; } // Dizziness if (_dizzyTime > 0.0f) { _dizzyTime -= timeMult; } // Shield if (_activeShieldTime > 0.0f) { _activeShieldTime -= timeMult; if (_activeShieldTime <= 0.0f) { _activeShield = ShieldType::None; } } // Sugar Rush if (_sugarRushLeft > 0.0f) { _sugarRushLeft -= timeMult; if (_sugarRushLeft > 0.0f) { _sugarRushStarsTime -= timeMult; if (_sugarRushStarsTime <= 0.0f) { _sugarRushStarsTime = Random().FastFloat(2.0f, 8.0f); auto* tilemap = _levelHandler->TileMap(); if (tilemap != nullptr) { auto* res = _metadata->FindAnimation(SugarRush); if (res != nullptr && res->Base->TextureDiffuse != nullptr) { Vector2i texSize = res->Base->TextureDiffuse->GetSize(); Vector2i size = res->Base->FrameDimensions; Vector2i frameConf = res->Base->FrameConfiguration; std::int32_t frame = res->FrameOffset + Random().Next(0, res->FrameCount); float speedX = Random().FastFloat(-4.0f, 4.0f); Tiles::TileMap::DestructibleDebris debris = { }; debris.Pos = _pos; debris.Depth = _renderer.layer() - 2; debris.Size = Vector2f((float)size.X, (float)size.Y); debris.Speed = Vector2f(speedX, Random().FastFloat(-4.0f, -2.2f)); debris.Acceleration = Vector2f(0.0f, 0.2f); debris.Scale = Random().FastFloat(0.1f, 0.5f); debris.ScaleSpeed = -0.002f; debris.Angle = Random().FastFloat(0.0f, fTwoPi); debris.AngleSpeed = speedX * 0.04f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.018f; debris.Time = 160.0f; debris.TexScaleX = (size.X / float(texSize.X)); debris.TexBiasX = ((float)(frame % frameConf.X) / frameConf.X); debris.TexScaleY = (size.Y / float(texSize.Y)); debris.TexBiasY = ((float)(frame / frameConf.X) / frameConf.Y); debris.DiffuseTexture = res->Base->TextureDiffuse.get(); tilemap->CreateDebris(debris); } } } } else { _renderer.Initialize(ActorRendererType::Default); } } // Weapon Wheel Transitions switch (_weaponWheelState) { case WeaponWheelState::Opening: if (_renderer.GetRendererType() == ActorRendererType::Default) { _renderer.Initialize(ActorRendererType::Outline); } _weaponWheelState = WeaponWheelState::Visible; break; case WeaponWheelState::Closing: if (_renderer.GetRendererType() == ActorRendererType::Outline) { _renderer.Initialize(ActorRendererType::Default); } _weaponWheelState = WeaponWheelState::Hidden; break; } // Copter if (_activeModifier == Modifier::Copter || _activeModifier == Modifier::LizardCopter) { _copterFramesLeft -= timeMult; if (_copterFramesLeft <= 0.0f) { SetModifier(Modifier::None); } else if (_activeModifierDecor != nullptr) { _activeModifierDecor->MoveInstantly(_pos, MoveType::Absolute | MoveType::Force); } } #if defined(WITH_AUDIO) if (_copterSound != nullptr) { if ((_currentAnimation->State & AnimState::Copter) == AnimState::Copter) { _copterSound->setPosition(Vector3f(_pos.X, _pos.Y, 0.8f)); } else { _copterSound->stop(); _copterSound = nullptr; } } #endif // Trail if (PreferencesCache::ShowPlayerTrails) { for (std::int32_t i = 0; i < _trail.size(); i++) { auto& part = _trail[i]; part.Intensity -= timeMult * 0.04f; part.Brightness -= timeMult * 0.04f; part.RadiusFar -= timeMult * 0.4f; if (part.RadiusFar <= 0.0f) { _trail.eraseUnordered(i); i--; } } if ((_keepRunningTime > 0.0f || _speed.SqrLength() > (_isRunPressed ? 36.0f : 100.0f)) && _inTubeTime <= 0.0f) { constexpr float TrailDivision = 10.0f; Vector2f trailDelta = (_pos - _trailLastPos); std::int32_t trailDistance = (std::int32_t)(trailDelta.Length() / TrailDivision); if (trailDistance > 0) { trailDelta.Normalize(); while (trailDistance-- > 0) { _trailLastPos += trailDelta * TrailDivision; auto& light = _trail.emplace_back(); light.Pos = _trailLastPos; light.Intensity = 0.5f; light.Brightness = 0.8f; light.RadiusNear = 0.0f; light.RadiusFar = 28.0f; } } } else { _trailLastPos = _pos; } } // Tube if (_inTubeTime > 0.0f) { _inTubeTime -= timeMult; if (_inTubeTime <= 0.0f) { _controllable = true; SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset, true); } else { // Skip controls, player is not controllable in tube #if defined(WITH_AUDIO) // Weapons are automatically disabled if player is not controllable if (_weaponSound != nullptr) { _weaponSound->stop(); _weaponSound = nullptr; } #endif } } } void Player::OnHandleMovement(float timeMult, bool areaWeaponAllowed, bool canJumpPrev) { // Move if (PreferencesCache::ToggleRunAction) { if (_levelHandler->PlayerActionHit(this, PlayerAction::Run)) { _isRunPressed = !_isRunPressed; } } else { _isRunPressed = _levelHandler->PlayerActionPressed(this, PlayerAction::Run); } if (_health <= 0) { return; } if (_keepRunningTime <= 0.0f) { bool canWalk = (_controllable && _controllableExternal && !_isLifting && _suspendType != SuspendType::SwingingVine && (_playerType != PlayerType::Frog || !_levelHandler->PlayerActionPressed(this, PlayerAction::Fire))); float playerMovement = _levelHandler->PlayerHorizontalMovement(this); float playerMovementVelocity = std::abs(playerMovement); if (_currentSpecialMove == SpecialMoveType::Buttstomp) { _speed.X = 0.2f * playerMovement; if (_isRunPressed) { _speed.X *= 2.6f; } } else if (canWalk && playerMovementVelocity > 0.5f) { SetAnimation(_currentAnimation->State & ~(AnimState::Lookup | AnimState::Crouch)); if (_dizzyTime > 0.0f) { SetFacingLeft(playerMovement > 0.0f); } else { SetFacingLeft(playerMovement < 0.0f); } _isActivelyPushing = _wasActivelyPushing = true; float acceleration = (_levelHandler->IsReforged() ? Acceleration : Acceleration * 2.0f); if (_dizzyTime > 0.0f || _playerType == PlayerType::Frog) { _speed.X = std::clamp(_speed.X + acceleration * timeMult * (IsFacingLeft() ? -1 : 1), -MaxDizzySpeed * playerMovementVelocity, MaxDizzySpeed * playerMovementVelocity); } else if (_inShallowWater != -1 && _levelHandler->IsReforged() && _playerType != PlayerType::Lori) { // Use lower speed in shallow water if Reforged // Also, exclude Lori, because she can't ledge climb or double jump (rescue/01_colon1) _speed.X = std::clamp(_speed.X + acceleration * timeMult * (IsFacingLeft() ? -1 : 1), -MaxShallowWaterSpeed * playerMovementVelocity, MaxShallowWaterSpeed * playerMovementVelocity); } else { if (_suspendType == SuspendType::None && !_inWater && _isRunPressed) { _speed.X = std::clamp(_speed.X + acceleration * timeMult * (IsFacingLeft() ? -1 : 1), -MaxDashingSpeed * playerMovementVelocity, MaxDashingSpeed * playerMovementVelocity); } else if (_suspendType == SuspendType::Vine) { if (_wasFirePressed) { _speed.X = 0.0f; } else { // If Run is pressed, player moves faster on vines if (_isRunPressed) { playerMovementVelocity *= 1.6f; } _speed.X = std::clamp(_speed.X + acceleration * timeMult * (IsFacingLeft() ? -1 : 1), -MaxVineSpeed * playerMovementVelocity, MaxVineSpeed * playerMovementVelocity); } } else if (_suspendType != SuspendType::Hook) { _speed.X = std::clamp(_speed.X + acceleration * timeMult * (IsFacingLeft() ? -1 : 1), -MaxRunningSpeed * playerMovementVelocity, MaxRunningSpeed * playerMovementVelocity); } } if (CanJump()) { _wasUpPressed = _wasDownPressed = false; } } else if (_inTubeTime <= 0.0f) { _speed.X = std::max((std::abs(_speed.X) - Deceleration * timeMult), 0.0f) * (_speed.X < 0.0f ? -1.0f : 1.0f); _isActivelyPushing = false; float absSpeedX = std::abs(_speed.X); if (absSpeedX > 4.0f) { SetFacingLeft(_speed.X < 0.0f); } else if (absSpeedX < 0.001f) { _wasActivelyPushing = false; } } } else { _keepRunningTime -= timeMult; _isActivelyPushing = _wasActivelyPushing = true; float absSpeedX = std::abs(_speed.X); if (absSpeedX > 1.0f) { SetFacingLeft(_speed.X < 0.0f); } else if (absSpeedX < 1.0f) { _keepRunningTime = 0.0f; } } if (_hitFloorTime > 0.0f) { _hitFloorTime -= timeMult; } if (!_controllable || !_controllableExternal || _currentSpecialMove == SpecialMoveType::Buttstomp) { #if defined(WITH_AUDIO) // Weapons are automatically disabled if player is not controllable if (_currentWeapon != WeaponType::Thunderbolt || _fireFramesLeft <= 0.0f) { if (_weaponSound != nullptr) { _weaponSound->stop(); _weaponSound = nullptr; } } #endif return; } if (_inWater || _activeModifier != Modifier::None) { float playerMovement = _levelHandler->PlayerVerticalMovement(this); float playerMovementVelocity = std::abs(playerMovement); if (playerMovementVelocity > 0.3f) { float mult, max; switch (_activeModifier) { case Modifier::Airboard: mult = (playerMovement > 0 ? 1.0f : -0.2f); max = MaxRunningSpeed; break; case Modifier::LizardCopter: mult = (playerMovement > 0 ? 2.0f : -4.0f); max = (playerMovement > 0 ? MaxRunningSpeed : 2.0f * MaxRunningSpeed); break; default: mult = (playerMovement > 0 ? 1.0f : -1.0f); max = MaxRunningSpeed; break; } _speed.Y = std::clamp(_speed.Y + Acceleration * timeMult * mult, -max * playerMovementVelocity, max * playerMovementVelocity); } else { _speed.Y = std::max((std::abs(_speed.Y) - Deceleration * timeMult), 0.0f) * (_speed.Y < 0.0f ? -1.0f : 1.0f); } if (_activeModifier == Modifier::LizardCopter && _levelHandler->PlayerActionHit(this, PlayerAction::Jump)) { // Allow to jump off the copter // TODO: Copter shouldn't diappear immediately SetModifier(Modifier::None); // Don't trigger buttstomp or copter ears when pressing down _wasDownPressed = true; _wasJumpPressed = true; } } else { // Look-up if (_levelHandler->PlayerActionPressed(this, PlayerAction::Up)) { if (!_wasUpPressed && _dizzyTime <= 0.0f) { // Check also previous CanJump to avoid animation glitches on Springs if (((canJumpPrev && CanJump()) || (_suspendType != SuspendType::None && _suspendType != SuspendType::SwingingVine)) && !_isLifting && std::abs(_speed.X) < std::numeric_limits::epsilon()) { _wasUpPressed = true; SetAnimation(AnimState::Lookup | (_currentAnimation->State & AnimState::Hook)); } } } else if (_wasUpPressed) { _wasUpPressed = false; SetAnimation(_currentAnimation->State & ~AnimState::Lookup); } // Crouch / Buttstomp - Uses different bindings whether it's in the air or not if (_levelHandler->PlayerActionPressed(this, CanJump() ? PlayerAction::Down : PlayerAction::Buttstomp)) { if (_suspendType == SuspendType::SwingingVine) { // TODO: Swinging vine } else if (_suspendType != SuspendType::None) { // Jump off vine/hook if (IsContinuousJumpAllowed() || _levelHandler->PlayerActionHit(this, CanJump() ? PlayerAction::Down : PlayerAction::Buttstomp)) { _wasDownPressed = true; MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative | MoveType::Force); _suspendType = SuspendType::None; _suspendTime = 4.0f; SetState(ActorState::ApplyGravitation, true); } } else if (_dizzyTime <= 0.0f) { // Check also previous CanJump to avoid animation glitches on Springs if (canJumpPrev && CanJump()) { if (!_isLifting && std::abs(_speed.X) < std::numeric_limits::epsilon()) { _wasDownPressed = true; if (_fireFramesLeft > 0.0f) { SetAnimation(AnimState::Crouch | AnimState::Shoot); } else { SetAnimation(AnimState::Crouch); } } } else if (!CanJump() && !_wasDownPressed && _playerType != PlayerType::Frog) { _wasDownPressed = true; _speed.X = 0.0f; _speed.Y = 0.0f; _internalForceY = 0.0f; _externalForce.Y = 0.0f; SetState(ActorState::ApplyGravitation, false); SetAnimation(AnimState::Buttstomp); SetPlayerTransition(AnimState::TransitionButtstompStart, true, false, SpecialMoveType::Buttstomp, [this]() { _speed.Y = 9.0f; SetState(ActorState::ApplyGravitation, true); SetAnimation(AnimState::Buttstomp); PlaySfx("Buttstomp"_s, 1.0f, 0.8f); PlaySfx("Buttstomp2"_s); }); } } } else if (_wasDownPressed) { _wasDownPressed = false; SetAnimation(_currentAnimation->State & ~AnimState::Crouch); } // Jump if (_levelHandler->PlayerActionPressed(this, PlayerAction::Jump)) { if (!_wasJumpPressed) { _wasJumpPressed = true; if (_suspendType == SuspendType::None && _jumpTime <= 0.0f) { if (_isLifting && CanJump() && _currentSpecialMove == SpecialMoveType::None) { SetState(ActorState::CanJump, false); SetAnimation(_currentAnimation->State & ~(AnimState::Lookup | AnimState::Crouch)); PlayPlayerSfx("Jump"_s); _carryingObject = nullptr; SetState(ActorState::IsSolidObject | ActorState::CollideWithSolidObjects, false); _isLifting = false; _controllable = false; _jumpTime = 12.0f; _speed.Y = -3.0f; _internalForceY = -0.88f; SetTransition(AnimState::TransitionLiftEnd, false, [this]() { _controllable = true; SetState(ActorState::CollideWithSolidObjects, true); }); } else { switch (_playerType) { case PlayerType::Jazz: { if ((_currentAnimation->State & AnimState::Crouch) == AnimState::Crouch) { _controllable = false; SetAnimation(AnimState::Uppercut); SetPlayerTransition(AnimState::TransitionUppercutA, true, true, SpecialMoveType::Uppercut, [this]() { _externalForce.Y = (_levelHandler->IsReforged() ? -1.4f : -1.2f); _speed.Y = -2.0f; SetState(ActorState::CanJump, false); SetPlayerTransition(AnimState::TransitionUppercutB, true, true, SpecialMoveType::Uppercut); }); } else { if (_speed.Y > 0.01f && !CanJump() && (_currentAnimation->State & (AnimState::Fall | AnimState::Copter)) != AnimState::Idle) { SetState(ActorState::ApplyGravitation, false); _speed.Y = 1.5f; _externalForce.Y = 0.0f; if ((_currentAnimation->State & AnimState::Copter) != AnimState::Copter) { SetAnimation(AnimState::Copter); } _copterFramesLeft = 70.0f; #if defined(WITH_AUDIO) if (_copterSound == nullptr) { _copterSound = PlaySfx("Copter"_s, 0.6f, 1.5f); if (_copterSound != nullptr) { _copterSound->setLooping(true); } } #endif } } break; } case PlayerType::Spaz: { if ((_currentAnimation->State & AnimState::Crouch) == AnimState::Crouch) { _controllable = false; _controllableTimeout = 60.0f; SetAnimation(AnimState::Uppercut); SetPlayerTransition(AnimState::TransitionUppercutA, true, false, SpecialMoveType::Sidekick, [this]() { _externalForce.X = 8.0f * (IsFacingLeft() ? -1.0f : 1.0f); _speed.X = 14.4f * (IsFacingLeft() ? -1.0f : 1.0f); SetState(ActorState::ApplyGravitation, false); SetPlayerTransition(AnimState::TransitionUppercutB, true, false, SpecialMoveType::Sidekick); }); PlayPlayerSfx("Sidekick"_s); } else { if (!CanJump() && _canDoubleJump) { _canDoubleJump = false; _isFreefall = false; _internalForceY = (_levelHandler->IsReforged() ? -1.15f : -0.88f) - 0.1f * (1.0f - timeMult); _speed.Y = -0.6f - std::max(0.0f, (std::abs(_speed.X) - 4.0f) * 0.3f); _speed.X = std::clamp(_speed.X * 0.4f, -1.0f, 1.0f); PlayPlayerSfx("DoubleJump"_s); SetTransition(AnimState::Spring, false); } } break; } case PlayerType::Lori: { if ((_currentAnimation->State & AnimState::Crouch) == AnimState::Crouch) { _controllable = false; _controllableTimeout = 40.0f; SetAnimation(AnimState::Uppercut); SetPlayerTransition(AnimState::TransitionUppercutA, true, false, SpecialMoveType::Sidekick, [this]() { _externalForce.X = 4.0f * (IsFacingLeft() ? -1.0f : 1.0f); _speed.X = 9.3f * (IsFacingLeft() ? -1.0f : 1.0f); SetState(ActorState::ApplyGravitation, false); }); } else { if (_speed.Y > 0.01f && !CanJump() && (_currentAnimation->State & (AnimState::Fall | AnimState::Copter)) != AnimState::Idle) { SetState(ActorState::ApplyGravitation, false); _speed.Y = 1.5f; _externalForce.Y = 0.0f; if ((_currentAnimation->State & AnimState::Copter) != AnimState::Copter) { SetAnimation(AnimState::Copter); } _copterFramesLeft = 70.0f; #if defined(WITH_AUDIO) if (_copterSound == nullptr) { _copterSound = PlaySfx("Copter"_s, 0.6f, 1.5f); if (_copterSound != nullptr) { _copterSound->setLooping(true); } } #endif } } break; } } } } } if (_suspendType != SuspendType::None) { // Drop off hook/vine if (IsContinuousJumpAllowed() || _levelHandler->PlayerActionHit(this, PlayerAction::Jump)) { if (_suspendType == SuspendType::SwingingVine) { CancelCarryingObject(); _springCooldown = 30.0f; } else { MoveInstantly(Vector2(0.0f, -4.0f), MoveType::Relative | MoveType::Force); } SetState(ActorState::CanJump, true); _canDoubleJump = true; } } if (!CanJump()) { // Extend copter time if (_copterFramesLeft > 0.0f) { _copterFramesLeft = 70.0f; } } else if (_currentSpecialMove == SpecialMoveType::None && _jumpTime <= 0.0f && !_levelHandler->PlayerActionPressed(this, PlayerAction::Down)) { // Standard jump if (IsContinuousJumpAllowed() || _levelHandler->PlayerActionHit(this, PlayerAction::Jump)) { SetState(ActorState::CanJump, false); _isFreefall = false; SetAnimation(_currentAnimation->State & (~AnimState::Lookup & ~AnimState::Crouch)); PlayPlayerSfx("Jump"_s); _jumpTime = 10.0f; _carryingObject = nullptr; // Gravitation is sometimes off because of active copter, turn it on again SetState(ActorState::ApplyGravitation, true); SetState(ActorState::IsSolidObject, false); if (_levelHandler->IsReforged()) { _speed.Y = -3.6f - std::max(0.0f, (std::abs(_speed.X) - 4.0f) * 0.3f); _internalForceY = -1.02f - 0.07f * (1.0f - timeMult); if (_playerType == PlayerType::Lori) { _speed.Y *= 1.3f; } } else { _speed.Y = -8.0f - std::max(0.0f, (std::abs(_speed.X) - 4.0f) * 0.3f); } } } } else { if (_wasJumpPressed) { _wasJumpPressed = false; if (_levelHandler->IsReforged()) { if (_internalForceY < 0.0f) { _internalForceY = 0.0f; } } else { if (_speed.Y < -4.0f) { _speed.Y = -4.0f; } } } } } // Fire bool weaponInUse = false; if (_weaponAllowed && areaWeaponAllowed && _levelHandler->PlayerActionPressed(this, PlayerAction::Fire)) { if (!_isLifting && _suspendType != SuspendType::SwingingVine && !_canPushFurther) { if (_playerType == PlayerType::Frog) { if (_currentTransition == nullptr && std::abs(_speed.X) < 0.1f && std::abs(_speed.Y) < 0.1f && std::abs(_externalForce.X) < 0.1f && std::abs(_externalForce.Y) < 0.1f) { PlayPlayerSfx("Tongue"_s, 0.8f); _controllable = false; _controllableTimeout = 120.0f; SetTransition(_currentAnimation->State | AnimState::Shoot, false, [this]() { _controllable = true; _controllableTimeout = 0.0f; }); } } else if (_weaponAmmo[(std::int32_t)_currentWeapon] != 0) { _wasFirePressed = true; // Shooting has higher priority than pushing if object can't be moved further anymore _pushFramesLeft = 0.0f; bool weaponCooledDown = (_weaponCooldown <= 0.0f); weaponInUse = FireCurrentWeapon(_currentWeapon); if (weaponInUse) { if (_currentTransition != nullptr && (_currentTransition->State == AnimState::Spring || _currentTransition->State == AnimState::TransitionShootToIdle)) { ForceCancelTransition(); } SetAnimation(_currentAnimation->State | AnimState::Shoot); // Rewind the animation, if it should be played only once if (weaponCooledDown) { if (_currentAnimation->LoopMode == AnimationLoopMode::Once) { _renderer.AnimTime = 0.0f; } auto rumbleEffect = (_currentWeapon == WeaponType::Toaster || _currentWeapon == WeaponType::Thunderbolt ? "FireWeak"_s : "Fire"_s); _levelHandler->PlayerExecuteRumble(this, rumbleEffect); } _fireFramesLeft = 20.0f; } } } } else if (_wasFirePressed) { _wasFirePressed = false; _weaponCooldown = 0.0f; } #if defined(WITH_AUDIO) if (_weaponSound != nullptr) { if (weaponInUse) { _weaponSound->setPosition(Vector3f(_pos.X, _pos.Y, 0.8f)); } else { _weaponSound->stop(); _weaponSound = nullptr; if (_currentWeapon == WeaponType::Thunderbolt) { PlayPlayerSfx("WeaponThunderboltEnd"_s, 0.8f); _levelHandler->PlayerExecuteRumble(this, "Fire"_s); } } } #endif } bool Player::OnDraw(RenderQueue& renderQueue) { if (_weaponFlareTime > 0.0f && !_inWater && _currentTransition == nullptr) { auto* res = _metadata->FindAnimation(WeaponFlare); if (res != nullptr && res->Base->TextureDiffuse != nullptr) { auto& command = _weaponFlareCommand; if (command == nullptr) { command = std::make_unique(RenderCommand::Type::Sprite); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite)) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE); //command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } Vector2i texSize = res->Base->TextureDiffuse->GetSize(); std::int32_t curAnimFrame = res->FrameOffset + (_weaponFlareFrame % res->FrameCount); std::int32_t col = curAnimFrame % res->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / res->Base->FrameConfiguration.X; float texScaleX = (float(res->Base->FrameDimensions.X) / float(texSize.X)); float texBiasX = (float(res->Base->FrameDimensions.X * col) / float(texSize.X)); float texScaleY = (float(res->Base->FrameDimensions.Y) / float(texSize.Y)); float texBiasY = (float(res->Base->FrameDimensions.Y * row) / float(texSize.Y)); float scaleY = std::max(_weaponFlareTime / 8.0f, 0.4f); switch (_playerType) { case PlayerType::Jazz: scaleY *= 1.1f; break; case PlayerType::Spaz: scaleY *= 1.6f; break; case PlayerType::Lori: scaleY *= 1.3f; break; } bool facingLeft = IsFacingLeft(); bool lookUp = (_currentAnimation->State & AnimState::Lookup) == AnimState::Lookup; std::int32_t gunspotOffsetX = (_currentAnimation->Base->Hotspot.X - _currentAnimation->Base->Gunspot.X); std::int32_t gunspotOffsetY = (_currentAnimation->Base->Hotspot.Y - _currentAnimation->Base->Gunspot.Y); float gunspotPosX, gunspotPosY; if (lookUp) { gunspotPosX = _pos.X + (gunspotOffsetX) * (facingLeft ? 1 : -1); gunspotPosY = _pos.Y - (gunspotOffsetY - 3) - res->Base->FrameDimensions.Y; } else { gunspotPosX = _pos.X + (gunspotOffsetX - 7) * (facingLeft ? 1 : -1); gunspotPosY = _pos.Y - gunspotOffsetY; if (facingLeft) { texBiasX += texScaleX; texScaleX *= -1.0f; } } if (!PreferencesCache::UnalignedViewport) { gunspotPosX = std::floor(gunspotPosX); gunspotPosY = std::floor(gunspotPosY); } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(res->Base->FrameDimensions.X, res->Base->FrameDimensions.Y * scaleY); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(1.0f, 1.0f, 1.0f, 1.8f); Matrix4x4f worldMatrix = Matrix4x4f::Translation(gunspotPosX, gunspotPosY, 0.0f); if (lookUp) { worldMatrix.RotateZ(-fRadAngle90); } worldMatrix.Translate(res->Base->FrameDimensions.X * -0.5f, res->Base->FrameDimensions.Y * scaleY * -0.5f, 0.0f); command->SetTransformation(worldMatrix); command->SetLayer(_renderer.layer() + 2); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command.get()); } } switch (_activeShield) { case ShieldType::Fire: { auto* res = _metadata->FindAnimation(ShieldFire); if (res != nullptr && res->Base->TextureDiffuse != nullptr) { constexpr float PosMultiplier = 0.003f; float frames = _levelHandler->GetElapsedFrames(); float shieldAlpha = std::min(_activeShieldTime * 0.01f, 1.0f); float shieldScale = std::min(_activeShieldTime * 0.016f + 0.6f, 1.0f); float shieldSize = 70.0f * shieldScale; float shieldPosX = _pos.X - shieldSize * 0.5f; float shieldPosY = _pos.Y - shieldSize * 0.5f; if (!PreferencesCache::UnalignedViewport) { shieldPosX = std::floor(shieldPosX); shieldPosY = std::floor(shieldPosY); } { auto& command = _shieldRenderCommands[0]; if (command == nullptr) { command = std::make_unique(RenderCommand::Type::Sprite); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::ShieldFire))) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue( frames * -0.008f + _pos.X * PosMultiplier, frames * 0.006f - sinf(frames * 0.006f), -sinf(frames * 0.015f), frames * 0.006f + _pos.Y * PosMultiplier); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(shieldSize, shieldSize); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(2.0f, 2.0f, 0.8f, 0.9f * shieldAlpha); command->SetTransformation(Matrix4x4f::Translation(shieldPosX, shieldPosY, 0.0f)); command->SetLayer(_renderer.layer() - 4); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command.get()); } { auto& command = _shieldRenderCommands[1]; if (command == nullptr) { command = std::make_unique(RenderCommand::Type::Sprite); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::ShieldFire))) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue( frames * 0.006f, sinf(frames * 0.006f) + _pos.Y * PosMultiplier, sinf(frames * 0.015f) + _pos.X * PosMultiplier, frames * -0.006f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(shieldSize, shieldSize); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(2.0f, 2.0f, 1.0f, 1.0f * shieldAlpha); command->SetTransformation(Matrix4x4f::Translation(shieldPosX, shieldPosY, 0.0f)); command->SetLayer(_renderer.layer() + 4); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command.get()); } } break; } case ShieldType::Water: { auto* res = _metadata->FindAnimation(ShieldWater); if (res != nullptr && res->Base->TextureDiffuse != nullptr) { float frames = _levelHandler->GetElapsedFrames(); float shieldAlpha = std::min(_activeShieldTime * 0.01f, 1.0f); float shieldScale = std::min(_activeShieldTime * 0.016f + 0.6f, 1.0f); auto& command = _shieldRenderCommands[1]; if (command == nullptr) { command = std::make_unique(RenderCommand::Type::Sprite); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite)) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } Vector2i texSize = res->Base->TextureDiffuse->GetSize(); std::int32_t curAnimFrame = res->FrameOffset + ((std::int32_t)(frames * 0.24f) % res->FrameCount); std::int32_t col = curAnimFrame % res->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / res->Base->FrameConfiguration.X; float texScaleX = (float(res->Base->FrameDimensions.X) / float(texSize.X)); float texBiasX = (float(res->Base->FrameDimensions.X * col) / float(texSize.X)); float texScaleY = (float(res->Base->FrameDimensions.Y) / float(texSize.Y)); float texBiasY = (float(res->Base->FrameDimensions.Y * row) / float(texSize.Y)); float shieldPosX = _pos.X - res->Base->FrameDimensions.X * shieldScale * 0.5f; float shieldPosY = _pos.Y - res->Base->FrameDimensions.Y * shieldScale * 0.5f; if (!PreferencesCache::UnalignedViewport) { shieldPosX = std::floor(shieldPosX); shieldPosY = std::floor(shieldPosY); } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(res->Base->FrameDimensions.X * shieldScale, res->Base->FrameDimensions.Y * shieldScale); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(1.0f, 1.0f, 1.0f, shieldAlpha); command->SetTransformation(Matrix4x4f::Translation(shieldPosX, shieldPosY, 0.0f)); command->SetLayer(_renderer.layer() + 4); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command.get()); } break; } case ShieldType::Lightning: { auto* res = _metadata->FindAnimation(ShieldLightning); if (res != nullptr && res->Base->TextureDiffuse != nullptr) { constexpr float PosMultiplier = 0.001f; float frames = _levelHandler->GetElapsedFrames(); float shieldAlpha = std::min(_activeShieldTime * 0.01f, 1.0f); float shieldScale = std::min(_activeShieldTime * 0.016f + 0.6f, 1.0f); float shieldSize = 70.0f * shieldScale + sinf(frames * 0.06f) * 4.0f; float shieldPosX = _pos.X - shieldSize * 0.5f; float shieldPosY = _pos.Y - shieldSize * 0.5f; if (!PreferencesCache::UnalignedViewport) { shieldPosX = std::floor(shieldPosX); shieldPosY = std::floor(shieldPosY); } { auto& command = _shieldRenderCommands[0]; if (command == nullptr) { command = std::make_unique(RenderCommand::Type::Sprite); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::ShieldLightning))) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue( frames * -0.008f + _pos.X * PosMultiplier, frames * 0.006f - sinf(frames * 0.006f) + _pos.Y * PosMultiplier, -sinf(frames * 0.015f), frames * 0.006f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(shieldSize, shieldSize); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(2.0f, 2.0f, 0.8f, 0.9f * shieldAlpha); command->SetTransformation(Matrix4x4f::Translation(shieldPosX, shieldPosY, 0.0f)); command->SetLayer(_renderer.layer() - 4); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command.get()); } { auto& command = _shieldRenderCommands[1]; if (command == nullptr) { command = std::make_unique(RenderCommand::Type::Sprite); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::ShieldLightning))) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } auto* instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue( frames * 0.006f + _pos.X * PosMultiplier, sinf(frames * 0.006f) + _pos.Y * PosMultiplier, sinf(frames * 0.015f), frames * -0.006f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(shieldSize, shieldSize); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(2.0f, 2.0f, 1.0f, shieldAlpha); command->SetTransformation(Matrix4x4f::Translation(shieldPosX, shieldPosY, 0.0f)); command->SetLayer(_renderer.layer() + 4); command->GetMaterial().SetTexture(*res->Base->TextureDiffuse.get()); renderQueue.AddCommand(command.get()); } } break; } } return ActorBase::OnDraw(renderQueue); } void Player::OnEmitLights(SmallVectorImpl& lights) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 1.0f; if (_sugarRushLeft > 0.0f) { light.Brightness = 0.4f; light.RadiusNear = 60.0f; light.RadiusFar = 180.0f; } else { light.RadiusNear = 40.0f; light.RadiusFar = 110.0f; } for (std::int32_t i = 0; i < (std::int32_t)_trail.size(); i++) { lights.emplace_back(_trail[i]); } } bool Player::OnPerish(ActorBase* collider) { if (_currentTransition != nullptr && _currentTransition->State == AnimState::TransitionDeath) { return false; } SetState(ActorState::IsInvulnerable, true); ForceCancelTransition(); if (_playerType == PlayerType::Frog) { _playerType = _playerTypeOriginal; // Load original metadata switch (_playerType) { case PlayerType::Jazz: RequestMetadata("Interactive/PlayerJazz"_s); break; case PlayerType::Spaz: RequestMetadata("Interactive/PlayerSpaz"_s); break; case PlayerType::Lori: RequestMetadata("Interactive/PlayerLori"_s); break; case PlayerType::Frog: RequestMetadata("Interactive/PlayerFrog"_s); break; } // Refresh animation state AnimState prevState = _currentAnimation->State; _currentSpecialMove = SpecialMoveType::None; _currentAnimation = nullptr; SetAnimation(prevState); // Morph to original type with animation and then trigger death SetPlayerTransition(AnimState::TransitionFromFrog, false, true, SpecialMoveType::None, [this]() { OnPerishInner(); }); } else { OnPerishInner(); } return false; } void Player::OnUpdateHitbox() { // The sprite is always located relative to the hotspot. // The coldspot is usually located at the ground level of the sprite, // but for falling sprites for some reason somewhere above the hotspot instead. // It is absolutely important that the position of the hitbox stays constant // to the hotspot, though; otherwise getting stuck at walls happens all the time. if (_levelHandler->IsReforged()) { AABBInner = AABBf(_pos.X - 11.0f, _pos.Y + 8.0f - 18.0f, _pos.X + 11.0f, _pos.Y + 8.0f + 12.0f); } else { AABBInner = AABBf(_pos.X - 10.0f, _pos.Y + 8.0f - 16.0f, _pos.X + 10.0f, _pos.Y + 8.0f + 12.0f); } } bool Player::OnHandleCollision(std::shared_ptr other) { ZoneScoped; bool handled = false; bool removeSpecialMove = false; if (auto* turtleShell = runtime_cast(other.get())) { if (_currentSpecialMove == SpecialMoveType::Buttstomp && _currentTransition != nullptr && _sugarRushLeft <= 0.0f) { // Buttstomp is probably in starting transition, do nothing yet unless sugar rush is active } else if (_currentSpecialMove != SpecialMoveType::None || _sugarRushLeft > 0.0f) { other->DecreaseHealth(INT32_MAX, this); handled = true; if ((_currentAnimation->State & AnimState::Buttstomp) == AnimState::Buttstomp) { removeSpecialMove = true; _speed.Y *= -0.6f; SetState(ActorState::CanJump, false); } } } else if (auto* enemy = runtime_cast(other.get())) { if (_currentSpecialMove == SpecialMoveType::Buttstomp && _currentTransition != nullptr && _sugarRushLeft <= 0.0f) { // Buttstomp is probably in starting transition, do nothing yet unless sugar rush or shield is active } else if (_currentSpecialMove != SpecialMoveType::None || _sugarRushLeft > 0.0f || (enemy->IsFrozen() && _speed.Length() >= 9.0f)) { if (!enemy->IsInvulnerable()) { enemy->DecreaseHealth(4, this); handled = true; Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::Small); if (_sugarRushLeft > 0.0f) { if (!_inWater && CanJump()) { _speed.Y = 3; SetState(ActorState::CanJump, false); _externalForce.Y = -0.6f; } _speed.Y *= -0.5f; } if ((_currentAnimation->State & AnimState::Buttstomp) == AnimState::Buttstomp) { removeSpecialMove = true; _speed.Y *= -0.6f; SetState(ActorState::CanJump, false); SetInvulnerability(FrameTimer::FramesPerSecond, InvulnerableType::Transient); } else if (_currentSpecialMove != SpecialMoveType::None && enemy->GetHealth() > 0) { removeSpecialMove = true; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; if (_currentSpecialMove == SpecialMoveType::Sidekick) { _speed.X *= 0.5f; } } if (_currentSpecialMove == SpecialMoveType::None && _sugarRushLeft <= 0.0f && enemy->IsFrozen()) { _speed = -_speed; if (_speed.Y > -4.0f) { _speed.Y = -4.0f; } SetState(ActorState::CanJump, false); } _levelHandler->PlayerExecuteRumble(this, "Land"_s); } } else if (enemy->CanHurtPlayer()) { if (!IsInvulnerable()) { if (_activeShieldTime > 0.0f) { // Decrease remaining shield time by 5 secs if (_activeShieldTime > (5.0f * FrameTimer::FramesPerSecond)) { _activeShieldTime -= (5.0f * FrameTimer::FramesPerSecond); } float invulnerableTime = _levelHandler->GetHurtInvulnerableTime(); SetInvulnerability(invulnerableTime, InvulnerableType::Blinking); PlayPlayerSfx("HurtSoft"_s); } else { TakeDamage(1, 4 * (_pos.X > enemy->GetPos().X ? 1.0f : -1.0f)); } } } } else if (auto* spring = runtime_cast(other.get())) { // Collide only with hitbox here if (_controllableExternal && (_currentTransition == nullptr || _currentTransition->State != AnimState::TransitionLedgeClimb) && _springCooldown <= 0.0f && spring->AABBInner.Overlaps(AABBInner)) { Vector2f force = spring->Activate(); OnHitSpring(spring->GetPos(), force, spring->KeepSpeedX, spring->KeepSpeedY, removeSpecialMove); } handled = true; } else if (auto* bonusWarp = runtime_cast(other.get())) { if (_currentTransition == nullptr || _currentTransitionCancellable) { auto cost = bonusWarp->GetCost(); if (cost <= _coins) { _coins -= cost; bonusWarp->Activate(this); // Convert remaing coins to gems and equivalent score _gems[0] += _coins; AddScore(_coins * 100); _coins = 0; } else if (_bonusWarpTimer <= 0.0f) { _levelHandler->HandlePlayerCoins(this, _coins, _coins); PlaySfx("BonusWarpNotEnoughCoins"_s); _bonusWarpTimer = 400.0f; } } handled = true; } else if (auto* otherPlayer = runtime_cast(other.get())) { if (_levelHandler->CanPlayersCollide() && (_currentTransition == nullptr || (_currentTransition->State != AnimState::TransitionWarpIn && _currentTransition->State != AnimState::TransitionWarpInFreefall && _currentTransition->State != AnimState::TransitionWarpOut && _currentTransition->State != AnimState::TransitionWarpOutFreefall))) { Vector2f diff = (_pos - otherPlayer->GetPos()); if (diff.SqrLength() < 24.0f * 24.0f) { _speed = diff.Normalize() * 4.0f - (_isActivelyPushing ? (_speed * 0.2f) : Vector2f::Zero); if (diff.Y < 0.0f && std::abs(_speed.X) < 2.0f) { _speed.X = (_speed.X < 0.0f ? -2.0f : 2.0f); } } } } if (removeSpecialMove) { _controllable = true; EndDamagingMove(); } return handled; } void Player::OnHitFloor(float timeMult) { if (_activeModifier == Modifier::None && (_currentAnimation->State & AnimState::Copter) == AnimState::Copter) { _copterFramesLeft = 0.0f; SetAnimation(_currentAnimation->State & ~AnimState::Copter); if (!_isAttachedToPole) { SetState(ActorState::ApplyGravitation, true); } } if (_levelHandler->EventMap()->IsHurting(_pos.X, _pos.Y + 24.0f, Direction::Up)) { if (!IsInvulnerable() && _sugarRushLeft <= 0.0f) { if (_activeShieldTime > 0.0f) { // Decrease remaining shield time by 5 secs if (_activeShieldTime > (5.0f * FrameTimer::FramesPerSecond)) { _activeShieldTime -= (5.0f * FrameTimer::FramesPerSecond); } float invulnerableTime = _levelHandler->GetHurtInvulnerableTime(); SetInvulnerability(invulnerableTime, InvulnerableType::Blinking); PlayPlayerSfx("HurtSoft"_s); } else { TakeDamage(1, _speed.X * 0.25f); } } } else if (!_inWater && _activeModifier == Modifier::None) { if (_hitFloorTime <= 0.0f && !CanJump()) { _hitFloorTime = 30.0f; PlaySfx("Land"_s, 0.8f); if (PreferencesCache::GamepadRumble >= 2) { // "Land" effect is enabled only for Strong preset _levelHandler->PlayerExecuteRumble(this, "Land"_s); } if (Random().NextFloat() < 0.6f) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y + 20, _renderer.layer() - 2), Explosion::Type::TinyDark); } } } else { // Prevent stucking with water/airboard SetState(ActorState::CanJump, false); if (_speed.Y > 0.0f) { _speed.Y = 0.0f; } } _canDoubleJump = true; _isFreefall = false; SetState(ActorState::IsSolidObject, true); } void Player::OnHitCeiling(float timeMult) { if (_levelHandler->EventMap()->IsHurting(_pos.X, _pos.Y - 4.0f, Direction::Down)) { if (!IsInvulnerable() && _sugarRushLeft <= 0.0f) { if (_activeShieldTime > 0.0f) { // Decrease remaining shield time by 5 secs if (_activeShieldTime > (5.0f * FrameTimer::FramesPerSecond)) { _activeShieldTime -= (5.0f * FrameTimer::FramesPerSecond); } float invulnerableTime = _levelHandler->GetHurtInvulnerableTime(); SetInvulnerability(invulnerableTime, InvulnerableType::Blinking); PlayPlayerSfx("HurtSoft"_s); } else { TakeDamage(1, _speed.X * 0.25f); } } } } void Player::OnHitWall(float timeMult) { // Reset speed and show Push animation _speed.X = 0.0f; _pushFramesLeft = 2.0f; _keepRunningTime = 0.0f; if (_levelHandler->EventMap()->IsHurting(_pos.X + (_speed.X > 0.0f ? 16.0f : -16.0f), _pos.Y, (_speed.X > 0.0f ? Direction::Left : Direction::Right))) { if (!IsInvulnerable() && _sugarRushLeft <= 0.0f) { if (_activeShieldTime > 0.0f) { // Decrease remaining shield time by 5 secs if (_activeShieldTime > (5.0f * FrameTimer::FramesPerSecond)) { _activeShieldTime -= (5.0f * FrameTimer::FramesPerSecond); } float invulnerableTime = _levelHandler->GetHurtInvulnerableTime(); SetInvulnerability(invulnerableTime, InvulnerableType::Blinking); PlayPlayerSfx("HurtSoft"_s); } else { TakeDamage(1, _speed.X * 0.25f); } } } else { if (IsLedgeClimbAllowed() && _isActivelyPushing && _suspendType == SuspendType::None && _activeModifier == Modifier::None && !CanJump() && !_inWater && _currentSpecialMove == SpecialMoveType::None && (_currentTransition == nullptr || _currentTransition->State != AnimState::TransitionUppercutEnd) && _speed.Y >= -1.0f && _externalForce.Y >= 0.0f && _copterFramesLeft <= 0.0f && _keepRunningTime <= 0.0f && _fireFramesLeft <= 0.0f && _dizzyTime <= 0.0f) { // Check if the character supports ledge climbing if (_metadata->FindAnimation(AnimState::TransitionLedgeClimb)) { constexpr std::int32_t MaxTolerancePixels = 6; SetState(ActorState::CollideWithTilesetReduced, false); float x = (IsFacingLeft() ? -8.0f : 8.0f); AABBf hitbox1 = AABBInner + Vector2f(x, -42.0f - MaxTolerancePixels); // Empty space to climb to AABBf hitbox2 = AABBInner + Vector2f(x, -42.0f + 2.0f); // Wall below the empty space AABBf hitbox3 = AABBInner + Vector2f(x, -42.0f + 2.0f + 24.0f); // Wall between the player and the wall above (vertically) AABBf hitbox4 = AABBInner + Vector2f(x, 20.0f); // Wall below the player AABBf hitbox5 = AABBf(AABBInner.L + 2, hitbox1.T, AABBInner.R - 2, AABBInner.B); // Player can't climb through walls TileCollisionParams params = { TileDestructType::None, false }; if (_levelHandler->IsPositionEmpty(this, hitbox1, params) && !_levelHandler->IsPositionEmpty(this, hitbox2, params) && !_levelHandler->IsPositionEmpty(this, hitbox3, params) && !_levelHandler->IsPositionEmpty(this, hitbox4, params) && _levelHandler->IsPositionEmpty(this, hitbox5, params)) { uint8_t* wallParams; if (_levelHandler->EventMap()->GetEventByPosition(IsFacingLeft() ? hitbox2.L : hitbox2.R, hitbox2.B, &wallParams) != EventType::ModifierNoClimb) { // Move the player upwards, if it is in tolerance, so the animation will look better AABBf aabb = AABBInner + Vector2f(x, -42.0f); for (std::int32_t y = 0; y >= -MaxTolerancePixels; y -= 1) { if (_levelHandler->IsPositionEmpty(this, aabb, params)) { MoveInstantly(Vector2f(0.0f, (float)y), MoveType::Relative | MoveType::Force, params); break; } aabb.T -= 1.0f; aabb.B -= 1.0f; } // Prepare the player for animation _controllable = false; SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects, false); _speed.X = 0.0f; _speed.Y = -1.4f; if (timeMult < 1.0f) { _speed.Y += (1.0f - timeMult) * 0.4f; } _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _pushFramesLeft = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; // Stick the player to wall MoveInstantly(Vector2f(IsFacingLeft() ? -6.0f : 6.0f, 0.0f), MoveType::Relative | MoveType::Force, params); SetAnimation(AnimState::Idle); SetTransition(AnimState::TransitionLedgeClimb, false, [this]() { // Reset the player to normal state _controllable = true; SetState(ActorState::CanJump | ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects, true); _pushFramesLeft = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; _hitFloorTime = 60.0f; _speed.Y = 0.0f; // Move it far from the ledge TileCollisionParams params = { TileDestructType::None, false }; MoveInstantly(Vector2f(IsFacingLeft() ? -4.0f : 4.0f, 0.0f), MoveType::Relative, params); // Move the player upwards, so it will not be stuck in the wall for (float y = -1.0f; y > -24.0f; y -= 1.0f) { if (MoveInstantly(Vector2f(0.0f, y), MoveType::Relative, params)) { break; } } }); } } SetState(ActorState::CollideWithTilesetReduced, true); } } } } void Player::OnHitSpring(Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY, bool& removeSpecialMove) { std::int32_t sign = ((force.X + force.Y) > std::numeric_limits::epsilon() ? 1 : -1); if (std::abs(force.X) > 0.0f) { MoveInstantly(Vector2f(_pos.X, (_pos.Y + pos.Y) * 0.5f), MoveType::Absolute); _copterFramesLeft = 0.0f; //speedX = force.X; _speed.X = (1.0f + std::abs(force.X)) * sign; _externalForce.X = force.X * 0.6f; _springCooldown = 10.0f; SetState(ActorState::CanJump, false); _wasActivelyPushing = false; _keepRunningTime = 100.0f; if (!keepSpeedY) { _speed.Y = 0.0f; _externalForce.Y = 0.0f; } if (_currentSpecialMove != SpecialMoveType::Sidekick) { if (_inIdleTransition) { _inIdleTransition = false; CancelTransition(); } removeSpecialMove = true; _controllableTimeout = 2.0f; if (_activeModifier == Modifier::None) { SetAnimation(_currentAnimation->State & ~(AnimState::Crouch | AnimState::Lookup | AnimState::Buttstomp)); SetPlayerTransition(AnimState::Dash | AnimState::Jump, true, false, SpecialMoveType::None); } } _levelHandler->PlayerExecuteRumble(this, "Spring"_s); } else if (std::abs(force.Y) > 0.0f) { MoveInstantly(Vector2f(lerp(_pos.X, pos.X, 0.3f), _pos.Y), MoveType::Absolute); if (_activeModifier == Modifier::None && _copterFramesLeft > 0.0f) { _copterFramesLeft = 0.0f; SetAnimation(_currentAnimation->State & ~AnimState::Copter); SetState(ActorState::ApplyGravitation, true); } _speed.Y = (4.0f + std::abs(force.Y)) * sign; if (!GetState(ActorState::ApplyGravitation)) { _externalForce.Y = force.Y * 0.14f; } else if (_levelHandler->IsReforged()) { _externalForce.Y = force.Y; } else { _externalForce.Y = force.Y * 0.8f; } _springCooldown = 10.0f; SetState(ActorState::CanJump, false); if (!keepSpeedX) { _speed.X = 0.0f; _externalForce.X = 0.0f; _keepRunningTime = 0.0f; } if (_inIdleTransition) { _inIdleTransition = false; CancelTransition(); } if (sign > 0) { removeSpecialMove = false; if (_activeModifier == Modifier::None) { _currentSpecialMove = SpecialMoveType::Buttstomp; SetAnimation(AnimState::Buttstomp); } } else { removeSpecialMove = true; _isSpring = true; if (_activeModifier == Modifier::None) { SetAnimation(_currentAnimation->State & ~(AnimState::Crouch | AnimState::Lookup)); } } _levelHandler->PlayerExecuteRumble(this, "Spring"_s); PlayPlayerSfx("Spring"_s); } } void Player::OnWaterSplash(Vector2f pos, bool inwards) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)pos.X, (std::int32_t)pos.Y, _renderer.layer() + 2), Explosion::Type::WaterSplash); _levelHandler->PlayCommonSfx("WaterSplash"_s, Vector3f(pos.X, pos.Y, 0.0f), inwards ? 0.7f : 1.0f, 0.5f); } void Player::UpdateAnimation(float timeMult) { if (!_controllable) { return; } AnimState oldState = _currentAnimation->State; AnimState newState; if (_inWater) { newState = AnimState::Swim; } else if (_activeModifier == Modifier::Airboard) { newState = AnimState::Airboard; } else if (_activeModifier == Modifier::Copter) { newState = AnimState::Copter; } else if (_activeModifier == Modifier::LizardCopter) { newState = AnimState::Hook; } else if (_suspendType == SuspendType::SwingingVine) { newState = AnimState::Swing; } else if (_isLifting) { newState = AnimState::Lift; } else if (CanJump() && _isActivelyPushing && _pushFramesLeft > 0.0f && _keepRunningTime <= 0.0f && _fireFramesLeft <= 0.0f) { newState = AnimState::Push; if (_inIdleTransition) { _inIdleTransition = false; CancelTransition(); } } else { // Only certain ones don't need to be preserved from earlier state, others should be set as expected AnimState composite = (_currentAnimation->State & CompositeAnimMask); if (_isActivelyPushing == _wasActivelyPushing) { float absSpeedX = std::abs(_speed.X); if (absSpeedX > MaxRunningSpeed) { composite |= AnimState::Dash; } else if (_keepRunningTime > 0.0f) { composite |= AnimState::Run; } else if (absSpeedX > (_fireFramesLeft > 0.0f ? 1.0f : 0.0f)) { // Shooting needs higher threshold to fix pushing into a wall composite |= AnimState::Walk; } if (_inIdleTransition) { _inIdleTransition = false; CancelTransition(); } } if (_fireFramesLeft > 0.0f) { composite |= AnimState::Shoot; } if (_suspendType != SuspendType::None) { composite |= AnimState::Hook; } else { if (CanJump()) { // Grounded, no vertical speed if (_dizzyTime > 0.0f) { composite |= AnimState::Dizzy; } } else if (_speed.Y < 0.0f) { // Jumping, ver. speed is negative if (_isSpring) { composite |= AnimState::Spring; } else { composite |= AnimState::Jump; } } else if (_isFreefall) { // Free falling, ver. speed is positive composite |= AnimState::Freefall; _isSpring = false; } else { // Falling, ver. speed is positive composite |= AnimState::Fall; _isSpring = false; } } newState = composite; } if (newState == AnimState::Idle) { if (_idleTime > 600.0f) { _idleTime = 0.0f; if (_currentTransition == nullptr) { constexpr StringView IdleBored[] = { "IdleBored1"_s, "IdleBored2"_s, "IdleBored3"_s, "IdleBored4"_s, "IdleBored5"_s }; std::int32_t maxIdx; switch (_playerType) { case PlayerType::Jazz: maxIdx = 5; break; case PlayerType::Spaz: maxIdx = 4; break; case PlayerType::Lori: maxIdx = 3; break; default: maxIdx = 0; break; } if (maxIdx > 0) { std::int32_t selectedIdx = Random().Fast(0, maxIdx); if (SetTransition((AnimState)(536870944 + selectedIdx), true)) { PlayPlayerSfx(IdleBored[selectedIdx]); } } } } else { _idleTime += timeMult; } } else { _idleTime = 0.0f; } SetAnimation(newState); if (!_isAttachedToPole) { switch (oldState) { case AnimState::Walk: if (newState == AnimState::Idle) { _inIdleTransition = true; SetTransition(AnimState::TransitionRunToIdle, true, [this]() { _inIdleTransition = false; }); } else if (newState == AnimState::Dash) { SetTransition(AnimState::TransitionRunToDash, true); } break; case AnimState::Dash: if (newState == AnimState::Idle) { _inIdleTransition = true; SetTransition(AnimState::TransitionDashToIdle, true, [this]() { if (_inIdleTransition) { SetTransition(AnimState::TransitionRunToIdle, true, [this]() { _inIdleTransition = false; }); } }); } break; case AnimState::Fall: case AnimState::Freefall: if (newState == AnimState::Idle) { SetTransition(AnimState::TransitionFallToIdle, true); } break; case AnimState::Idle: if (newState == AnimState::Jump) { SetTransition(AnimState::TransitionIdleToJump, true); } else if (newState != AnimState::Idle) { _inLedgeTransition = false; if (_currentTransition != nullptr && _currentTransition->State == AnimState::TransitionLedge) { CancelTransition(); } } else if (!_inLedgeTransition && _carryingObject == nullptr && std::abs(_speed.X) < 1.0f && std::abs(_speed.Y) < 1.0f) { AABBf aabbL = AABBf(AABBInner.L + 2, AABBInner.B - 10, AABBInner.L + 4, AABBInner.B + 28); AABBf aabbR = AABBf(AABBInner.R - 4, AABBInner.B - 10, AABBInner.R - 2, AABBInner.B + 28); TileCollisionParams params = { TileDestructType::None, true }; if (IsFacingLeft() ? (_levelHandler->IsPositionEmpty(this, aabbL, params) && !_levelHandler->IsPositionEmpty(this, aabbR, params)) : (!_levelHandler->IsPositionEmpty(this, aabbL, params) && _levelHandler->IsPositionEmpty(this, aabbR, params))) { _inLedgeTransition = true; if (_playerType == PlayerType::Spaz) { // Spaz's and Lori's animation should be continual, so reset it in callback SetTransition(AnimState::TransitionLedge, true, [this]() { _inLedgeTransition = false; }); } else { SetTransition(AnimState::TransitionLedge, true); } PlayPlayerSfx("Ledge"_s); } } break; } } } void Player::PushSolidObjects(float timeMult) { if (_pushFramesLeft > 0.0f) { _pushFramesLeft -= timeMult; } else { _canPushFurther = false; } if (CanJump() && _controllable && _controllableExternal && _isActivelyPushing && std::abs(_speed.X) > 0.0f) { AABBf hitbox = AABBInner + Vector2f(_speed.X < 0.0f ? -2.0f : 2.0f, 0.0f); TileCollisionParams params = { TileDestructType::None, false }; ActorBase* collider; if (!_levelHandler->IsPositionEmpty(this, hitbox, params, &collider)) { if (auto* solidObject = runtime_cast(collider)) { SetState(ActorState::IsSolidObject, false); float pushSpeedX = solidObject->Push(_speed.X < 0, timeMult); if (std::abs(pushSpeedX) > 0.0f) { _speed.X = pushSpeedX * 1.2f * timeMult; _pushFramesLeft = 3.0f; _fireFramesLeft = 0.0f; _canPushFurther = true; } else { _canPushFurther = false; } SetState(ActorState::IsSolidObject, true); } } } else if (GetState(ActorState::IsSolidObject)) { AABBf aabb = AABBf(AABBInner.L, AABBInner.T - 20.0f, AABBInner.R, AABBInner.T + 6.0f); TileCollisionParams params = { TileDestructType::None, false }; ActorBase* collider; ActorState prevState = GetState(); SetState(ActorState::CollideWithTileset, false); if (!_levelHandler->IsPositionEmpty(this, aabb, params, &collider)) { if (auto* solidObject = runtime_cast(collider)) { if (AABBInner.T >= solidObject->AABBInner.T && !_isLifting && std::abs(_speed.Y) < 1.0f) { _isLifting = true; SetTransition(AnimState::TransitionLiftStart, true); } } else { _isLifting = false; } } else { _isLifting = false; } SetState(prevState); } else { _isLifting = false; } } void Player::CheckEndOfSpecialMoves(float timeMult) { // Buttstomp if (_currentSpecialMove == SpecialMoveType::Buttstomp && (CanJump() || _suspendType != SuspendType::None)) { EndDamagingMove(); if (_suspendType == SuspendType::None && !_isSpring) { std::int32_t tx = (std::int32_t)_pos.X / 32; std::int32_t ty = ((std::int32_t)_pos.Y + 24) / 32; std::uint8_t* eventParams; if (_levelHandler->EventMap()->GetEventByPosition(tx, ty, &eventParams) == EventType::GemStomp) { _levelHandler->EventMap()->StoreTileEvent(tx, ty, EventType::Empty); for (std::int32_t i = 0; i < 8; i++) { float fx = Random().NextFloat(-12.0f, 12.0f); float fy = Random().NextFloat(-2.0f, 0.2f); std::uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] = { 0, 0x01 | 0x04 }; std::shared_ptr actor = _levelHandler->EventSpawner()->SpawnEvent(EventType::Gem, spawnParams, ActorState::None, Vector3i((std::int32_t)(_pos.X + fx * 2.0f), (std::int32_t)(_pos.Y + fy * 4.0f), _renderer.layer() - 10)); if (actor != nullptr) { actor->AddExternalForce(fx, fy); _levelHandler->AddActor(actor); } } } if (_levelHandler->IsReforged()) { _controllable = false; SetTransition(AnimState::TransitionButtstompEnd, false, [this]() { _controllable = true; }); } else { SetTransition(AnimState::TransitionButtstompEnd, true); } } else { _controllable = true; } } // Uppercut if (_currentSpecialMove == SpecialMoveType::Uppercut && _currentTransition == nullptr && ((_currentAnimation->State & AnimState::Uppercut) == AnimState::Uppercut) && _speed.Y > -2) { EndDamagingMove(); } // Sidekick if (_currentSpecialMove == SpecialMoveType::Sidekick && _currentTransition == nullptr && (_controllable || std::abs(_speed.X) < 0.01f)) { EndDamagingMove(); _controllable = true; _controllableTimeout = 0.0f; if (_suspendType == SuspendType::None) { SetTransition(AnimState::TransitionUppercutEnd, false); } } // Copter Ears if (_activeModifier != Modifier::Copter && _activeModifier != Modifier::LizardCopter) { // TODO: Is this still needed? bool cancelCopter; if ((_currentAnimation->State & AnimState::Copter) == AnimState::Copter) { cancelCopter = (CanJump() || _suspendType != SuspendType::None || _copterFramesLeft <= 0.0f); _copterFramesLeft -= timeMult; _speed.Y = std::min(_speed.Y + _levelHandler->GetGravity() * timeMult, 1.5f); } else { cancelCopter = ((_currentAnimation->State & AnimState::Fall) == AnimState::Fall && _copterFramesLeft > 0.0f); } if (cancelCopter) { _copterFramesLeft = 0.0f; SetAnimation(_currentAnimation->State & ~AnimState::Copter); if (!_isAttachedToPole) { SetState(ActorState::ApplyGravitation, true); } } } } void Player::CheckSuspendState(float timeMult) { if (_suspendTime > 0.0f) { _suspendTime -= timeMult; return; } if (_suspendType == SuspendType::SwingingVine || _activeModifier != Modifier::None) { return; } auto tiles = _levelHandler->TileMap(); if (tiles == nullptr) { return; } AnimState currentState = _currentAnimation->State; SuspendType newSuspendState = tiles->GetTileSuspendState(_pos.X, _pos.Y - 1.0f); if (newSuspendState == _suspendType) { if (newSuspendState == SuspendType::None) { constexpr float ToleranceX = 8.0f; constexpr float ToleranceY = 4.0f; newSuspendState = tiles->GetTileSuspendState(_pos.X - ToleranceX, _pos.Y - 1.0f); if (newSuspendState != SuspendType::Hook) { newSuspendState = tiles->GetTileSuspendState(_pos.X + ToleranceX, _pos.Y - 1.0f); if (newSuspendState != SuspendType::Hook) { // Also try with Y tolerance newSuspendState = tiles->GetTileSuspendState(_pos.X, _pos.Y - 1.0f + ToleranceY); if (newSuspendState != SuspendType::Hook) { newSuspendState = tiles->GetTileSuspendState(_pos.X - ToleranceX, _pos.Y - 1.0f + ToleranceY); if (newSuspendState != SuspendType::Hook) { newSuspendState = tiles->GetTileSuspendState(_pos.X + ToleranceX, _pos.Y - 1.0f + ToleranceY); if (newSuspendState != SuspendType::Hook) { return; } else { MoveInstantly(Vector2f(ToleranceX, ToleranceY), MoveType::Relative | MoveType::Force); } } else { MoveInstantly(Vector2f(-ToleranceX, ToleranceY), MoveType::Relative | MoveType::Force); } } else { MoveInstantly(Vector2f(0.0f, ToleranceY), MoveType::Relative | MoveType::Force); } } else { MoveInstantly(Vector2f(ToleranceX, 0.0f), MoveType::Relative | MoveType::Force); } } else { MoveInstantly(Vector2f(-ToleranceX, 0.0f), MoveType::Relative | MoveType::Force); } } else { return; } } if (newSuspendState != SuspendType::None && _playerType != PlayerType::Frog && _frozenTimeLeft <= 0.0f) { if (_currentSpecialMove == SpecialMoveType::None) { _suspendType = newSuspendState; SetState(ActorState::ApplyGravitation, false); if (_speed.Y > 0.0f && newSuspendState == SuspendType::Vine) { PlayPlayerSfx("HookAttach"_s, 0.8f, 1.2f); } _speed.Y = 0.0f; _externalForce.Y = 0.0f; _isFreefall = false; _isSpring = false; _copterFramesLeft = 0.0f; if (newSuspendState == SuspendType::Hook || _wasFirePressed) { _speed.X = 0.0f; _externalForce.X = 0.0f; } // Move downwards until we're on the standard height while (tiles->GetTileSuspendState(_pos.X, _pos.Y - 1) != SuspendType::None) { MoveInstantly(Vector2f(0.0f, 1.0f), MoveType::Relative | MoveType::Force); } MoveInstantly(Vector2f(0.0f, -1.0f), MoveType::Relative | MoveType::Force); SetAnimation(AnimState::Hook); } } else { _suspendType = SuspendType::None; _suspendTime = 8.0f; if ((currentState & (AnimState::Buttstomp | AnimState::Copter)) == AnimState::Idle && !_isAttachedToPole) { SetState(ActorState::ApplyGravitation, true); } } } void Player::OnHandleWater() { if (_inWater) { if (_pos.Y >= _levelHandler->GetWaterLevel()) { SetState(ActorState::ApplyGravitation, false); if (std::abs(_speed.X) > 1.0f || std::abs(_speed.Y) > 1.0f) { float angle; if (_speed.X == 0.0f) { if (IsFacingLeft()) { angle = atan2(-_speed.Y, -std::numeric_limits::epsilon()); } else { angle = atan2(_speed.Y, std::numeric_limits::epsilon()); } } else if (_speed.X < 0.0f) { angle = atan2(-_speed.Y, -_speed.X); } else { angle = atan2(_speed.Y, _speed.X); } if (angle > fPi) { angle = angle - fTwoPi; } _renderer.setRotation(std::clamp(angle, -fPiOver3, fPiOver3)); } // Adjust swimming animation speed if (_currentTransition == nullptr) { _renderer.AnimDuration = std::max(_currentAnimation->AnimDuration + 1.0f - Vector2f(_speed.X, _speed.Y).Length() * 0.26f, 0.4f); } } else if (_waterCooldownLeft <= 0.0f) { _inWater = false; _waterCooldownLeft = 20.0f; SetState(ActorState::ApplyGravitation | ActorState::CanJump, true); _externalForce.Y = -0.6f; _renderer.setRotation(0.0f); SetAnimation(AnimState::Jump); OnWaterSplash(Vector2f(_pos.X, _levelHandler->GetWaterLevel()), false); } } else { if (_pos.Y >= _levelHandler->GetWaterLevel() && _waterCooldownLeft <= 0.0f) { _inWater = true; _waterCooldownLeft = 20.0f; _controllable = true; EndDamagingMove(); OnWaterSplash(Vector2f(_pos.X, _levelHandler->GetWaterLevel()), true); } // Adjust walking animation speed if (_currentAnimation->State == AnimState::Walk && _currentTransition == nullptr) { _renderer.AnimDuration = _currentAnimation->AnimDuration * (1.4f - 0.4f * std::min(std::abs(_speed.X), MaxRunningSpeed) / MaxRunningSpeed); } } } void Player::OnHandleAreaEvents(float timeMult, bool& areaWeaponAllowed, std::int32_t& areaWaterBlock) { areaWeaponAllowed = true; areaWaterBlock = -1; auto events = _levelHandler->EventMap(); if (events == nullptr) { return; } std::uint8_t* p; EventType tileEvent = events->GetEventByPosition(_pos.X, _pos.Y, &p); switch (tileEvent) { case EventType::LightAmbient: { // Intensity, Red, Green, Blue, Flicker // TODO: Change only player view, handle splitscreen multiplayer _levelHandler->SetAmbientLight(this, p[0] / 255.0f); break; } case EventType::WarpOrigin: { // Warp ID, Fast, Set Lap // Allow warping only if not in non-cancellable transition, except for some cosmetic but non-cancellable ones if (_currentTransition == nullptr || _currentTransitionCancellable || (_currentTransition->State == (AnimState::Dash | AnimState::Jump) || _currentTransition->State == AnimState::Spring || _currentTransition->State == AnimState::TransitionCopterShootToCopter || _currentTransition->State == AnimState::TransitionFallShootToFall || _currentTransition->State == AnimState::TransitionHookShootToHook || _currentTransition->State == AnimState::TransitionShootToIdle || _currentTransition->State == AnimState::TransitionUppercutEnd)) { Vector2f c = events->GetWarpTarget(p[0]); if (c.X >= 0.0f && c.Y >= 0.0f) { WarpFlags flags = WarpFlags::Default; if (p[1] != 0) { flags |= WarpFlags::Fast; } if (p[2] != 0) { flags |= WarpFlags::IncrementLaps; } WarpToPosition(c, flags); } } break; } case EventType::ModifierDeath: { TakeDamage(INT32_MAX, 0.0f, true); break; } case EventType::ModifierSetWater: { _levelHandler->BroadcastTriggeredEvent(this, EventType::ModifierSetWater, p); break; } case EventType::ModifierLimitCameraView: { // Left, Width std::uint16_t left = *(std::uint16_t*)&p[0]; std::uint16_t width = *(std::uint16_t*)&p[2]; _levelHandler->LimitCameraView(this, _pos, (left == 0 ? (std::int32_t)(_pos.X / Tiles::TileSet::DefaultTileSize) : left) * Tiles::TileSet::DefaultTileSize, width * Tiles::TileSet::DefaultTileSize); break; } case EventType::ModifierHPole: { InitialPoleStage(true); break; } case EventType::ModifierVPole: { InitialPoleStage(false); break; } case EventType::ModifierTube: { // XSpeed, YSpeed, Wait Time, Trig Sample, Become No-clip, No-clip Only // TODO: Implement other parameters bool becomeNoclip = (p[4] != 0); bool noclipOnly = (p[5] != 0); if (noclipOnly == GetState(ActorState::CollideWithTileset)) { // A player in Noclip Mode cannot use a tube event with Noclip Only set to false, // nor can a player not in Noclip Mode use a tube event with Noclip Only set to true break; } EndDamagingMove(); SetAnimation(AnimState::Dash | AnimState::Jump); _controllable = false; SetState(ActorState::CanJump | ActorState::ApplyGravitation, false); _speed.X = (float)(std::int8_t)p[0]; _speed.Y = (float)(std::int8_t)p[1]; Vector2f pos = _pos; if (_speed.X == 0.0f) { pos.X = (std::floor(pos.X / 32) * 32) + 16; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); OnUpdateHitbox(); } else if (_speed.Y == 0.0f) { pos.Y = (std::floor(pos.Y / 32) * 32) + 8; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); OnUpdateHitbox(); } else if (_inTubeTime <= 0.0f) { pos.X = (std::floor(pos.X / 32) * 32) + 16; pos.Y = (std::floor(pos.Y / 32) * 32) + 8; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); OnUpdateHitbox(); } SetState(ActorState::CollideWithTileset, !becomeNoclip); _inTubeTime = (becomeNoclip ? 600.0f : 10.0f); break; } case EventType::AreaEndOfLevel: { // ExitType, Fast (No score count, only black screen), TextID, TextOffset, Coins if (_levelExiting == LevelExitingState::None) { // TODO: Implement Fast parameter uint16_t coinsRequired = *(std::uint16_t*)&p[4]; if (coinsRequired <= _coins) { _coins -= coinsRequired; ExitType exitType = (ExitType)p[0]; if (p[1] != 0) { exitType |= ExitType::FastTransition; } StringView nextLevel; if (p[2] != 0) { nextLevel = _levelHandler->GetLevelText(p[2], p[3], '|'); } _levelHandler->BeginLevelChange(this, exitType, nextLevel); } else if (_bonusWarpTimer <= 0.0f) { _levelHandler->HandlePlayerCoins(this, _coins, _coins); PlaySfx("BonusWarpNotEnoughCoins"_s); _bonusWarpTimer = 400.0f; } } break; } case EventType::AreaText: { // Text, TextOffset, Vanish std::uint8_t index = p[1]; StringView text = _levelHandler->GetLevelText(p[0], index != 0 ? index : -1, '|'); _levelHandler->ShowLevelText(text, this); if (p[2] != 0) { events->StoreTileEvent((std::int32_t)(_pos.X / 32), (std::int32_t)(_pos.Y / 32), EventType::Empty); } break; } case EventType::AreaCallback: { // Function, Param, Vanish // Skip AreaCallbacks if player is currently warping if (_currentTransition == nullptr || (_currentTransition->State != AnimState::TransitionWarpIn && _currentTransition->State != AnimState::TransitionWarpOut && _currentTransition->State != AnimState::TransitionWarpInFreefall && _currentTransition->State != AnimState::TransitionWarpOutFreefall)) { _levelHandler->BroadcastTriggeredEvent(this, EventType::AreaCallback, p); } break; } case EventType::AreaActivateBoss: { // Music _levelHandler->BroadcastTriggeredEvent(this, EventType::AreaActivateBoss, p); // Deactivate sugar rush if it's active if (_sugarRushLeft > 1.0f) { _sugarRushLeft = 1.0f; } break; } case EventType::AreaFlyOff: { if (_activeModifier == Modifier::Airboard) { SetModifier(Modifier::None); } break; } case EventType::AreaRevertMorph: { if (_playerType != _playerTypeOriginal) { MorphRevert(); } break; } case EventType::AreaMorphToFrog: { if (_playerType != PlayerType::Frog) { MorphTo(PlayerType::Frog); } break; } case EventType::AreaNoFire: { switch (p[0]) { case 0: areaWeaponAllowed = false; break; case 1: _weaponAllowed = true; break; case 2: _weaponAllowed = false; break; } break; } case EventType::AreaWaterBlock: { areaWaterBlock = ((std::int32_t)_pos.Y / 32) * 32 + p[0]; break; } case EventType::TriggerZone: { // Trigger ID, Turn On, Switch // TODO: Implement Switch parameter _levelHandler->SetTrigger(p[0], p[1] != 0); break; } case EventType::RollingRockTrigger: { // Rock ID _levelHandler->BroadcastTriggeredEvent(this, EventType::RollingRockTrigger, p); break; } } // TODO: Implement Slide modifier with JJ2+ parameter // Check floating from each corner of an extended hitbox // Player should not pass from a single tile wide gap if the columns left or right have // float events, so checking for a wider box is necessary. constexpr float ExtendedHitbox = 2.0f; if (!_isAttachedToPole && (_currentTransition == nullptr || _currentTransition->State != AnimState::TransitionLedgeClimb)) { if (_currentSpecialMove != SpecialMoveType::Buttstomp) { if ((events->GetEventByPosition(_pos.X, _pos.Y, &p) == EventType::AreaFloatUp) || (events->GetEventByPosition(AABBInner.L - ExtendedHitbox, AABBInner.T - ExtendedHitbox, &p) == EventType::AreaFloatUp) || (events->GetEventByPosition(AABBInner.R + ExtendedHitbox, AABBInner.T - ExtendedHitbox, &p) == EventType::AreaFloatUp) || (events->GetEventByPosition(AABBInner.R + ExtendedHitbox, AABBInner.B + ExtendedHitbox, &p) == EventType::AreaFloatUp) || (events->GetEventByPosition(AABBInner.L - ExtendedHitbox, AABBInner.B + ExtendedHitbox, &p) == EventType::AreaFloatUp) ) { // External force of pinball bumber has higher priority if (_externalForceCooldown <= 0.0f || _speed.Y < 0.0f) { if ((_currentAnimation->State & AnimState::Copter) == AnimState::Copter) { _speed.Y = std::max(_speed.Y - _levelHandler->GetGravity() * timeMult * 8.0f, -6.0f); } else if (GetState(ActorState::ApplyGravitation)) { float gravity = _levelHandler->GetGravity(); _externalForce.Y = -2.0f * gravity * timeMult; _speed.Y = std::min(gravity * timeMult, _speed.Y); } else { _speed.Y = std::max(_speed.Y - _levelHandler->GetGravity() * timeMult, -6.0f); } } } } if ((events->GetEventByPosition(_pos.X, _pos.Y, &p) == EventType::AreaHForce) || (events->GetEventByPosition(AABBInner.L - ExtendedHitbox, AABBInner.T - ExtendedHitbox, &p) == EventType::AreaHForce) || (events->GetEventByPosition(AABBInner.R + ExtendedHitbox, AABBInner.T - ExtendedHitbox, &p) == EventType::AreaHForce) || (events->GetEventByPosition(AABBInner.R + ExtendedHitbox, AABBInner.B + ExtendedHitbox, &p) == EventType::AreaHForce) || (events->GetEventByPosition(AABBInner.L - ExtendedHitbox, AABBInner.B + ExtendedHitbox, &p) == EventType::AreaHForce) ) { std::uint8_t p1 = p[4]; std::uint8_t p2 = p[5]; if ((p2 != 0 || p1 != 0)) { MoveInstantly(Vector2f((p2 - p1) * 0.7f * timeMult, 0), MoveType::Relative); } } if (GetState(ActorState::CanJump)) { // Floor events tileEvent = events->GetEventByPosition(_pos.X, _pos.Y + 32, &p); switch (tileEvent) { case EventType::AreaHForce: { std::uint8_t p1 = p[0]; std::uint8_t p2 = p[1]; std::uint8_t p3 = p[2]; std::uint8_t p4 = p[3]; if (p2 != 0 || p1 != 0) { MoveInstantly(Vector2f((p2 - p1) * 0.7f * timeMult, 0), MoveType::Relative); } if (p4 != 0 || p3 != 0) { _speed.X += (p4 - p3) * 0.1f; } break; } } } } } std::shared_ptr Player::PlayPlayerSfx(StringView identifier, float gain, float pitch) { auto it = _metadata->Sounds.find(String::nullTerminatedView(identifier)); if (it != _metadata->Sounds.end()) { AudioBuffer* buffer; if (!it->second.Buffers.empty()) { std::int32_t idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (std::int32_t)it->second.Buffers.size()) : 0); buffer = &it->second.Buffers[idx]->Buffer; } else { buffer = nullptr; } return _levelHandler->PlaySfx(this, identifier, buffer, Vector3f::Zero, true, gain, pitch); } return nullptr; } bool Player::SetPlayerTransition(AnimState state, bool cancellable, bool removeControl, SpecialMoveType specialMove, Function&& callback) { if (removeControl) { _controllable = false; _controllableTimeout = 0.0f; } _currentSpecialMove = specialMove; return SetTransition(state, cancellable, std::move(callback)); } bool Player::CanFreefall() { AABBf aabb = AABBf(_pos.X - 14, _pos.Y + 8 - 12, _pos.X + 14, _pos.Y + 8 + 12 + 100); TileCollisionParams params = { TileDestructType::None, true }; return _levelHandler->IsPositionEmpty(this, aabb, params); } void Player::OnPerishInner() { _trailLastPos = _pos; #if defined(WITH_AUDIO) if (_copterSound != nullptr) { _copterSound->stop(); _copterSound = nullptr; } if (_weaponSound != nullptr) { _weaponSound->stop(); _weaponSound = nullptr; } #endif SetState(ActorState::CanJump, false); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; _pushFramesLeft = 0.0f; _weaponCooldown = 0.0f; _controllableTimeout = 0.0f; _inShallowWater = -1; _keepRunningTime = 0.0f; _invulnerableTime = 0.0f; _lastPoleTime = 0.0f; _isAttachedToPole = false; SetDizzy(0.0f); SetModifier(Modifier::None); SetShield(ShieldType::None, 0.0f); SetPlayerTransition(AnimState::TransitionDeath, false, true, SpecialMoveType::None, [this]() { _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _inShallowWater = -1; _keepRunningTime = 0.0f; _carryingObject = nullptr; if (_lives > 1 || !_levelHandler->IsLocalSession()) { if (_lives > 1 && _lives < UINT8_MAX) { _lives--; } // Revert food eaten only if Reforged if (_levelHandler->IsReforged()) { _foodEaten = _foodEatenCheckpoint; } // Revert coins, gems, ammo and weapon upgrades _coins = _coinsCheckpoint; std::memcpy(_gems, _gemsCheckpoint, sizeof(_gems)); std::memcpy(_weaponAmmo, _weaponAmmoCheckpoint, sizeof(_weaponAmmo)); std::memcpy(_weaponUpgrades, _weaponUpgradesCheckpoint, sizeof(_weaponUpgrades)); // Remove all fast fires and Blaster upgrades _weaponUpgrades[(std::int32_t)WeaponType::Blaster] = 0; // Reset current weapon to Blaster if player has no ammo on checkpoint if (_weaponAmmo[(std::int32_t)_currentWeapon] == 0) { SetCurrentWeapon(WeaponType::Blaster, SetCurrentWeaponReason::Rollback); } if (_sugarRushLeft > 0.0f) { _sugarRushLeft = 0.0f; _renderer.Initialize(ActorRendererType::Default); } // Spawn corpse std::shared_ptr corpse = std::make_shared(); std::uint8_t playerParams[2] = { (std::uint8_t)_playerType, (std::uint8_t)(IsFacingLeft() ? 1 : 0) }; corpse->OnActivated(ActorActivationDetails( _levelHandler, Vector3i(_pos.X, _pos.Y, _renderer.layer() - 40), playerParams )); _levelHandler->AddActor(corpse); SetAnimation(AnimState::Idle); if (_levelHandler->HandlePlayerDied(this)) { // Reset health _health = _maxHealth; // Player can be respawned immediately if (_invulnerableTime <= 0.0f) { SetState(ActorState::IsInvulnerable, false); } SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects, true); _controllable = true; // Return to the last save point MoveInstantly(_checkpointPos, MoveType::Absolute | MoveType::Force); _levelHandler->SetAmbientLight(this, _checkpointLight); } else { // Respawn is delayed _controllable = false; _renderer.setDrawEnabled(false); _invulnerableTime = 0.0f; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors, false); } } else { _lives = 0; _renderer.setDrawEnabled(false); _levelHandler->HandleGameOver(this); } }); PlayPlayerSfx("Die"_s, 1.3f); _levelHandler->PlayerExecuteRumble(this, "Die"_s); } void Player::SwitchToNextWeapon() { #if defined(WITH_AUDIO) if (_weaponSound != nullptr) { _weaponSound->stop(); _weaponSound = nullptr; } #endif // Find next available weapon WeaponType weaponType = (WeaponType)(((std::int32_t)_currentWeapon + 1) % (std::int32_t)WeaponType::Count); for (std::int32_t i = 0; i < (std::int32_t)WeaponType::Count && _weaponAmmo[(std::int32_t)weaponType] == 0; i++) { weaponType = (WeaponType)(((std::int32_t)weaponType + 1) % (std::int32_t)WeaponType::Count); } SetCurrentWeapon(weaponType, SetCurrentWeaponReason::User); _weaponCooldown = 1.0f; } void Player::SwitchToWeaponByIndex(std::uint32_t weaponIndex) { if (weaponIndex >= (std::uint32_t)WeaponType::Count || _weaponAmmo[weaponIndex] == 0) { PlayPlayerSfx("ChangeWeapon"_s); return; } #if defined(WITH_AUDIO) if (_weaponSound != nullptr) { _weaponSound->stop(); _weaponSound = nullptr; } #endif SetCurrentWeapon((WeaponType)weaponIndex, SetCurrentWeaponReason::User); _weaponCooldown = 1.0f; } template void Player::FireWeapon(float cooldownBase, float cooldownUpgrade, bool emitFlare) { // NOTE: cooldownBase and cooldownUpgrade cannot be template parameters in Emscripten Vector3i initialPos; Vector2f gunspotPos; float angle; GetFirePointAndAngle(initialPos, gunspotPos, angle); std::shared_ptr shot = std::make_shared(); std::uint8_t shotParams[1] = { _weaponUpgrades[(std::int32_t)weaponType] }; shot->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot->OnFire(shared_from_this(), gunspotPos, _speed, angle, IsFacingLeft()); _levelHandler->AddActor(shot); std::int32_t fastFire = (_weaponUpgrades[(std::int32_t)WeaponType::Blaster] >> 1); _weaponCooldown = cooldownBase - (fastFire * cooldownUpgrade); if (emitFlare) { EmitWeaponFlare(); } } void Player::FireWeaponRF() { Vector3i initialPos; Vector2f gunspotPos; float angle; GetFirePointAndAngle(initialPos, gunspotPos, angle); uint8_t shotParams[1] = { _weaponUpgrades[(std::int32_t)WeaponType::RF] }; if ((_weaponUpgrades[(std::int32_t)WeaponType::RF] & 0x1) != 0) { std::shared_ptr shot1 = std::make_shared(); shot1->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot1->OnFire(shared_from_this(), gunspotPos, _speed, angle - 0.3f, IsFacingLeft()); _levelHandler->AddActor(shot1); std::shared_ptr shot2 = std::make_shared(); shot2->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot2->OnFire(shared_from_this(), gunspotPos, _speed, angle, IsFacingLeft()); _levelHandler->AddActor(shot2); std::shared_ptr shot3 = std::make_shared(); shot3->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot3->OnFire(shared_from_this(), gunspotPos, _speed, angle + 0.3f, IsFacingLeft()); _levelHandler->AddActor(shot3); } else { std::shared_ptr shot1 = std::make_shared(); shot1->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot1->OnFire(shared_from_this(), gunspotPos, _speed, angle - 0.26f, IsFacingLeft()); _levelHandler->AddActor(shot1); std::shared_ptr shot2 = std::make_shared(); shot2->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot2->OnFire(shared_from_this(), gunspotPos, _speed, angle + 0.26f, IsFacingLeft()); _levelHandler->AddActor(shot2); } _weaponCooldown = 120.0f; EmitWeaponFlare(); } void Player::FireWeaponPepper() { Vector3i initialPos; Vector2f gunspotPos; float angle; GetFirePointAndAngle(initialPos, gunspotPos, angle); uint8_t shotParams[1] = { _weaponUpgrades[(std::int32_t)WeaponType::Pepper] }; std::shared_ptr shot1 = std::make_shared(); shot1->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot1->OnFire(shared_from_this(), gunspotPos, _speed, angle - Random().NextFloat(-0.2f, 0.2f), IsFacingLeft()); _levelHandler->AddActor(shot1); std::shared_ptr shot2 = std::make_shared(); shot2->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot2->OnFire(shared_from_this(), gunspotPos, _speed, angle + Random().NextFloat(-0.2f, 0.2f), IsFacingLeft()); _levelHandler->AddActor(shot2); std::int32_t fastFire = (_weaponUpgrades[(std::int32_t)WeaponType::Blaster] >> 1); _weaponCooldown = 30.0f - (fastFire * 2.76f); EmitWeaponFlare(); } void Player::FireWeaponTNT() { std::shared_ptr tnt = std::make_shared(); tnt->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2) )); tnt->OnFire(shared_from_this()); _levelHandler->AddActor(tnt); _levelHandler->PlayerExecuteRumble(this, "FireWeak"_s); _weaponCooldown = 60.0f; } bool Player::FireWeaponThunderbolt() { if (_isActivelyPushing || _inWater || _isAttachedToPole) { return false; } Vector3i initialPos; Vector2f gunspotPos; float angle; GetFirePointAndAngle(initialPos, gunspotPos, angle); std::shared_ptr shot = std::make_shared(); uint8_t shotParams[1] = { _weaponUpgrades[(std::int32_t)WeaponType::Thunderbolt] }; shot->OnActivated(ActorActivationDetails( _levelHandler, initialPos, shotParams )); shot->OnFire(shared_from_this(), gunspotPos, _speed, angle, IsFacingLeft()); _levelHandler->AddActor(shot); _weaponCooldown = 12.0f - (_weaponUpgrades[(std::int32_t)WeaponType::Blaster] * 0.1f); if (!_inWater && (_currentAnimation->State & AnimState::Lookup) != AnimState::Lookup) { AddExternalForce(IsFacingLeft() ? 2.0f : -2.0f, 0.0f); } #if defined(WITH_AUDIO) if (_weaponSound == nullptr) { PlaySfx("WeaponThunderboltStart"_s, 0.5f); _weaponSound = PlaySfx("WeaponThunderbolt"_s, 1.0f); if (_weaponSound != nullptr) { _weaponSound->setLooping(true); _weaponSound->setPitch(Random().FastFloat(1.05f, 1.2f)); _weaponSound->setLowPass(0.9f); } } #endif return true; } bool Player::FireCurrentWeapon(WeaponType weaponType) { if (_weaponCooldown > 0.0f) { return (weaponType != WeaponType::TNT); } std::uint16_t ammoDecrease = 256; switch (weaponType) { case WeaponType::Blaster: switch (_activeShield) { case ShieldType::Fire: { if (_inWater) { return false; } FireWeapon(10.0f, 0.0f, true); break; } case ShieldType::Water: { FireWeapon(8.0f, 0.0f); break; } case ShieldType::Lightning: { FireWeapon(10.0f, 0.0f); break; } default: { FireWeapon(30.0f, 2.76f, true); PlayPlayerSfx("WeaponBlaster"_s); break; } } ammoDecrease = 0; break; case WeaponType::Bouncer: FireWeapon(30.0f, 2.76f, true); break; case WeaponType::Freezer: // TODO: Add upgraded freezer FireWeapon(30.0f, 2.76f); break; case WeaponType::Seeker: FireWeapon(120.0f, 0.0f, true); break; case WeaponType::RF: FireWeaponRF(); break; case WeaponType::Toaster: { if (_inWater) { return false; } FireWeapon(6.0f, 0.0f); #if defined(WITH_AUDIO) if (_weaponSound == nullptr) { _weaponSound = PlaySfx("WeaponToaster"_s, 0.6f); if (_weaponSound != nullptr) { _weaponSound->setLooping(true); } } #endif ammoDecrease = 50; break; } case WeaponType::TNT: FireWeaponTNT(); break; case WeaponType::Pepper: FireWeaponPepper(); break; case WeaponType::Electro: FireWeapon(30.0f, 2.76f, true); break; case WeaponType::Thunderbolt: { if (!FireWeaponThunderbolt()) { return false; } ammoDecrease = ((_weaponUpgrades[(std::int32_t)WeaponType::Thunderbolt] & 0x1) != 0 ? 40 : 80); // Lower ammo consumption with upgrade break; } default: return false; } auto& currentAmmo = _weaponAmmo[(std::int32_t)weaponType]; if (ammoDecrease > currentAmmo) { ammoDecrease = currentAmmo; } currentAmmo -= ammoDecrease; // No ammo, switch weapons if (currentAmmo == 0) { // Remove upgrade if no ammo left _weaponUpgrades[(std::int32_t)weaponType] &= ~0x01; SwitchToNextWeapon(); PlayPlayerSfx("ChangeWeapon"_s); _weaponCooldown = 20.0f; } return (weaponType != WeaponType::TNT); } void Player::EmitWeaponFlare() { _weaponFlareFrame = (Random().Next() & 0xFFFF); _weaponFlareTime = 6.0f; } void Player::SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason) { // Handle only local sessions here, online sessions are handled in derived classes if (reason == SetCurrentWeaponReason::AddAmmo && !PreferencesCache::SwitchToNewWeapon && _levelHandler->IsLocalSession()) { return; } _currentWeapon = weaponType; } void Player::GetFirePointAndAngle(Vector3i& initialPos, Vector2f& gunspotPos, float& angle) { if (_currentTransition != nullptr && (_currentTransition->State == AnimState::Spring || _currentTransition->State == AnimState::TransitionShootToIdle)) { ForceCancelTransition(); } SetAnimation(_currentAnimation->State | AnimState::Shoot); initialPos = Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() - 2); gunspotPos = _pos; if (_inWater) { angle = _renderer.rotation(); std::int32_t size = (_currentAnimation->Base->FrameDimensions.X / 2); gunspotPos.X += (cosf(angle) * size) * (IsFacingLeft() ? -1.0f : 1.0f); gunspotPos.Y += (sinf(angle) * size) * (IsFacingLeft() ? -1.0f : 1.0f) - (_currentAnimation->Base->Hotspot.Y - _currentAnimation->Base->Gunspot.Y); } else { gunspotPos.X += (_currentAnimation->Base->Hotspot.X - _currentAnimation->Base->Gunspot.X) * (IsFacingLeft() ? 1 : -1); gunspotPos.Y -= (_currentAnimation->Base->Hotspot.Y - _currentAnimation->Base->Gunspot.Y); if ((_currentAnimation->State & AnimState::Lookup) == AnimState::Lookup) { initialPos.X = (std::int32_t)gunspotPos.X; angle = (IsFacingLeft() ? fRadAngle90 : fRadAngle270); } else { initialPos.Y = (std::int32_t)gunspotPos.Y; angle = 0.0f; } } } bool Player::OnLevelChanging(Actors::ActorBase* initiator, ExitType exitType) { // Deactivate any shield if (_activeShieldTime > 70.0f) { _activeShieldTime = 70.0f; } if (_spawnedBird != nullptr) { _spawnedBird->FlyAway(); _spawnedBird = nullptr; } switch (_levelExiting) { case LevelExitingState::Waiting: { if (CanJump() && std::abs(_speed.X) < 1.0f && std::abs(_speed.Y) < 1.0f) { _levelExiting = LevelExitingState::Transition; ForceCancelTransition(); SetPlayerTransition(AnimState::TransitionEndOfLevel, false, true, SpecialMoveType::None, [this]() { _renderer.setDrawEnabled(false); _levelExiting = LevelExitingState::Ready; }); PlayPlayerSfx("EndOfLevel1"_s, 1.0f / _levelHandler->GetPlayers().size()); SetState(ActorState::ApplyGravitation, false); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; } else if (_lastPoleTime <= 0.0f) { // Waiting timeout - use warp transition instead _levelExiting = LevelExitingState::Transition; ForceCancelTransition(); SetPlayerTransition(_isFreefall ? AnimState::TransitionWarpInFreefall : AnimState::TransitionWarpIn, false, true, SpecialMoveType::None, [this]() { _renderer.setDrawEnabled(false); _levelExiting = LevelExitingState::Ready; }); PlayPlayerSfx("WarpIn"_s, 1.0f / _levelHandler->GetPlayers().size()); _levelHandler->PlayerExecuteRumble(this, "Warp"_s); SetState(ActorState::ApplyGravitation, false); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; } else { // Refresh animation state, because UpdateAnimation() is not called when _controllable is false AnimState oldState = _currentAnimation->State; AnimState newState = (oldState & CompositeAnimMask); if (std::abs(_speed.X) > std::numeric_limits::epsilon()) { newState |= AnimState::Walk; } if (!CanJump()) { if (_speed.Y < 0.0f) { newState |= AnimState::Jump; } else { newState |= AnimState::Fall; } } SetAnimation(newState); if ((oldState == AnimState::Fall || oldState == AnimState::Freefall) && newState == AnimState::Idle) { SetTransition(AnimState::TransitionFallToIdle, true); } } return false; } case LevelExitingState::WaitingForWarp: { if (_lastPoleTime <= 0.0f) { _levelExiting = LevelExitingState::Transition; ForceCancelTransition(); SetPlayerTransition(_isFreefall || _inWater ? AnimState::TransitionWarpInFreefall : AnimState::TransitionWarpIn, false, true, SpecialMoveType::None, [this]() { _renderer.setDrawEnabled(false); _levelExiting = LevelExitingState::Ready; }); PlayPlayerSfx("WarpIn"_s, 1.0f / _levelHandler->GetPlayers().size()); _levelHandler->PlayerExecuteRumble(this, "Warp"_s); SetState(ActorState::ApplyGravitation, false); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; } else { // Refresh animation state, because UpdateAnimation() is not called when _controllable is false AnimState oldState = _currentAnimation->State; AnimState newState = (oldState & CompositeAnimMask); if (std::abs(_speed.X) > std::numeric_limits::epsilon()) { newState |= AnimState::Walk; } if (!CanJump()) { if (_speed.Y < 0.0f) { newState |= AnimState::Jump; } else { newState |= AnimState::Fall; } } SetAnimation(newState); if ((oldState == AnimState::Fall || oldState == AnimState::Freefall) && newState == AnimState::Idle) { SetTransition(AnimState::TransitionFallToIdle, true); } } return false; } case LevelExitingState::Transition: return false; case LevelExitingState::Ready: return true; } if (_health <= 0) { // Player is dead, just skip the transition _levelExiting = LevelExitingState::Ready; return true; } if (_suspendType != SuspendType::None) { MoveInstantly(Vector2f(0.0f, 4.0f), MoveType::Relative | MoveType::Force); _suspendType = SuspendType::None; _suspendTime = 60.0f; } _controllable = false; SetState(ActorState::IsInvulnerable | ActorState::ApplyGravitation, true); _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; _pushFramesLeft = 0.0f; _invulnerableTime = 0.0f; if (_sugarRushLeft > 1.0f) { _sugarRushLeft = 1.0f; } _renderer.setDrawEnabled(true); ExitType exitTypeMasked = (exitType & ExitType::TypeMask); if ((exitType & ExitType::FastTransition) == ExitType::FastTransition) { if (exitTypeMasked == ExitType::Warp || exitTypeMasked == ExitType::Bonus || exitTypeMasked == ExitType::Boss) { _levelExiting = LevelExitingState::WaitingForWarp; // Re-used for waiting timeout _lastPoleTime = 0.0f; return false; } else { _levelExiting = LevelExitingState::Ready; return true; } } else { if (initiator == this || (initiator == nullptr && _playerIndex == 0)) { PlayPlayerSfx("EndOfLevel"_s); } if (exitTypeMasked == ExitType::Warp || exitTypeMasked == ExitType::Bonus || exitTypeMasked == ExitType::Boss || _inWater) { _levelExiting = LevelExitingState::WaitingForWarp; // Re-used for waiting timeout _lastPoleTime = 100.0f; } else { _levelExiting = LevelExitingState::Waiting; SetFacingLeft(false); // Re-used for waiting timeout _lastPoleTime = 300.0f; } return false; } } void Player::ReceiveLevelCarryOver(ExitType exitType, const PlayerCarryOver& carryOver) { _lives = (std::int32_t)carryOver.Lives; _score = carryOver.Score; _foodEaten = (std::int32_t)carryOver.FoodEaten; _foodEatenCheckpoint = _foodEaten; _currentWeapon = carryOver.CurrentWeapon; std::memcpy(_gemsTotal, carryOver.Gems, sizeof(_gemsTotal)); std::memcpy(_weaponAmmo, carryOver.Ammo, sizeof(_weaponAmmo)); std::memcpy(_weaponAmmoCheckpoint, carryOver.Ammo, sizeof(_weaponAmmoCheckpoint)); std::memcpy(_weaponUpgrades, carryOver.WeaponUpgrades, sizeof(_weaponUpgrades)); std::memcpy(_weaponUpgradesCheckpoint, carryOver.WeaponUpgrades, sizeof(_weaponUpgradesCheckpoint)); _weaponAmmo[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; _weaponAmmoCheckpoint[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; ExitType exitTypeMasked = (exitType & ExitType::TypeMask); if (exitTypeMasked == ExitType::Warp || exitTypeMasked == ExitType::Bonus || exitTypeMasked == ExitType::Boss) { // Use delayed spawning SetState(ActorState::ApplyGravitation, false); _renderer.setDrawEnabled(false); _lastExitType = exitType; _controllable = false; _controllableTimeout = ((exitType & ExitType::FastTransition) == ExitType::FastTransition ? 5.0f : 55.0f); } else if ((exitType & ExitType::Frozen) == ExitType::Frozen) { // Use instant spawning Freeze(100.0f); } // Preload all weapons for (std::int32_t i = 0; i < std::int32_t(arraySize(_weaponAmmo)); i++) { if (_weaponAmmo[i] != 0) { PreloadMetadataAsync(String("Weapon/"_s + WeaponNames[i])); } } } PlayerCarryOver Player::PrepareLevelCarryOver() { PlayerCarryOver carryOver; carryOver.Type = _playerType; carryOver.Lives = (_lives > UINT8_MAX ? UINT8_MAX : (std::uint8_t)_lives); carryOver.Score = _score; carryOver.FoodEaten = (_foodEaten > UINT8_MAX ? UINT8_MAX : (std::uint8_t)_foodEaten); carryOver.CurrentWeapon = _currentWeapon; for (std::size_t i = 0; i < arraySize(carryOver.Gems); i++) { carryOver.Gems[i] = _gemsTotal[i] + _gems[i]; } std::memcpy(carryOver.Ammo, _weaponAmmo, sizeof(_weaponAmmo)); std::memcpy(carryOver.WeaponUpgrades, _weaponUpgrades, sizeof(_weaponUpgrades)); return carryOver; } void Player::InitializeFromStream(ILevelHandler* levelHandler, Stream& src, std::uint16_t version) { std::uint8_t playerIndex = src.ReadVariableInt32(); PlayerType playerType = (PlayerType)src.ReadValue(); PlayerType playerTypeOriginal = (PlayerType)src.ReadValue(); float checkpointPosX = src.ReadValueAsLE(); float checkpointPosY = src.ReadValueAsLE(); std::uint8_t playerParams[2] = { (std::uint8_t)playerType, (std::uint8_t)playerIndex }; OnActivated(Actors::ActorActivationDetails( levelHandler, Vector3i((std::int32_t)checkpointPosX, (std::int32_t)checkpointPosY, ILevelHandler::PlayerZ - playerIndex), playerParams )); _playerTypeOriginal = playerTypeOriginal; _checkpointLight = src.ReadValueAsLE(); _lives = src.ReadVariableInt32(); _coins = src.ReadVariableInt32(); _coinsCheckpoint = _coins; _foodEaten = src.ReadVariableInt32(); _foodEatenCheckpoint = _foodEaten; _score = src.ReadVariableInt32(); _gems[0] = src.ReadVariableInt32(); if (version >= 3) { // Gem types are split since v3.0.0 _gems[1] = src.ReadVariableInt32(); _gems[2] = src.ReadVariableInt32(); _gems[3] = src.ReadVariableInt32(); _gemsTotal[0] = src.ReadVariableInt32(); _gemsTotal[1] = src.ReadVariableInt32(); _gemsTotal[2] = src.ReadVariableInt32(); _gemsTotal[3] = src.ReadVariableInt32(); } std::memcpy(_gemsCheckpoint, _gems, sizeof(_gems)); levelHandler->SetAmbientLight(this, _checkpointLight); std::int32_t weaponCount = src.ReadVariableInt32(); DEATH_ASSERT(weaponCount == std::int32_t(arraySize(_weaponAmmoCheckpoint)), "Weapon count mismatch", ); _currentWeapon = (WeaponType)src.ReadVariableInt32(); src.Read(_weaponAmmoCheckpoint, sizeof(_weaponAmmoCheckpoint)); src.Read(_weaponUpgradesCheckpoint, sizeof(_weaponUpgradesCheckpoint)); std::memcpy(_weaponAmmo, _weaponAmmoCheckpoint, sizeof(_weaponAmmoCheckpoint)); std::memcpy(_weaponUpgrades, _weaponUpgradesCheckpoint, sizeof(_weaponUpgradesCheckpoint)); // Reset current weapon to Blaster if player has no ammo on checkpoint if (_weaponAmmo[(std::int32_t)_currentWeapon] == 0) { SetCurrentWeapon(WeaponType::Blaster, SetCurrentWeaponReason::Rollback); } } void Player::SerializeResumableToStream(Stream& dest) { dest.WriteVariableInt32(_playerIndex); dest.WriteValue((std::uint8_t)_playerType); dest.WriteValue((std::uint8_t)_playerTypeOriginal); dest.WriteValueAsLE(_checkpointPos.X); dest.WriteValueAsLE(_checkpointPos.Y); dest.WriteValueAsLE(_checkpointLight); dest.WriteVariableInt32(_lives); dest.WriteVariableInt32(_coinsCheckpoint); dest.WriteVariableInt32(_foodEatenCheckpoint); dest.WriteVariableInt32(_score); dest.WriteVariableInt32(_gemsCheckpoint[0]); dest.WriteVariableInt32(_gemsCheckpoint[1]); dest.WriteVariableInt32(_gemsCheckpoint[2]); dest.WriteVariableInt32(_gemsCheckpoint[3]); dest.WriteVariableInt32(_gemsTotal[0]); dest.WriteVariableInt32(_gemsTotal[1]); dest.WriteVariableInt32(_gemsTotal[2]); dest.WriteVariableInt32(_gemsTotal[3]); dest.WriteVariableInt32(std::int32_t(arraySize(_weaponAmmoCheckpoint))); dest.WriteVariableInt32((std::int32_t)_currentWeapon); dest.Write(_weaponAmmoCheckpoint, sizeof(_weaponAmmoCheckpoint)); dest.Write(_weaponUpgradesCheckpoint, sizeof(_weaponUpgradesCheckpoint)); } bool Player::Respawn(Vector2f pos) { if ((GetState() & (ActorState::IsInvulnerable | ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors)) != ActorState::IsInvulnerable) { return false; } _health = _maxHealth; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); _controllable = true; _renderer.setDrawEnabled(true); SetState(ActorState::IsInvulnerable, false); SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithOtherActors, true); return true; } void Player::WarpToPosition(Vector2f pos, WarpFlags flags) { if ((flags & WarpFlags::Fast) == WarpFlags::Fast) { Vector2f posPrev = _pos; bool hideTrail = (posPrev - pos).Length() > 250.0f; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); if (hideTrail) { _trailLastPos = _pos; } _levelHandler->HandlePlayerWarped(this, posPrev, flags); } else { EndDamagingMove(); SetState(ActorState::IsInvulnerable, true); SetState(ActorState::ApplyGravitation, false); SetAnimation(_currentAnimation->State & ~(AnimState::Uppercut | AnimState::Buttstomp)); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; _pushFramesLeft = 0.0f; // For warping from the water _renderer.setRotation(0.0f); if ((flags & WarpFlags::SkipWarpIn) == WarpFlags::SkipWarpIn) { DoWarpOut(pos, flags); } else { PlayPlayerSfx("WarpIn"_s); _levelHandler->PlayerExecuteRumble(this, "Warp"_s); SetPlayerTransition(_isFreefall ? AnimState::TransitionWarpInFreefall : AnimState::TransitionWarpIn, false, true, SpecialMoveType::None, [this, pos, flags]() { DoWarpOut(pos, flags); }); } } } void Player::DoWarpOut(Vector2f pos, WarpFlags flags) { Vector2f posPrev = _pos; MoveInstantly(pos, MoveType::Absolute | MoveType::Force); _trailLastPos = _pos; PlayPlayerSfx("WarpOut"_s); _levelHandler->PlayerExecuteRumble(this, "Warp"_s); _levelHandler->HandlePlayerWarped(this, posPrev, flags); _isFreefall |= CanFreefall(); SetPlayerTransition(_isFreefall ? AnimState::TransitionWarpOutFreefall : AnimState::TransitionWarpOut, false, true, SpecialMoveType::None, [this, flags]() { SetState(ActorState::IsInvulnerable, false); // Don't re-enable gravity if any modifier is active if (_activeModifier == Modifier::None) { SetState(ActorState::ApplyGravitation, true); } if ((flags & WarpFlags::Freeze) == WarpFlags::Freeze) { Freeze(100.0f); } else { _controllable = true; // UpdateAnimation() was probably skipped in this step, because _controllable was false, so call it here UpdateAnimation(0.0f); } }); } void Player::WarpToCheckpoint() { WarpToPosition(_checkpointPos, WarpFlags::SkipWarpIn); _levelHandler->SetAmbientLight(this, _checkpointLight); } void Player::InitialPoleStage(bool horizontal) { if (_isAttachedToPole || _playerType == PlayerType::Frog) { return; } std::int32_t x = (std::int32_t)_pos.X / Tiles::TileSet::DefaultTileSize; std::int32_t y = (std::int32_t)_pos.Y / Tiles::TileSet::DefaultTileSize; if (_lastPoleTime > 0.0f && _lastPolePos.X == x && _lastPolePos.Y == y) { return; } _lastPoleTime = 80.0f; _lastPolePos = Vector2i(x, y); float activeForce, lastSpeed; if (horizontal) { activeForce = (std::abs(_externalForce.X) > 1.0f ? _externalForce.X : _speed.X); lastSpeed = _speed.X; } else { activeForce = _speed.Y; lastSpeed = _speed.Y; } bool positive = (activeForce >= 0.0f); float tx = static_cast(x * Tiles::TileSet::DefaultTileSize + Tiles::TileSet::DefaultTileSize / 2); float ty = static_cast(y * Tiles::TileSet::DefaultTileSize + Tiles::TileSet::DefaultTileSize / 2); if (_levelHandler->IsReforged()) { auto* events = _levelHandler->EventMap(); std::uint8_t* p; if (horizontal) { if (events->GetEventByPosition(x, (_pos.Y < ty ? y - 1 : y + 1), &p) == EventType::ModifierHPole) { ty = _pos.Y; } } else { if (events->GetEventByPosition((_pos.X < tx ? x - 1 : x + 1), y, &p) == EventType::ModifierVPole) { tx = _pos.X; } } } MoveInstantly(Vector2f(tx, ty), MoveType::Absolute | MoveType::Force); OnUpdateHitbox(); _speed.X = 0.0f; _speed.Y = 0.0f; _externalForce.X = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; SetState(ActorState::ApplyGravitation, false); _renderer.setRotation(0.0f); _isAttachedToPole = true; if (_inIdleTransition) { _inIdleTransition = false; CancelTransition(); } _keepRunningTime = 0.0f; _pushFramesLeft = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; SetAnimation(_currentAnimation->State & ~(AnimState::Uppercut /*| AnimState::Sidekick*/ | AnimState::Buttstomp)); AnimState poleAnim = (horizontal ? AnimState::TransitionPoleHSlow : AnimState::TransitionPoleVSlow); SetPlayerTransition(poleAnim, false, true, SpecialMoveType::None, [this, horizontal, positive, lastSpeed]() { NextPoleStage(horizontal, positive, 2, lastSpeed); }); _controllableTimeout = 80.0f; PlayPlayerSfx("Pole"_s, 0.8f, 0.6f); } void Player::NextPoleStage(bool horizontal, bool positive, std::int32_t stagesLeft, float lastSpeed) { if (_inIdleTransition) { _inIdleTransition = false; CancelTransition(); } if (stagesLeft > 0) { AnimState poleAnim = (horizontal ? AnimState::TransitionPoleH : AnimState::TransitionPoleV); SetPlayerTransition(poleAnim, false, true, SpecialMoveType::None, [this, horizontal, positive, stagesLeft, lastSpeed]() { NextPoleStage(horizontal, positive, stagesLeft - 1, lastSpeed); }); _controllableTimeout = 80.0f; PlayPlayerSfx("Pole"_s, 1.0f, 0.6f); } else { std::int32_t sign = (positive ? 1 : -1); if (horizontal) { // To prevent stucking for (std::int32_t i = -1; i > -6; i--) { if (MoveInstantly(Vector2f(_speed.X, (float)i), MoveType::Relative)) { break; } } _speed.X = 10 * sign + lastSpeed * 0.2f; _externalForce.X = 10.0f * sign; SetFacingLeft(!positive); _keepRunningTime = 60.0f; SetPlayerTransition(AnimState::Dash | AnimState::Jump, true, true, SpecialMoveType::None); } else { MoveInstantly(Vector2f(0.0f, sign * 16.0f), MoveType::Relative | MoveType::Force); _speed.Y = 4.0f * sign + lastSpeed * 1.4f; _externalForce.Y = 1.3f * sign; } SetState(ActorState::ApplyGravitation, true); _isAttachedToPole = false; _wasActivelyPushing = false; _controllableTimeout = 4.0f; _lastPoleTime = 10.0f; PlayPlayerSfx("HookAttach"_s, 0.8f, 1.2f); } } Player::Modifier Player::GetModifier() const { return _activeModifier; } bool Player::SetModifier(Modifier modifier, const std::shared_ptr& decor) { if (_activeModifier == modifier) { return false; } if (_activeModifierDecor != nullptr) { _activeModifierDecor->OnDetach(this); _activeModifierDecor = nullptr; } switch (modifier) { case Modifier::Airboard: { _controllable = true; EndDamagingMove(); SetState(ActorState::ApplyGravitation, false); _speed.Y = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _activeModifier = Modifier::Airboard; MoveInstantly(Vector2f(0.0f, -16.0f), MoveType::Relative); break; } case Modifier::Copter: { _controllable = true; EndDamagingMove(); SetState(ActorState::ApplyGravitation, false); _speed.Y = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _activeModifier = Modifier::Copter; _copterFramesLeft = 10.0f * FrameTimer::FramesPerSecond; #if defined(WITH_AUDIO) if (_copterSound == nullptr) { _copterSound = PlaySfx("Copter"_s, 0.6f, 1.5f); if (_copterSound != nullptr) { _copterSound->setLooping(true); } } #endif break; } case Modifier::LizardCopter: { _controllable = true; EndDamagingMove(); SetState(ActorState::ApplyGravitation, false); _speed.Y = 0.0f; _externalForce.Y = 0.0f; _internalForceY = 0.0f; _activeModifier = Modifier::LizardCopter; _activeModifierDecor = decor; _activeModifierDecor->OnDetach(this); _copterFramesLeft = 3.0f * FrameTimer::FramesPerSecond; break; } default: { _activeModifier = Modifier::None; SetState(ActorState::CanJump | ActorState::ApplyGravitation, true); SetAnimation(AnimState::Fall); break; } } return true; } bool Player::TakeDamage(std::int32_t amount, float pushForce, bool ignoreInvulnerable) { if (amount <= 0 || _health <= 0 || (!ignoreInvulnerable && GetState(ActorState::IsInvulnerable)) || _levelExiting != LevelExitingState::None) { return false; } // Cancel active climbing and copter if (_currentTransition != nullptr && _currentTransition->State == AnimState::TransitionLedgeClimb) { ForceCancelTransition(); MoveInstantly(Vector2f(IsFacingLeft() ? 6.0f : -6.0f, 0.0f), MoveType::Relative | MoveType::Force); } else if (_activeModifier == Modifier::Copter || _activeModifier == Modifier::LizardCopter) { SetModifier(Modifier::None); } if (_spawnedBird != nullptr) { _spawnedBird->FlyAway(); _spawnedBird = nullptr; // Bird acts as extra life _health++; } DecreaseHealth(amount); _speed.X = 0.0f; _internalForceY = 0.0f; _fireFramesLeft = 0.0f; _copterFramesLeft = 0.0f; _pushFramesLeft = 0.0f; SetState(ActorState::CanJump, false); _isAttachedToPole = false; if (_health > 0) { _externalForce.X = pushForce; if (!_inWater && _activeModifier == Modifier::None) { _speed.Y = -6.5f; SetState(ActorState::ApplyGravitation | ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects, true); SetAnimation(AnimState::Idle); } SetPlayerTransition(AnimState::Hurt, false, true, SpecialMoveType::None, [this]() { _controllable = true; }); float invulnerableTime = _levelHandler->GetHurtInvulnerableTime(); SetInvulnerability(invulnerableTime, InvulnerableType::Blinking); PlayPlayerSfx("Hurt"_s); _levelHandler->PlayerExecuteRumble(this, "Hurt"_s); } else { _externalForce.X = 0.0f; _speed.Y = 0.0f; PlayPlayerSfx("Die"_s, 1.3f); } return true; } bool Player::Freeze(float timeLeft) { if (_currentTransition != nullptr && _currentTransition->State == AnimState::TransitionDeath) { // Don't allow freezing during death transition return false; } if (timeLeft > 0.0f) { _renderer.AnimPaused = true; _controllable = false; _controllableTimeout = timeLeft; } else if (_frozenTimeLeft > 0.0f) { _controllable = true; _controllableTimeout = 0.0f; } _frozenTimeLeft = timeLeft; return true; } void Player::SetInvulnerability(float timeLeft, InvulnerableType type) { if (timeLeft <= 0.0f) { if (_invulnerableTime > 0.0f) { SetState(ActorState::IsInvulnerable, false); _invulnerableTime = 0.0f; _shieldSpawnTime = ShieldDisabled; _renderer.setDrawEnabled(true); } return; } if (type == InvulnerableType::Shielded) { if (_invulnerableTime > 0.0f) { // If the players is already blinking, show it now _renderer.setDrawEnabled(true); } if (_shieldSpawnTime <= ShieldDisabled) { _shieldSpawnTime = 1.0f; } } else { _invulnerableBlinkTime = (type == InvulnerableType::Transient ? -1.0f : 0.0f); _shieldSpawnTime = ShieldDisabled; } SetState(ActorState::IsInvulnerable, true); _invulnerableTime = timeLeft; } void Player::EndDamagingMove() { if (_currentSpecialMove == SpecialMoveType::None) { return; } SetState(ActorState::ApplyGravitation, true); SetAnimation(_currentAnimation->State & ~(AnimState::Uppercut | AnimState::Buttstomp)); if (_currentSpecialMove == SpecialMoveType::Uppercut) { if (_suspendType == SuspendType::None) { SetTransition(AnimState::TransitionUppercutEnd, false); } _controllable = true; if (_externalForce.Y < 0.0f) { _externalForce.Y = 0.0f; } } else if (_currentSpecialMove == SpecialMoveType::Sidekick) { CancelTransition(); _controllable = true; _controllableTimeout = 10; } _currentSpecialMove = SpecialMoveType::None; } std::int32_t Player::GetScore() const { return _score; } void Player::AddScore(std::int32_t amount) { _score = std::min(std::max(_score + amount, 0), 999999999); } bool Player::AddHealth(std::int32_t amount) { constexpr std::int32_t HealthLimit = 5; if (_health >= HealthLimit) { return false; } if (amount < 0) { _health = std::max(_maxHealth, HealthLimit); PlayPlayerSfx("PickupMaxCarrot"_s); } else { _health = std::min(_health + amount, HealthLimit); if (_maxHealth < _health) { _maxHealth = _health; } PlayPlayerSfx("PickupFood"_s); } return true; } std::int32_t Player::GetLives() const { return _lives; } bool Player::AddLives(std::int32_t count) { constexpr std::int32_t LivesLimit = 99; if (_lives >= LivesLimit) { return false; } _lives = std::min(_lives + count, LivesLimit); PlayPlayerSfx("PickupOneUp"_s); return true; } std::int32_t Player::GetCoins() const { return _coins; } void Player::AddCoins(std::int32_t count) { std::int32_t prevCoins = _coins; _coins += count; _levelHandler->HandlePlayerCoins(this, prevCoins, _coins); PlayPlayerSfx("PickupCoin"_s); } void Player::AddCoinsInternal(std::int32_t count) { _coins += count; } std::int32_t Player::GetGems(std::uint8_t gemType) const { if (gemType >= arraySize(_gems)) { return 0; } return _gems[gemType]; } void Player::AddGems(std::uint8_t gemType, std::int32_t count) { if (gemType >= arraySize(_gems)) { return; } std::int32_t prevGems = _gems[gemType]; _gems[gemType] += count; _levelHandler->HandlePlayerGems(this, gemType, prevGems, _gems[gemType]); float pitch = 1.0f - fabs(2.0f * fmod(_gemsPitch * 0.05f, 1.0f) - 1.0f); PlayPlayerSfx("PickupGem"_s, 1.0f, std::min(0.7f + pitch * 0.6f, 1.3f)); _gemsTimer = 120.0f; _gemsPitch++; } std::int32_t Player::GetConsumedFood() const { return _foodEaten; } void Player::ConsumeFood(bool isDrinkable) { PlayPlayerSfx(isDrinkable ? "PickupDrink"_s : "PickupFood"_s); _foodEaten++; if (_foodEaten >= 100 && _levelHandler->CanActivateSugarRush()) { _foodEaten = _foodEaten % 100; ActivateSugarRush(1300.0f); } } void Player::ActivateSugarRush(float duration) { if (_sugarRushLeft > 0.0f) { _sugarRushLeft = std::max(duration, 1.0f); return; } if (duration <= 0.0f) { return; } _sugarRushLeft = duration; _renderer.Initialize(ActorRendererType::PartialWhiteMask); _weaponWheelState = WeaponWheelState::Hidden; _levelHandler->HandleActivateSugarRush(this); } bool Player::AddAmmo(WeaponType weaponType, std::int16_t count) { constexpr std::int16_t Multiplier = 256; constexpr std::int16_t AmmoLimit = 99 * Multiplier; if (weaponType >= WeaponType::Count || _weaponAmmo[(std::int32_t)weaponType] < 0 || _weaponAmmo[(std::int32_t)weaponType] >= AmmoLimit) { return false; } bool switchTo = (_weaponAmmo[(std::int32_t)weaponType] == 0); _weaponAmmo[(std::int32_t)weaponType] = (std::int16_t)std::min((std::int32_t)_weaponAmmo[(std::int32_t)weaponType] + count * Multiplier, (int32_t)AmmoLimit); if (switchTo) { SetCurrentWeapon(weaponType, SetCurrentWeaponReason::AddAmmo); switch (_currentWeapon) { case WeaponType::Blaster: PreloadMetadataAsync("Weapon/Blaster"_s); break; case WeaponType::Bouncer: PreloadMetadataAsync("Weapon/Bouncer"_s); break; case WeaponType::Freezer: PreloadMetadataAsync("Weapon/Freezer"_s); break; case WeaponType::Seeker: PreloadMetadataAsync("Weapon/Seeker"_s); break; case WeaponType::RF: PreloadMetadataAsync("Weapon/RF"_s); break; case WeaponType::Toaster: PreloadMetadataAsync("Weapon/Toaster"_s); break; case WeaponType::TNT: PreloadMetadataAsync("Weapon/TNT"_s); break; case WeaponType::Pepper: PreloadMetadataAsync("Weapon/Pepper"_s); break; case WeaponType::Electro: PreloadMetadataAsync("Weapon/Electro"_s); break; case WeaponType::Thunderbolt: PreloadMetadataAsync("Weapon/Thunderbolt"_s); break; } } PlayPlayerSfx("PickupAmmo"_s); return true; } void Player::AddWeaponUpgrade(WeaponType weaponType, std::uint8_t upgrade) { bool shouldSwitchToWeapon = (_weaponUpgrades[(std::int32_t)weaponType] == 0); _weaponUpgrades[(std::int32_t)weaponType] |= upgrade; if (shouldSwitchToWeapon) { SetCurrentWeapon(weaponType, SetCurrentWeaponReason::AddUpgrade); } } bool Player::AddFastFire(std::int32_t count) { const std::int32_t FastFireLimit = 9; std::int32_t current = (_weaponUpgrades[(std::int32_t)WeaponType::Blaster] >> 1); if (current >= FastFireLimit) { return false; } current = std::min(current + count, FastFireLimit); _weaponUpgrades[(std::int32_t)WeaponType::Blaster] = (std::uint8_t)((_weaponUpgrades[(std::int32_t)WeaponType::Blaster] & 0x1) | (current << 1)); PlayPlayerSfx("PickupAmmo"_s); return true; } bool Player::MorphTo(PlayerType type) { if (_playerType == type) { return false; } PlayerType playerTypePrevious = _playerType; _playerType = type; // Load new metadata switch (type) { case PlayerType::Jazz: RequestMetadata("Interactive/PlayerJazz"); break; case PlayerType::Spaz: RequestMetadata("Interactive/PlayerSpaz"); break; case PlayerType::Lori: RequestMetadata("Interactive/PlayerLori"); break; case PlayerType::Frog: RequestMetadata("Interactive/PlayerFrog"); break; } // Refresh animation state if ((_currentSpecialMove == SpecialMoveType::None) || (_currentSpecialMove == SpecialMoveType::Buttstomp && (type == PlayerType::Jazz || type == PlayerType::Spaz || type == PlayerType::Lori))) { AnimState prevAnim = _currentAnimation->State; _currentAnimation = nullptr; if (!SetAnimation(prevAnim)) { if (!SetAnimation(AnimState::Idle)) { return false; } } } else { _currentAnimation = nullptr; if (!SetAnimation(AnimState::Fall)) { return false; } SetState(ActorState::ApplyGravitation, true); _controllable = true; if (_currentSpecialMove == SpecialMoveType::Uppercut && _externalForce.Y < 0.0f) { _externalForce.Y = 0.0f; } _currentSpecialMove = SpecialMoveType::None; } // Set transition if (type == PlayerType::Frog) { PlayPlayerSfx("Transform"); _controllable = false; _controllableTimeout = 120.0f; switch (playerTypePrevious) { case PlayerType::Jazz: SetTransition(TransformFrogFromJazz, false, [this]() { _controllable = true; _controllableTimeout = 0.0f; }); break; case PlayerType::Spaz: SetTransition(TransformFrogFromSpaz, false, [this]() { _controllable = true; _controllableTimeout = 0.0f; }); break; case PlayerType::Lori: SetTransition(TransformFrogFromLori, false, [this]() { _controllable = true; _controllableTimeout = 0.0f; }); break; } } else if (playerTypePrevious == PlayerType::Frog) { _controllable = false; _controllableTimeout = 120.0f; SetTransition(AnimState::TransitionFromFrog, false, [this]() { _controllable = true; _controllableTimeout = 0.0f; }); } else { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X - 12.0f), (std::int32_t)(_pos.Y - 6.0f), _renderer.layer() + 4), Explosion::Type::SmokeBrown); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X - 8.0f), (std::int32_t)(_pos.Y + 28.0f), _renderer.layer() + 4), Explosion::Type::SmokeBrown); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + 12.0f), (std::int32_t)(_pos.Y + 10.0f), _renderer.layer() + 4), Explosion::Type::SmokeBrown); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)(_pos.Y + 12.0f), _renderer.layer() + 6), Explosion::Type::SmokeBrown); } return true; } void Player::MorphRevert() { MorphTo(_playerTypeOriginal); } bool Player::SetDizzy(float timeLeft) { bool wasNotDizzy = (_dizzyTime <= 0.0f); _dizzyTime = timeLeft; return wasNotDizzy; } bool Player::SetShield(ShieldType shieldType, float time) { bool shouldSwitchToBlaster = (shieldType != ShieldType::None && _activeShield == ShieldType::None); _activeShield = shieldType; _activeShieldTime = (shieldType != ShieldType::None ? time : 0.0f); if (shouldSwitchToBlaster) { SetCurrentWeapon(WeaponType::Blaster, SetCurrentWeaponReason::Shield); } return true; } bool Player::IncreaseShieldTime(float time) { if (_activeShieldTime <= 0.0f) { return false; } _activeShieldTime += time; PlayPlayerSfx("PickupGem"_s); return true; } bool Player::SpawnBird(std::uint8_t type, Vector2f pos) { if (_spawnedBird != nullptr) { return false; } _spawnedBird = std::make_shared(); std::uint8_t birdParams[2] = { type, (std::uint8_t)_playerIndex }; _spawnedBird->OnActivated(ActorActivationDetails( _levelHandler, Vector3i((std::int32_t)pos.X, (std::int32_t)pos.Y, _renderer.layer() + 80), birdParams )); _levelHandler->AddActor(_spawnedBird); return true; } bool Player::DisableControllable(float timeout) { if (!_controllable) { if (timeout <= 0.0f) { _controllable = true; _controllableTimeout = 0.0f; return true; } else { return false; } } if (timeout <= 0.0f) { return false; } _controllable = false; if (timeout == std::numeric_limits::infinity()) { _controllableTimeout = 0.0f; } else { _controllableTimeout = timeout; } SetAnimation(AnimState::Idle); return true; } void Player::SetCheckpoint(Vector2f pos, float ambientLight) { _checkpointPos = Vector2f(pos.X, pos.Y - 20.0f); _checkpointLight = ambientLight; _foodEatenCheckpoint = _foodEaten; _coinsCheckpoint = _coins; std::memcpy(_gemsCheckpoint, _gems, sizeof(_gems)); std::memcpy(_weaponAmmoCheckpoint, _weaponAmmo, sizeof(_weaponAmmo)); std::memcpy(_weaponUpgradesCheckpoint, _weaponUpgrades, sizeof(_weaponUpgrades)); } void Player::CancelCarryingObject(ActorBase* expectedActor) { if (expectedActor != nullptr && _carryingObject != expectedActor) { return; } _carryingObject = nullptr; if (_suspendType == SuspendType::SwingingVine) { _suspendType = SuspendType::None; SetState(ActorState::ApplyGravitation, true); _renderer.setRotation(0.0f); } } void Player::UpdateCarryingObject(ActorBase* actor, SuspendType suspendType) { DEATH_DEBUG_ASSERT(actor != nullptr); if (_carryingObject != nullptr && _carryingObject != actor) { return; } _carryingObject = actor; _canDoubleJump = true; if (suspendType == SuspendType::SwingingVine) { _suspendType = suspendType; SetState(ActorState::ApplyGravitation, false); } else if (_suspendType == SuspendType::SwingingVine) { _suspendType = SuspendType::None; SetState(ActorState::ApplyGravitation, true); _renderer.setRotation(0.0f); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Player.h000066400000000000000000000323321512772601700243760ustar00rootroot00000000000000#pragma once #include "ActorBase.h" #include "../LevelInitialization.h" #include "../ShieldType.h" #include "../SuspendType.h" #include "../WarpFlags.h" namespace Death::IO { class Stream; } #if defined(WITH_ANGELSCRIPT) namespace Jazz2::Scripting { class ScriptPlayerWrapper; } namespace Jazz2::Scripting::Legacy { class jjPLAYER; } #endif #if defined(WITH_MULTIPLAYER) namespace Jazz2::Multiplayer { class MpLevelHandler; } #endif namespace Jazz2::UI { class HUD; } using namespace Death::IO; namespace Jazz2::Actors { namespace Environment { class Bird; class SwingingVine; } namespace Solid { class PinballBumper; class PinballPaddle; } namespace Weapons { class Thunderbolt; } /** @brief Represents a controllable player */ class Player : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); friend class UI::HUD; #if defined(WITH_ANGELSCRIPT) friend class Scripting::ScriptPlayerWrapper; friend class Scripting::Legacy::jjPLAYER; #endif #if defined(WITH_MULTIPLAYER) friend class Jazz2::Multiplayer::MpLevelHandler; #endif friend class Environment::SwingingVine; friend class Solid::PinballBumper; friend class Solid::PinballPaddle; friend class Weapons::Thunderbolt; public: /** @brief Modifier */ enum class Modifier : std::uint8_t { None, Airboard, Copter, LizardCopter }; /** @brief Special move type */ enum class SpecialMoveType : std::uint8_t { None, Buttstomp, Uppercut, Sidekick }; /** @brief Type of invulnerability */ enum class InvulnerableType { Transient, Blinking, Shielded }; Player(); ~Player(); /** @brief Returns player index */ std::uint8_t GetPlayerIndex() const { return (std::uint8_t)_playerIndex; } /** @brief Returns player type */ PlayerType GetPlayerType() const { return _playerType; } /** @brief Returns current special move */ SpecialMoveType GetSpecialMove() const { return _currentSpecialMove; } /** @brief Return weapon ammo */ ArrayView GetWeaponAmmo() const { return _weaponAmmo; } /** @brief Returns weapon upgrades */ ArrayView GetWeaponUpgrades() const { return _weaponUpgrades; } /** @brief Returns `true` if sugar rush is active */ bool HasSugarRush() const { return (_sugarRushLeft > 0.0f); } /** @brief Returns `true` if the player can jump */ bool CanJump() const; /** @brief Returns `true` if the player can bread solid objects */ bool CanBreakSolidObjects() const; /** @brief Returns `true` if the player can move vertically, i.e. not affected by gravity */ bool CanMoveVertically() const; /** @brief Returns `true` if continuous jump is allowed */ virtual bool IsContinuousJumpAllowed() const; /** @brief Returns `true` if ledge climbing is allowed */ virtual bool IsLedgeClimbAllowed() const; /** @brief Called when the level is about to change */ virtual bool OnLevelChanging(Actors::ActorBase* initiator, ExitType exitType); /** @brief Called at the beginning of the next level to reveive carry over information */ virtual void ReceiveLevelCarryOver(ExitType exitType, const PlayerCarryOver& carryOver); /** @brief Returns current carry over information */ virtual PlayerCarryOver PrepareLevelCarryOver(); /** @brief Initializes player state from a stream */ void InitializeFromStream(ILevelHandler* levelHandler, Stream& src, std::uint16_t version); /** @brief Serializes player state to a stream */ void SerializeResumableToStream(Stream& dest); /** @brief Respawns the player */ virtual bool Respawn(Vector2f pos); /** @brief Warps to a given position */ virtual void WarpToPosition(Vector2f pos, WarpFlags flags); /** @brief Warps to the last checkpoint */ void WarpToCheckpoint(); /** @brief Returns current modifier */ Modifier GetModifier() const; /** @brief Sets current modifier */ virtual bool SetModifier(Modifier modifier, const std::shared_ptr& decor = nullptr); /** @brief Takes damage */ virtual bool TakeDamage(std::int32_t amount, float pushForce = 0.0f, bool ignoreInvulnerable = false); /** @brief Freezes the player for specified time */ virtual bool Freeze(float timeLeft); /** @brief Sets invulnerability */ virtual void SetInvulnerability(float timeLeft, InvulnerableType type); /** @brief Returns score */ std::int32_t GetScore() const; /** @brief Adds score */ virtual void AddScore(std::int32_t amount); /** @brief Adds health */ virtual bool AddHealth(std::int32_t amount); /** @brief Returns lives */ std::int32_t GetLives() const; /** @brief Adds lives */ virtual bool AddLives(std::int32_t count); /** @brief Returns coins */ std::int32_t GetCoins() const; /** @brief Adds coins */ void AddCoins(std::int32_t count); /** @brief Adds coins without notification (internal use only) */ void AddCoinsInternal(std::int32_t count); /** @brief Returns gems */ std::int32_t GetGems(std::uint8_t gemType) const; /** @brief Adds gems */ void AddGems(std::uint8_t gemType, std::int32_t count); /** @brief Returns food eaten */ std::int32_t GetConsumedFood() const; /** @brief Consumes food */ void ConsumeFood(bool isDrinkable); /** @brief Activates sugar rush */ void ActivateSugarRush(float duration); /** @brief Adds weapon ammo */ virtual bool AddAmmo(WeaponType weaponType, std::int16_t count); /** @brief Adds weapon upgrade */ virtual void AddWeaponUpgrade(WeaponType weaponType, std::uint8_t upgrade); /** @brief Adds fast fire */ bool AddFastFire(std::int32_t count); /** @brief Morphs to a given player type */ virtual bool MorphTo(PlayerType type); /** @brief Reverts morpth to the original player type */ void MorphRevert(); /** @brief Sets duration of dizziness */ virtual bool SetDizzy(float timeLeft); /** @brief Returns active shield */ ShieldType GetActiveShield() const { return _activeShield; } /** @brief Sets active shield */ virtual bool SetShield(ShieldType shieldType, float timeLeft); /** @brief Increases active shield time */ virtual bool IncreaseShieldTime(float timeLeft); /** @brief Spawns bird companion */ bool SpawnBird(std::uint8_t type, Vector2f pos); /** @brief Disables controls for specified time */ bool DisableControllable(float timeout); /** @brief Sets checkpoint */ void SetCheckpoint(Vector2f pos, float ambientLight); /** @brief Returns carrying object */ ActorBase* GetCarryingObject() const { return _carryingObject; } /** @brief Cancels carrying object */ void CancelCarryingObject(ActorBase* expectedActor = nullptr); /** @brief Updates carrying object */ void UpdateCarryingObject(ActorBase* actor, SuspendType suspendType = SuspendType::None); /** @brief Switches current weapon to a given index */ void SwitchToWeaponByIndex(std::uint32_t weaponIndex); /** @brief Returns weapon fire point and angle */ void GetFirePointAndAngle(Vector3i& initialPos, Vector2f& gunspotPos, float& angle); protected: /** @brief State of level exiting */ enum class LevelExitingState { None, Waiting, WaitingForWarp, Transition, Ready }; /** @brief State of HUD weapon wheel */ enum class WeaponWheelState { Hidden, Opening, Visible, Closing }; /** @brief Reason the current weapon was changed */ enum class SetCurrentWeaponReason { Unknown, /**< Unspecified */ User, /**< Set by the user */ Rollback, /**< Set due to rollback */ AddAmmo, /**< Set because an ammo for a new weapon was collected */ AddUpgrade, /**< Set because a new upgrade for a weapon was collected */ Shield /**< Set because a shield was activated */ }; /** @{ @name Constants */ static constexpr float MaxDashingSpeed = 9.0f; static constexpr float MaxRunningSpeed = 4.0f; static constexpr float MaxVineSpeed = 2.0f; static constexpr float MaxDizzySpeed = 2.4f; static constexpr float MaxShallowWaterSpeed = 3.6f; static constexpr float Acceleration = 0.2f; static constexpr float Deceleration = 0.22f; static constexpr const char* WeaponNames[(std::int32_t)WeaponType::Count] = { "Blaster", "Bouncer", "Freezer", "Seeker", "RF", "Toaster", "TNT", "Pepper", "Electro", "Thunderbolt" }; /** @} */ #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring std::int32_t _playerIndex; bool _isActivelyPushing, _wasActivelyPushing; bool _controllable; bool _controllableExternal; float _controllableTimeout; ExitType _lastExitType; bool _wasUpPressed, _wasDownPressed, _wasJumpPressed, _wasFirePressed, _isRunPressed; PlayerType _playerType, _playerTypeOriginal; SpecialMoveType _currentSpecialMove; bool _isAttachedToPole, _canPushFurther; float _copterFramesLeft, _fireFramesLeft, _pushFramesLeft, _waterCooldownLeft; LevelExitingState _levelExiting; bool _isFreefall, _inWater, _isLifting, _isSpring; std::int32_t _inShallowWater; Modifier _activeModifier; bool _inIdleTransition, _inLedgeTransition; bool _canDoubleJump; ActorBase* _carryingObject; float _externalForceCooldown; float _springCooldown; #if defined(WITH_AUDIO) std::shared_ptr _copterSound; #endif std::int32_t _lives, _coins, _coinsCheckpoint, _foodEaten, _foodEatenCheckpoint, _score; Vector2f _checkpointPos; float _checkpointLight; float _sugarRushLeft, _sugarRushStarsTime; float _shieldSpawnTime; std::int32_t _gems[4]; std::int32_t _gemsCheckpoint[4]; std::int32_t _gemsTotal[4]; std::int32_t _gemsPitch; float _gemsTimer; float _bonusWarpTimer; SuspendType _suspendType; float _suspendTime; float _invulnerableTime; float _invulnerableBlinkTime; float _jumpTime; float _idleTime; float _hitFloorTime; float _keepRunningTime; float _lastPoleTime; Vector2i _lastPolePos; float _inTubeTime; float _dizzyTime; std::shared_ptr _spawnedBird; std::shared_ptr _activeModifierDecor; SmallVector _trail; Vector2f _trailLastPos; ShieldType _activeShield; float _activeShieldTime; float _weaponFlareTime; std::int32_t _weaponFlareFrame; std::unique_ptr _weaponFlareCommand; std::unique_ptr _shieldRenderCommands[2]; float _weaponCooldown; WeaponType _currentWeapon; bool _weaponAllowed; std::uint16_t _weaponAmmo[(std::int32_t)WeaponType::Count]; std::uint16_t _weaponAmmoCheckpoint[(std::int32_t)WeaponType::Count]; std::uint8_t _weaponUpgrades[(std::int32_t)WeaponType::Count]; std::uint8_t _weaponUpgradesCheckpoint[(std::int32_t)WeaponType::Count]; WeaponWheelState _weaponWheelState; #if defined(WITH_AUDIO) std::shared_ptr _weaponSound; #endif #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnTileDeactivated() override; bool OnPerish(ActorBase* collider) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnDraw(RenderQueue& renderQueue) override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnHandleCollision(std::shared_ptr other) override; void OnHitFloor(float timeMult) override; void OnHitCeiling(float timeMult) override; void OnHitWall(float timeMult) override; /** @brief Called when a spring is hit */ virtual void OnHitSpring(Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY, bool& removeSpecialMove); /** @brief Called when water should splash */ virtual void OnWaterSplash(Vector2f pos, bool inwards); /** @brief Plays a sound effect for the player */ std::shared_ptr PlayPlayerSfx(StringView identifier, float gain = 1.0f, float pitch = 1.0f); /** @brief Starts a player animation transition */ bool SetPlayerTransition(AnimState state, bool cancellable, bool removeControl, SpecialMoveType specialMove, Function&& callback = {}); /** @brief Returns `true` if the player should freefall */ bool CanFreefall(); /** @brief Ends active damaging move */ void EndDamagingMove(); /** @brief Fires currently equipped weapon */ virtual bool FireCurrentWeapon(WeaponType weaponType); /** @brief Emits weapon flare after firing */ virtual void EmitWeaponFlare(); /** @brief Sets current weapon */ virtual void SetCurrentWeapon(WeaponType weaponType, SetCurrentWeaponReason reason); private: static constexpr float ShieldDisabled = -1000000000000.0f; void UpdateAnimation(float timeMult); void PushSolidObjects(float timeMult); void CheckEndOfSpecialMoves(float timeMult); void CheckSuspendState(float timeMult); void OnUpdatePhysics(float timeMult); void OnUpdateTimers(float timeMult); void OnHandleMovement(float timeMult, bool areaWeaponAllowed, bool canJumpPrev); void OnHandleWater(); void OnHandleAreaEvents(float timeMult, bool& areaWeaponAllowed, std::int32_t& areaWaterBlock); void DoWarpOut(Vector2f pos, WarpFlags flags); void InitialPoleStage(bool horizontal); void NextPoleStage(bool horizontal, bool positive, std::int32_t stagesLeft, float lastSpeed); void OnPerishInner(); void SwitchToNextWeapon(); template void FireWeapon(float cooldownBase, float cooldownUpgrade, bool emitFlare = false); void FireWeaponPepper(); void FireWeaponRF(); void FireWeaponTNT(); bool FireWeaponThunderbolt(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/PlayerCorpse.cpp000066400000000000000000000015221512772601700261020ustar00rootroot00000000000000#include "PlayerCorpse.h" #include "../PlayerType.h" namespace Jazz2::Actors { PlayerCorpse::PlayerCorpse() { } Task PlayerCorpse::OnActivatedAsync(const ActorActivationDetails& details) { PlayerType playerType = (PlayerType)details.Params[0]; SetFacingLeft(details.Params[1] != 0); SetState(ActorState::PreserveOnRollback, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithOtherActors, false); switch (playerType) { default: case PlayerType::Jazz: async_await RequestMetadataAsync("Interactive/PlayerJazz"_s); break; case PlayerType::Spaz: async_await RequestMetadataAsync("Interactive/PlayerSpaz"_s); break; case PlayerType::Lori: async_await RequestMetadataAsync("Interactive/PlayerLori"_s); break; } SetAnimation((AnimState)536870912); async_return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/PlayerCorpse.h000066400000000000000000000005011512772601700255430ustar00rootroot00000000000000#pragma once #include "ActorBase.h" namespace Jazz2::Actors { /** @brief Represents a dead coprse of a player */ class PlayerCorpse : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: PlayerCorpse(); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/000077500000000000000000000000001512772601700240405ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/AmmoBarrel.cpp000066400000000000000000000055671512772601700266020ustar00rootroot00000000000000#include "AmmoBarrel.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Solid { AmmoBarrel::AmmoBarrel() { } void AmmoBarrel::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/BarrelContainer"_s); } Task AmmoBarrel::OnActivatedAsync(const ActorActivationDetails& details) { Movable = true; WeaponType weaponType = (WeaponType)details.Params[0]; if (weaponType != WeaponType::Blaster) { AddContent(EventType::Ammo, 5, &details.Params[0], 1); } async_await RequestMetadataAsync("Object/BarrelContainer"_s); SetAnimation(AnimState::Idle); async_return true; } bool AmmoBarrel::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return GenericContainer::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { WeaponType weaponType = shotBase->GetWeaponType(); if (_levelHandler->IsReforged() && (weaponType == WeaponType::RF || weaponType == WeaponType::Seeker || weaponType == WeaponType::Pepper || weaponType == WeaponType::Electro)) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(INT32_MAX); } else { shotBase->TriggerRicochet(this); } return true; } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return GenericContainer::OnHandleCollision(std::move(other)); } bool AmmoBarrel::OnPerish(ActorBase* collider) { if (_content.empty()) { // Random Ammo create SmallVector weaponTypes; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { const auto playerAmmo = player->GetWeaponAmmo(); for (std::int32_t i = 1; i < (std::int32_t)WeaponType::Count; i++) { if (playerAmmo[i] > 0) { weaponTypes.push_back((WeaponType)i); } } } if (weaponTypes.empty()) { weaponTypes.push_back(WeaponType::Bouncer); } std::int32_t n = Random().Next(4, 7); for (std::int32_t i = 0; i < n; i++) { std::uint8_t weaponType = (std::uint8_t)weaponTypes[Random().Next(0, (std::uint32_t)weaponTypes.size())]; AddContent(EventType::Ammo, 1, &weaponType, sizeof(weaponType)); } } PlaySfx("Break"_s); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 3); CreateSpriteDebris((AnimState)3, 2); CreateSpriteDebris((AnimState)4, 1); return GenericContainer::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/AmmoBarrel.h000066400000000000000000000007641512772601700262410ustar00rootroot00000000000000#pragma once #include "GenericContainer.h" namespace Jazz2::Actors::Solid { /** @brief Ammo barrel */ class AmmoBarrel : public GenericContainer { DEATH_RUNTIME_OBJECT(GenericContainer); public: AmmoBarrel(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/AmmoCrate.cpp000066400000000000000000000111311512772601700264110ustar00rootroot00000000000000#include "AmmoCrate.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Solid { AmmoCrate::AmmoCrate() { } void AmmoCrate::Preload(const ActorActivationDetails& details) { WeaponType weaponType = (WeaponType)details.Params[0]; switch (weaponType) { case WeaponType::Bouncer: PreloadMetadataAsync("Object/Crate/AmmoBouncer"_s); break; case WeaponType::Freezer: PreloadMetadataAsync("Object/Crate/AmmoFreezer"_s); break; case WeaponType::Seeker: PreloadMetadataAsync("Object/Crate/AmmoSeeker"_s); break; case WeaponType::RF: PreloadMetadataAsync("Object/Crate/AmmoRF"_s); break; case WeaponType::Toaster: PreloadMetadataAsync("Object/Crate/AmmoToaster"_s); break; case WeaponType::TNT: PreloadMetadataAsync("Object/Crate/AmmoTNT"_s); break; case WeaponType::Pepper: PreloadMetadataAsync("Object/Crate/AmmoPepper"_s); break; case WeaponType::Electro: PreloadMetadataAsync("Object/Crate/AmmoElectro"_s); break; //case WeaponType::Thunderbolt: TODO default: PreloadMetadataAsync("Object/Crate/Generic"_s); break; } } Task AmmoCrate::OnActivatedAsync(const ActorActivationDetails& details) { Movable = true; WeaponType weaponType = (WeaponType)details.Params[0]; if (weaponType != WeaponType::Blaster) { AddContent(EventType::Ammo, 5, &details.Params[0], 1); } switch (weaponType) { case WeaponType::Bouncer: async_await RequestMetadataAsync("Object/Crate/AmmoBouncer"_s); break; case WeaponType::Freezer: async_await RequestMetadataAsync("Object/Crate/AmmoFreezer"_s); break; case WeaponType::Seeker: async_await RequestMetadataAsync("Object/Crate/AmmoSeeker"_s); break; case WeaponType::RF: async_await RequestMetadataAsync("Object/Crate/AmmoRF"_s); break; case WeaponType::Toaster: async_await RequestMetadataAsync("Object/Crate/AmmoToaster"_s); break; case WeaponType::TNT: async_await RequestMetadataAsync("Object/Crate/AmmoTNT"_s); break; case WeaponType::Pepper: async_await RequestMetadataAsync("Object/Crate/AmmoPepper"_s); break; case WeaponType::Electro: async_await RequestMetadataAsync("Object/Crate/AmmoElectro"_s); break; //case WeaponType::Thunderbolt: TODO default: async_await RequestMetadataAsync("Object/Crate/Generic"_s); break; } SetAnimation(AnimState::Idle); async_return true; } bool AmmoCrate::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return GenericContainer::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(1); return true; } } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return GenericContainer::OnHandleCollision(std::move(other)); } bool AmmoCrate::OnPerish(ActorBase* collider) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); PlaySfx("Break"_s); if (_content.empty()) { // Random Ammo create SmallVector weaponTypes; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { const auto playerAmmo = player->GetWeaponAmmo(); for (std::int32_t i = 1; i < (std::int32_t)WeaponType::Count; i++) { if (playerAmmo[i] > 0) { weaponTypes.push_back((WeaponType)i); } } } if (weaponTypes.empty()) { weaponTypes.push_back(WeaponType::Bouncer); } std::int32_t n = Random().Next(4, 7); for (std::int32_t i = 0; i < n; i++) { std::uint8_t weaponType = (std::uint8_t)weaponTypes[Random().Next(0, (std::uint32_t)weaponTypes.size())]; AddContent(EventType::Ammo, 1, &weaponType, sizeof(weaponType)); } CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 2); _frozenTimeLeft = std::min(1.0f, _frozenTimeLeft); SetTransition(AnimState::TransitionDeath, false, [this, collider]() { GenericContainer::OnPerish(collider); }); SpawnContent(); return true; } else { CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 2); return GenericContainer::OnPerish(collider); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/AmmoCrate.h000066400000000000000000000007611512772601700260650ustar00rootroot00000000000000#pragma once #include "GenericContainer.h" namespace Jazz2::Actors::Solid { /** @brief Ammo crate */ class AmmoCrate : public GenericContainer { DEATH_RUNTIME_OBJECT(GenericContainer); public: AmmoCrate(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/BarrelContainer.cpp000066400000000000000000000042341512772601700276210ustar00rootroot00000000000000#include "BarrelContainer.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" namespace Jazz2::Actors::Solid { BarrelContainer::BarrelContainer() { } void BarrelContainer::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/BarrelContainer"_s); } Task BarrelContainer::OnActivatedAsync(const ActorActivationDetails& details) { Movable = true; EventType eventType = (EventType)*(uint16_t*)&details.Params[0]; int count = details.Params[2]; if (eventType != EventType::Empty && count > 0) { AddContent(eventType, count, &details.Params[3], 16 - 3); } async_await RequestMetadataAsync("Object/BarrelContainer"_s); SetAnimation(AnimState::Idle); async_return true; } bool BarrelContainer::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return GenericContainer::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { WeaponType weaponType = shotBase->GetWeaponType(); if (_levelHandler->IsReforged() && (weaponType == WeaponType::RF || weaponType == WeaponType::Seeker || weaponType == WeaponType::Pepper || weaponType == WeaponType::Electro)) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(INT32_MAX); } else { shotBase->TriggerRicochet(this); } return true; } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return GenericContainer::OnHandleCollision(std::move(other)); } bool BarrelContainer::OnPerish(ActorBase* collider) { PlaySfx("Break"_s); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 3); CreateSpriteDebris((AnimState)3, 2); CreateSpriteDebris((AnimState)4, 1); return GenericContainer::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/BarrelContainer.h000066400000000000000000000010031512772601700272550ustar00rootroot00000000000000#pragma once #include "GenericContainer.h" namespace Jazz2::Actors::Solid { /** @brief Barrel container */ class BarrelContainer : public GenericContainer { DEATH_RUNTIME_OBJECT(GenericContainer); public: BarrelContainer(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/Bridge.cpp000066400000000000000000000230261512772601700257430ustar00rootroot00000000000000#include "Bridge.h" #include "../../ContentResolver.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Actors::Solid { Bridge::Bridge() : _widths(nullptr), _widthsCount(0), _widthOffset(0), _leftX(0.0f), _leftHeight(0.0f), _rightX(0.0f), _rightHeight(0.0f) { } Bridge::~Bridge() { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { player->CancelCarryingObject(this); } } void Bridge::Preload(const ActorActivationDetails& details) { BridgeType bridgeType = (BridgeType)details.Params[2]; switch (bridgeType) { default: case BridgeType::Rope: PreloadMetadataAsync("Bridge/Rope"_s); break; case BridgeType::Stone: PreloadMetadataAsync("Bridge/Stone"_s); break; case BridgeType::Vine: PreloadMetadataAsync("Bridge/Vine"_s); break; case BridgeType::StoneRed: PreloadMetadataAsync("Bridge/StoneRed"_s); break; case BridgeType::Log: PreloadMetadataAsync("Bridge/Log"_s); break; case BridgeType::Gem: PreloadMetadataAsync("Bridge/Gem"_s); break; case BridgeType::Lab: PreloadMetadataAsync("Bridge/Lab"_s); break; } } Task Bridge::OnActivatedAsync(const ActorActivationDetails& details) { _bridgeWidth = *(uint16_t*)&details.Params[0] * 16; _bridgeType = (BridgeType)details.Params[2]; // Limit _heightFactor here, because with higher _heightFactor (for example in "04_haunted1") it starts to be inaccurate _heightFactor = std::round(std::min((float)_bridgeWidth / details.Params[3], 38.0f)); _pos.Y -= 6.0f; SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); switch (_bridgeType) { default: case BridgeType::Rope: async_await RequestMetadataAsync("Bridge/Rope"_s); _widths = PieceWidthsRope; _widthsCount = std::int32_t(arraySize(PieceWidthsRope)); break; case BridgeType::Stone: async_await RequestMetadataAsync("Bridge/Stone"_s); _widths = PieceWidthsStone; _widthsCount = std::int32_t(arraySize(PieceWidthsStone)); break; case BridgeType::Vine: async_await RequestMetadataAsync("Bridge/Vine"_s); _widths = PieceWidthsVine; _widthsCount = std::int32_t(arraySize(PieceWidthsVine)); _widthOffset = 8; break; case BridgeType::StoneRed: async_await RequestMetadataAsync("Bridge/StoneRed"_s); _widths = PieceWidthsStoneRed; _widthsCount = std::int32_t(arraySize(PieceWidthsStoneRed)); break; case BridgeType::Log: async_await RequestMetadataAsync("Bridge/Log"_s); _widths = PieceWidthsLog; _widthsCount = std::int32_t(arraySize(PieceWidthsLog)); break; case BridgeType::Gem: async_await RequestMetadataAsync("Bridge/Gem"_s); _widths = PieceWidthsGem; _widthsCount = std::int32_t(arraySize(PieceWidthsGem)); break; case BridgeType::Lab: async_await RequestMetadataAsync("Bridge/Lab"_s); _widths = PieceWidthsLab; _widthsCount = std::int32_t(arraySize(PieceWidthsLab)); _widthOffset = 12; break; } SetAnimation(AnimState::Default); auto& resolver = ContentResolver::Get(); if (!resolver.IsHeadless()) { std::int32_t widthCovered = _widths[0] / 2 - _widthOffset; for (std::int32_t i = 0; widthCovered <= _bridgeWidth + 4; i++) { BridgePiece& piece = _pieces.emplace_back(); piece.Pos = Vector2f(_pos.X + widthCovered - 16, _pos.Y); piece.Command = std::make_unique(RenderCommand::Type::Sprite); piece.Command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); piece.Command->GetMaterial().SetBlendingEnabled(true); piece.Command->GetMaterial().ReserveUniformsDataMemory(); piece.Command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = piece.Command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } widthCovered += (_widths[i % _widthsCount] + _widths[(i + 1) % _widthsCount]) / 2; } } async_return true; } void Bridge::OnUpdate(float timeMult) { std::int32_t n = 0; Player* foundPlayers[8]; float foundX[8]; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { auto diff = player->GetPos() - _pos; if (n < arraySize(foundPlayers) && diff.X >= -20.0f && diff.X <= _bridgeWidth + 20.0f && diff.Y > -27.0f && diff.Y < _heightFactor && player->GetSpeed().Y >= 0.0f) { foundX[n] = diff.X; foundPlayers[n] = player; n++; } else { player->CancelCarryingObject(this); } } if (n >= 2) { float left = std::numeric_limits::max(); float right = std::numeric_limits::min(); for (std::int32_t i = 0; i < n; i++) { if (left > foundX[i]) { left = foundX[i]; } if (right < foundX[i]) { right = foundX[i]; } } _leftHeight = (std::abs(_leftX - left) < 64.0f ? lerpByTime(_leftHeight, GetSectionHeight(left), 0.1f, timeMult) : 0.0f); _rightHeight = (std::abs(_rightX - right) < 64.0f ? lerpByTime(_rightHeight, GetSectionHeight(right), 0.1f, timeMult) : 0.0f); _leftX = left; _rightX = right; for (std::int32_t i = 0; i < n; i++) { float height; if (foundX[i] <= left) { height = _leftHeight; } else if (right <= foundX[i]) { height = _rightHeight; } else { height = lerp(_leftHeight, _rightHeight, (foundX[i] - left) / (right - left)); } auto* player = foundPlayers[i]; auto playerPos = player->GetPos(); player->UpdateCarryingObject(this); player->MoveInstantly(Vector2f(playerPos.X, _pos.Y + playerPos.Y - player->AABBInner.B + BaseY + height), MoveType::Absolute | MoveType::Force); } } else if (n == 1) { _leftHeight = _rightHeight = lerpByTime(std::max(_leftHeight, _rightHeight), GetSectionHeight(foundX[0]), 0.1f, timeMult); _leftX = _rightX = foundX[0]; for (std::int32_t i = 0; i < n; i++) { auto* player = foundPlayers[i]; auto playerPos = player->GetPos(); player->UpdateCarryingObject(this); player->MoveInstantly(Vector2f(playerPos.X, _pos.Y + playerPos.Y - player->AABBInner.B + BaseY + _leftHeight), MoveType::Absolute | MoveType::Force); } } else { _leftHeight = lerpByTime(_leftHeight, 0.0f, 0.1f, timeMult); _rightHeight = lerpByTime(_rightHeight, 0.0f, 0.1f, timeMult); } auto& resolver = ContentResolver::Get(); if (!resolver.IsHeadless()) { if (_leftHeight <= 0.001f && _rightHeight <= 0.001f) { // Render straight bridge std::int32_t widthCovered = _widths[0] / 2 - _widthOffset; for (std::int32_t i = 0; widthCovered <= _bridgeWidth + 4; i++) { BridgePiece& piece = _pieces[i]; piece.Pos = Vector2f(_pos.X + widthCovered - 16, _pos.Y); widthCovered += (_widths[i % _widthsCount] + _widths[(i + 1) % _widthsCount]) / 2; } } else { // Render deformed bridge std::int32_t widthCovered = _widths[0] / 2 - _widthOffset; for (std::int32_t i = 0; widthCovered <= _bridgeWidth + 4; i++) { float drop; if (widthCovered < _leftX) { drop = _leftHeight * sinf(fPiOver2 * widthCovered / _leftX); } else if (_rightX < widthCovered) { drop = _rightHeight * sinf(fPiOver2 * (_bridgeWidth - widthCovered) / (_bridgeWidth - _rightX)); } else { drop = lerp(_leftHeight, _rightHeight, (widthCovered - _leftX) / (_rightX - _leftX)); } BridgePiece& piece = _pieces[i]; piece.Pos.Y = _pos.Y + drop; widthCovered += (_widths[i % _widthsCount] + _widths[(i + 1) % _widthsCount]) / 2; } } } } void Bridge::OnUpdateHitbox() { AABBInner = AABBf(_pos.X - 16.0f, _pos.Y + BaseY, _pos.X + _bridgeWidth + 16.0f, _pos.Y - BaseY); } bool Bridge::OnDraw(RenderQueue& renderQueue) { if (_currentAnimation != nullptr) { Vector2i texSize = _currentAnimation->Base->TextureDiffuse->GetSize(); for (std::int32_t i = 0; i < _pieces.size(); i++) { auto* command = _pieces[i].Command.get(); std::int32_t curAnimFrame = _currentAnimation->FrameOffset + (i % _currentAnimation->FrameCount); std::int32_t col = curAnimFrame % _currentAnimation->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / _currentAnimation->Base->FrameConfiguration.X; float texScaleX = (float(_currentAnimation->Base->FrameDimensions.X) / float(texSize.X)); float texBiasX = (float(_currentAnimation->Base->FrameDimensions.X * col) / float(texSize.X)); float texScaleY = (float(_currentAnimation->Base->FrameDimensions.Y) / float(texSize.Y)); float texBiasY = (float(_currentAnimation->Base->FrameDimensions.Y * row) / float(texSize.Y)); auto* instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue((float)_currentAnimation->Base->FrameDimensions.X, (float)_currentAnimation->Base->FrameDimensions.Y); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); auto pos = _pieces[i].Pos; command->SetTransformation(Matrix4x4f::Translation(pos.X - _currentAnimation->Base->FrameDimensions.X / 2, pos.Y - _currentAnimation->Base->FrameDimensions.Y / 2, 0.0f)); command->SetLayer(_renderer.layer()); command->GetMaterial().SetTexture(*_currentAnimation->Base->TextureDiffuse.get()); renderQueue.AddCommand(command); } } return true; } float Bridge::GetSectionHeight(float x) const { float fase = fPi * std::clamp(x / _bridgeWidth, 0.0f, 1.0f); return _heightFactor * sinf(fase); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/Bridge.h000066400000000000000000000032501512772601700254050ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Solid { /** @brief Bridge */ class Bridge : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: Bridge(); ~Bridge(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnDraw(RenderQueue& renderQueue) override; private: enum class BridgeType { Rope = 0, Stone = 1, Vine = 2, StoneRed = 3, Log = 4, Gem = 5, Lab = 6, Last = Lab }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct BridgePiece { Vector2f Pos; std::unique_ptr Command; }; #endif static constexpr float BaseY = -6.0f; static constexpr std::int32_t PieceWidthsRope[] = { 13, 13, 10, 13, 13, 12, 11 }; static constexpr std::int32_t PieceWidthsStone[] = { 15, 9, 10, 9, 15, 9, 15 }; static constexpr std::int32_t PieceWidthsVine[] = { 7, 7, 7, 7, 10, 7, 7, 7, 7 }; static constexpr std::int32_t PieceWidthsStoneRed[] = { 10, 11, 11, 12 }; static constexpr std::int32_t PieceWidthsLog[] = { 13, 13, 13 }; static constexpr std::int32_t PieceWidthsGem[] = { 14 }; static constexpr std::int32_t PieceWidthsLab[] = { 14 }; BridgeType _bridgeType; std::int32_t _bridgeWidth; float _heightFactor; const std::int32_t* _widths; std::int32_t _widthsCount; std::int32_t _widthOffset; float _leftX, _leftHeight, _rightX, _rightHeight; SmallVector _pieces; float GetSectionHeight(float x) const; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/CrateContainer.cpp000066400000000000000000000041341512772601700274470ustar00rootroot00000000000000#include "CrateContainer.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" namespace Jazz2::Actors::Solid { CrateContainer::CrateContainer() { } void CrateContainer::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/Crate/Generic"_s); } Task CrateContainer::OnActivatedAsync(const ActorActivationDetails& details) { Movable = true; EventType eventType = (EventType)*(uint16_t*)&details.Params[0]; std::int32_t count = details.Params[2]; if (eventType != EventType::Empty && count > 0) { AddContent(eventType, count, &details.Params[3], 16 - 3); } async_await RequestMetadataAsync("Object/Crate/Generic"_s); SetAnimation(AnimState::Idle); async_return true; } bool CrateContainer::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return GenericContainer::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(1); return true; } } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return GenericContainer::OnHandleCollision(std::move(other)); } bool CrateContainer::OnPerish(ActorBase* collider) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); PlaySfx("Break"_s); CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 2); _frozenTimeLeft = std::min(1.0f, _frozenTimeLeft); SetTransition(AnimState::TransitionDeath, false, [this, collider]() { GenericContainer::OnPerish(collider); }); SpawnContent(); return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/CrateContainer.h000066400000000000000000000010001512772601700271010ustar00rootroot00000000000000#pragma once #include "GenericContainer.h" namespace Jazz2::Actors::Solid { /** @brief Crate container */ class CrateContainer : public GenericContainer { DEATH_RUNTIME_OBJECT(GenericContainer); public: CrateContainer(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/GemBarrel.cpp000066400000000000000000000044761512772601700264170ustar00rootroot00000000000000#include "GemBarrel.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" namespace Jazz2::Actors::Solid { GemBarrel::GemBarrel() { } void GemBarrel::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/BarrelContainer"_s); PreloadMetadataAsync("Collectible/Gems"_s); } Task GemBarrel::OnActivatedAsync(const ActorActivationDetails& details) { Movable = true; std::uint8_t eventParam = 0; AddContent(EventType::Gem, details.Params[0], &eventParam, sizeof(eventParam)); eventParam = 1; AddContent(EventType::Gem, details.Params[1], &eventParam, sizeof(eventParam)); eventParam = 2; AddContent(EventType::Gem, details.Params[2], &eventParam, sizeof(eventParam)); eventParam = 3; AddContent(EventType::Gem, details.Params[3], &eventParam, sizeof(eventParam)); async_await RequestMetadataAsync("Object/BarrelContainer"_s); SetAnimation(AnimState::Idle); async_return true; } bool GemBarrel::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return GenericContainer::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { WeaponType weaponType = shotBase->GetWeaponType(); if (weaponType == WeaponType::RF || weaponType == WeaponType::Seeker || weaponType == WeaponType::Pepper || weaponType == WeaponType::Electro) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(INT32_MAX); } else { shotBase->TriggerRicochet(this); } return true; } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return GenericContainer::OnHandleCollision(std::move(other)); } bool GemBarrel::OnPerish(ActorBase* collider) { PlaySfx("Break"_s); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 3); CreateSpriteDebris((AnimState)3, 2); CreateSpriteDebris((AnimState)4, 1); return GenericContainer::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/GemBarrel.h000066400000000000000000000007611512772601700260550ustar00rootroot00000000000000#pragma once #include "GenericContainer.h" namespace Jazz2::Actors::Solid { /** @brief Gem barrel */ class GemBarrel : public GenericContainer { DEATH_RUNTIME_OBJECT(GenericContainer); public: GemBarrel(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/GemCrate.cpp000066400000000000000000000044331512772601700262370ustar00rootroot00000000000000#include "GemCrate.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" namespace Jazz2::Actors::Solid { GemCrate::GemCrate() { } void GemCrate::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/Crate/Generic"_s); PreloadMetadataAsync("Collectible/Gems"_s); } Task GemCrate::OnActivatedAsync(const ActorActivationDetails& details) { Movable = true; std::uint8_t eventParam = 0; AddContent(EventType::Gem, details.Params[0], &eventParam, sizeof(eventParam)); eventParam = 1; AddContent(EventType::Gem, details.Params[1], &eventParam, sizeof(eventParam)); eventParam = 2; AddContent(EventType::Gem, details.Params[2], &eventParam, sizeof(eventParam)); eventParam = 3; AddContent(EventType::Gem, details.Params[3], &eventParam, sizeof(eventParam)); async_await RequestMetadataAsync("Object/Crate/Generic"_s); SetAnimation(AnimState::Idle); async_return true; } bool GemCrate::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return GenericContainer::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(1); return true; } } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return GenericContainer::OnHandleCollision(std::move(other)); } bool GemCrate::OnPerish(ActorBase* collider) { SetState(ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::ApplyGravitation, false); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); PlaySfx("Break"_s); CreateSpriteDebris((AnimState)1, 3); CreateSpriteDebris((AnimState)2, 2); _frozenTimeLeft = std::min(1.0f, _frozenTimeLeft); SetTransition(AnimState::TransitionDeath, false, [this, collider]() { GenericContainer::OnPerish(collider); }); SpawnContent(); return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/GemCrate.h000066400000000000000000000007561512772601700257100ustar00rootroot00000000000000#pragma once #include "GenericContainer.h" namespace Jazz2::Actors::Solid { /** @brief Gem crate */ class GemCrate : public GenericContainer { DEATH_RUNTIME_OBJECT(GenericContainer); public: GemCrate(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/GenericContainer.cpp000066400000000000000000000056311512772601700277700ustar00rootroot00000000000000#include "GenericContainer.h" #include "../Weapons/TNT.h" #include "../../ILevelHandler.h" #include "../../Events/EventSpawner.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Solid { GenericContainer::GenericContainer() { } bool GenericContainer::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } bool GenericContainer::OnPerish(ActorBase* collider) { SpawnContent(); return SolidObjectBase::OnPerish(collider); } void GenericContainer::AddContent(EventType eventType, std::int32_t count, const std::uint8_t* eventParams, std::int32_t eventParamsSize) { if (count > 0) { _content.emplace_back(eventType, count, eventParams, eventParamsSize); } } void GenericContainer::SpawnContent() { if (_content.empty()) { return; } AABBf aabbExtended = AABB; aabbExtended.L -= 20.0f; aabbExtended.R += 20.0f; aabbExtended.T -= 20.0f; TileCollisionParams params = { TileDestructType::None, true }; auto prevState = GetState(); SetState(ActorState::CollideWithTileset | ActorState::CollideWithSolidObjects | ActorState::SkipPerPixelCollisions, true); SetState(ActorState::IsSolidObject | ActorState::CollideWithSolidObjectsBelow, false); SetState(prevState); std::int32_t maxCount = 0; for (auto& item : _content) { if (maxCount < item.Count) { maxCount = item.Count; } } bool canThrowFar = (maxCount > 1 && _levelHandler->IsPositionEmpty(this, aabbExtended, params)); for (auto& item : _content) { for (std::int32_t j = 0; j < item.Count; j++) { float x, y, fx, fy; if (item.Count > 1) { if (canThrowFar) { fx = Random().NextFloat(-6.0f, 6.0f); fy = Random().NextFloat(-0.8f, 0.0f); x = _pos.X + fx * (2.0f + _content.size() * 0.1f); y = _pos.Y + fy * (12.0f + _content.size() * 0.2f); } else { fx = Random().NextFloat(-4.0f, 4.0f); fy = Random().NextFloat(-0.4f, 0.0f); x = _pos.X + fx * (1.0f + _content.size() * 0.1f); y = _pos.Y + fy * (4.0f + _content.size() * 0.2f); } } else { fx = 0.0f; fy = 0.0f; x = _pos.X; y = _pos.Y; } std::shared_ptr actor = _levelHandler->EventSpawner()->SpawnEvent(item.Type, item.EventParams, ActorState::None, Vector3i((std::int32_t)x, (std::int32_t)y, _renderer.layer() - 10)); if (actor != nullptr) { actor->AddExternalForce(fx, fy); _levelHandler->AddActor(actor); } } } _content.clear(); } GenericContainer::ContainerContent::ContainerContent(EventType eventType, std::int32_t count, const std::uint8_t* eventParams, std::int32_t eventParamsSize) { Type = eventType; Count = count; std::memcpy(EventParams, eventParams, eventParamsSize); if (eventParamsSize < 16) { std::memset(EventParams + eventParamsSize, 0, 16 - eventParamsSize); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/GenericContainer.h000066400000000000000000000020331512772601700274260ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" #include "../../EventType.h" namespace Jazz2::Actors::Solid { /** @brief Base class of an item container */ class GenericContainer : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: GenericContainer(); bool CanCauseDamage(ActorBase* collider) override; protected: /** @brief Container content item */ struct ContainerContent { /** @brief Event type */ EventType Type; /** @brief Object count */ std::int32_t Count; /** @brief Event parameters */ std::uint8_t EventParams[16]; ContainerContent(EventType eventType, std::int32_t count, const std::uint8_t* eventParams, std::int32_t eventParamsSize); }; SmallVector _content; bool OnPerish(ActorBase* collider) override; /** @brief Adds a content to the container */ void AddContent(EventType eventType, std::int32_t count, const std::uint8_t* eventParams, std::int32_t eventParamsSize); /** @brief Spawns the added content */ void SpawnContent(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/MovingPlatform.cpp000066400000000000000000000235201512772601700275120ustar00rootroot00000000000000#include "MovingPlatform.h" #include "../../ContentResolver.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Actors::Solid { MovingPlatform::MovingPlatform() { } MovingPlatform::~MovingPlatform() { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { player->CancelCarryingObject(this); } } void MovingPlatform::Preload(const ActorActivationDetails& details) { PlatformType platformType = (PlatformType)details.Params[0]; switch (platformType) { default: case PlatformType::CarrotusFruit: PreloadMetadataAsync("MovingPlatform/CarrotusFruit"_s); break; case PlatformType::Ball: PreloadMetadataAsync("MovingPlatform/Ball"_s); break; case PlatformType::CarrotusGrass: PreloadMetadataAsync("MovingPlatform/CarrotusGrass"_s); break; case PlatformType::Lab: PreloadMetadataAsync("MovingPlatform/Lab"_s); break; case PlatformType::Sonic: PreloadMetadataAsync("MovingPlatform/Sonic"_s); break; case PlatformType::Spike: PreloadMetadataAsync("MovingPlatform/Spike"_s); break; case PlatformType::SpikeBall: PreloadMetadataAsync("MovingPlatform/SpikeBall"_s); break; } } Task MovingPlatform::OnActivatedAsync(const ActorActivationDetails& details) { _type = (PlatformType)details.Params[0]; std::uint8_t length = details.Params[3]; _speed = *(std::int8_t*)&details.Params[2] * 0.0072f; std::uint8_t sync = details.Params[1]; _isSwing = details.Params[4] != 0; _phase = sync * fPiOver2 - _speed * _levelHandler->GetElapsedFrames(); _originPos = _pos; _lastPos = _originPos; IsOneWay = true; SetState(ActorState::CollideWithTileset | ActorState::IsSolidObject | ActorState::ApplyGravitation, false); switch (_type) { default: case PlatformType::CarrotusFruit: async_await RequestMetadataAsync("MovingPlatform/CarrotusFruit"_s); break; case PlatformType::Ball: async_await RequestMetadataAsync("MovingPlatform/Ball"_s); break; case PlatformType::CarrotusGrass: async_await RequestMetadataAsync("MovingPlatform/CarrotusGrass"_s); break; case PlatformType::Lab: async_await RequestMetadataAsync("MovingPlatform/Lab"_s); break; case PlatformType::Sonic: async_await RequestMetadataAsync("MovingPlatform/Sonic"_s); break; case PlatformType::Spike: async_await RequestMetadataAsync("MovingPlatform/Spike"_s); break; case PlatformType::SpikeBall: async_await RequestMetadataAsync("MovingPlatform/SpikeBall"_s); _maxHealth = _health = 8; break; } SetAnimation((AnimState)0); auto& resolver = ContentResolver::Get(); for (std::int32_t i = 0; i < length; i++) { ChainPiece& piece = _pieces.emplace_back(); if (!resolver.IsHeadless()) { piece.Command = std::make_unique(RenderCommand::Type::Sprite); piece.Command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); piece.Command->GetMaterial().SetBlendingEnabled(true); piece.Command->GetMaterial().ReserveUniformsDataMemory(); piece.Command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = piece.Command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } async_return true; } void MovingPlatform::OnUpdate(float timeMult) { UpdateFrozenState(timeMult); _lastPos = _pos; if (_frozenTimeLeft <= 0.0f) { _phase -= _speed * timeMult; if (_speed > 0.0f) { if (_phase < -fTwoPi) { _phase += fTwoPi; } } else if (_speed < 0.0f) { if (_phase > fTwoPi) { _phase -= fTwoPi; } } MoveInstantly(GetPhasePosition((std::int32_t)_pieces.size()), MoveType::Absolute | MoveType::Force); for (int i = 0; i < _pieces.size(); i++) { _pieces[i].Pos = GetPhasePosition(i); } } Vector2f diff = _pos - _lastPos; AABBf aabb = AABBInner; aabb.T -= 4.0f; if (_type != PlatformType::SpikeBall) { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetCarryingObject() == this) { AABBf aabb3 = aabb; aabb3.T -= 4.0f; if (aabb3.Overlaps(player->AABBInner) && player->GetState(ActorState::ApplyGravitation)) { // Try to adjust Y, because it collides with carrying platform sometimes bool success = false; Vector2f playerPos = player->GetPos(); Vector2f adjustedPos = Vector2f(playerPos.X + diff.X, playerPos.Y + diff.Y); for (std::int32_t i = 0; i < 8; i++) { if (player->MoveInstantly(adjustedPos, MoveType::Absolute)) { success = true; break; } adjustedPos.Y -= 0.5f * timeMult; } if (!success) { player->MoveInstantly(Vector2f(0.0f, diff.Y), MoveType::Relative); } player->UpdateCarryingObject(this); } else { player->CancelCarryingObject(); SetState(ActorState::IsSolidObject, false); } } else if (aabb.Overlaps(player->AABBInner) && player->GetSpeed().Y >= diff.Y * timeMult && !player->CanMoveVertically()) { player->UpdateCarryingObject(this); SetState(ActorState::IsSolidObject, true); } } if (_type == PlatformType::Spike) { AABBf aabb2 = aabb; aabb2.T += 40.0f; aabb2.B += 20.0f; _levelHandler->GetCollidingPlayers(aabb2, [](Actors::ActorBase* actor) { if (auto* player = runtime_cast(actor)) { if (player->GetSpeed().Y < 0.0f) { player->TakeDamage(1, 2.0f); } } return true; }); } } else { _levelHandler->GetCollidingPlayers(aabb, [](Actors::ActorBase* actor) { if (auto* player = runtime_cast(actor)) { player->TakeDamage(1, 2.0f); } return true; }); } } bool MovingPlatform::OnHandleCollision(std::shared_ptr other) { if (_type == PlatformType::SpikeBall && _health > 0) { if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(INT32_MAX); return true; } } } return SolidObjectBase::OnHandleCollision(std::move(other)); } bool MovingPlatform::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); return SolidObjectBase::OnPerish(collider); } void MovingPlatform::OnUpdateHitbox() { SolidObjectBase::OnUpdateHitbox(); switch (_type) { case PlatformType::CarrotusFruit: AABBInner.L -= 10.0f; AABBInner.T -= 14.0f; AABBInner.R += 10.0f; AABBInner.B = AABBInner.T + 16.0f; break; case PlatformType::Lab: AABBInner.L -= 18.0f; AABBInner.T -= 2.0f; AABBInner.R += 18.0f; AABBInner.B = AABBInner.T + 16.0f; break; case PlatformType::Sonic: AABBInner.L -= 14.0f; AABBInner.T -= 16.0f; AABBInner.R += 14.0f; AABBInner.B = AABBInner.T + 16.0f; break; case PlatformType::Spike: AABBInner.L -= 16.0f; AABBInner.T -= 30.0f; AABBInner.R += 16.0f; AABBInner.B = AABBInner.T + 20.0f; break; case PlatformType::SpikeBall: AABBInner.L -= 8.0f; AABBInner.T -= 8.0f; AABBInner.R += 8.0f; AABBInner.B += 8.0f; break; default: AABBInner.L -= 8.0f; AABBInner.T -= 10.0f; AABBInner.R += 8.0f; AABBInner.B = AABBInner.T + 16.0f; break; } } bool MovingPlatform::OnDraw(RenderQueue& renderQueue) { if (!_pieces.empty()) { auto* chainAnim = _metadata->FindAnimation((AnimState)1); // Chain if (chainAnim != nullptr && chainAnim->Base->TextureDiffuse != nullptr) { Vector2i texSize = chainAnim->Base->TextureDiffuse->GetSize(); for (std::int32_t i = 0; i < (std::int32_t)_pieces.size(); i++) { auto* command = _pieces[i].Command.get(); std::int32_t curAnimFrame = chainAnim->FrameOffset + (i % chainAnim->FrameCount); std::int32_t col = curAnimFrame % chainAnim->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / chainAnim->Base->FrameConfiguration.X; float texScaleX = (float(chainAnim->Base->FrameDimensions.X) / float(texSize.X)); float texBiasX = (float(chainAnim->Base->FrameDimensions.X * col) / float(texSize.X)); float texScaleY = (float(chainAnim->Base->FrameDimensions.Y) / float(texSize.Y)); float texBiasY = (float(chainAnim->Base->FrameDimensions.Y * row) / float(texSize.Y)); auto* instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue((float)chainAnim->Base->FrameDimensions.X, (float)chainAnim->Base->FrameDimensions.Y); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); auto pos = _pieces[i].Pos; command->SetTransformation(Matrix4x4f::Translation(pos.X - chainAnim->Base->FrameDimensions.X / 2, pos.Y - chainAnim->Base->FrameDimensions.Y / 2, 0.0f)); command->SetLayer(_renderer.layer() - 2); command->GetMaterial().SetTexture(*chainAnim->Base->TextureDiffuse.get()); renderQueue.AddCommand(command); } } } return SolidObjectBase::OnDraw(renderQueue); } Vector2f MovingPlatform::GetPhasePosition(std::int32_t distance) { float effectivePhase = _phase; if (_isSwing) { effectivePhase = fPiOver2 + sinf(effectivePhase) * fPiOver2; } else if (_pieces.size() > 4 && distance > 0 && distance < _pieces.size()) { float shift = sinf(fPi * (float)(distance + 1) / _pieces.size()) * _speed * 4.0f; effectivePhase -= shift; } float multiX = cosf(effectivePhase); float multiY = sinf(effectivePhase); return Vector2f( std::round(_originPos.X + multiX * distance * 12.0f), std::round(_originPos.Y + multiY * distance * 12.0f) ); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/MovingPlatform.h000066400000000000000000000022751512772601700271630ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Solid { /** @brief Moving platform */ class MovingPlatform : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: MovingPlatform(); ~MovingPlatform(); bool OnHandleCollision(std::shared_ptr other) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; bool OnDraw(RenderQueue& renderQueue) override; private: enum class PlatformType { CarrotusFruit = 1, Ball = 2, CarrotusGrass = 3, Lab = 4, Sonic = 5, Spike = 6, SpikeBall = 7, }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct ChainPiece { Vector2f Pos; std::unique_ptr Command; }; #endif PlatformType _type; float _speed; bool _isSwing; float _phase; Vector2f _originPos, _lastPos; SmallVector _pieces; Vector2f GetPhasePosition(int distance); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PinballBumper.cpp000066400000000000000000000065061512772601700273070ustar00rootroot00000000000000#include "PinballBumper.h" #include "../../ILevelHandler.h" #include "../Player.h" namespace Jazz2::Actors::Solid { PinballBumper::PinballBumper() : _cooldown(0.0f), _lightIntensity(0.0f), _lightBrightness(0.0f) { } void PinballBumper::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/PinballBumper"_s); } Task PinballBumper::OnActivatedAsync(const ActorActivationDetails& details) { std::uint8_t theme = details.Params[0]; SetState(ActorState::CollideWithTileset | ActorState::IsSolidObject | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/PinballBumper"_s); SetAnimation((AnimState)theme); async_return true; } void PinballBumper::OnUpdate(float timeMult) { if (_frozenTimeLeft > 0.0f) { return; } if (_cooldown <= 0.0f) { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 16.0f, [this, timeMult](ActorBase* actor) { if (auto* player = runtime_cast(actor)) { _cooldown = 16.0f; SetTransition(_currentAnimation->State | (AnimState)0x200, true); PlaySfx("Hit"_s, 0.8f); constexpr float forceMult = 12.0f; Vector2f force = (player->GetPos() - _pos).Normalize() * forceMult; if (!_levelHandler->IsReforged()) { force.Y *= 0.9f; } // Move the player back player->MoveInstantly(-player->_speed * timeMult, MoveType::Relative); // Reset speed if the force acts on the other side if (force.X < 0.0f) { if (player->_speed.X > 0.0f) player->_speed.X = 0.0f; if (player->_externalForce.X > 0.0f) player->_externalForce.X = 0.0f; } else if (force.X > 0.0f) { if (player->_speed.X < 0.0f) player->_speed.X = 0.0f; if (player->_externalForce.X < 0.0f) player->_externalForce.X = 0.0f; } if (force.Y < 0.0f) { if (player->_speed.Y > 0.0f) player->_speed.Y = 0.0f; if (player->_externalForce.Y > 0.0f) player->_externalForce.Y = 0.0f; } else if (force.Y > 0.0f) { if (player->_speed.Y < 0.0f) player->_speed.Y = 0.0f; if (player->_externalForce.Y < 0.0f) player->_externalForce.Y = 0.0f; } player->_speed.X += force.X * 0.4f; player->_speed.Y += force.Y * 0.4f; if (player->_activeModifier == Player::Modifier::None) { if (player->_copterFramesLeft > 0.0f) { player->_copterFramesLeft = 1.0f; } player->_externalForce.X += force.X * 0.04f; player->_externalForce.Y += force.Y * 0.04f; player->_externalForceCooldown = 10.0f; player->_controllable = true; player->SetState(ActorState::CanJump, false); player->EndDamagingMove(); } // TODO: Check this player->AddScore(500); } return true; }); } else { _cooldown -= timeMult; } if (_lightIntensity > 0.0f) { _lightIntensity -= timeMult * 0.01f; if (_lightIntensity < 0.0f) { _lightIntensity = 0.0f; } } if (_lightBrightness > 0.0f) { _lightBrightness -= timeMult * 0.02f; if (_lightIntensity < 0.0f) { _lightIntensity = 0.0f; } } } void PinballBumper::OnEmitLights(SmallVectorImpl& lights) { if (_lightIntensity > 0.0f) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = _lightIntensity; light.Brightness = _lightBrightness; light.RadiusNear = 24.0f; light.RadiusFar = 60.0f; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PinballBumper.h000066400000000000000000000011101512772601700267360ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Solid { /** @brief Pinball bumper */ class PinballBumper : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: PinballBumper(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; private: float _cooldown; float _lightIntensity; float _lightBrightness; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PinballPaddle.cpp000066400000000000000000000050221512772601700272360ustar00rootroot00000000000000#include "PinballPaddle.h" #include "../../ILevelHandler.h" #include "../Player.h" namespace Jazz2::Actors::Solid { PinballPaddle::PinballPaddle() : _cooldown(0.0f) { } void PinballPaddle::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/PinballPaddle"_s); } Task PinballPaddle::OnActivatedAsync(const ActorActivationDetails& details) { bool facingLeft = (details.Params[0] != 0); SetState(ActorState::CollideWithTileset | ActorState::IsSolidObject | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Object/PinballPaddle"_s); SetFacingLeft(facingLeft); SetAnimation(AnimState::Idle); async_return true; } void PinballPaddle::OnUpdate(float timeMult) { if (_frozenTimeLeft > 0.0f) { return; } if (_cooldown <= 0.0f) { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 30.0f, [this](ActorBase* actor) { if (auto* player = runtime_cast(actor)) { Vector2f playerPos = player->GetPos(); if (playerPos.Y > _pos.Y - 26.0f && playerPos.Y < _pos.Y + 10.0f) { _cooldown = 10.0f; SetTransition(AnimState::TransitionActivate, false); PlaySfx("Hit"_s, 0.6f, 0.4f); float mult = (playerPos.X - _pos.X) / _currentAnimation->Base->FrameDimensions.X; if (IsFacingLeft()) { mult = 1.0f - mult; } mult = std::clamp(0.2f + mult * 1.6f, 0.4f, 1.0f); float forceY = 1.9f * mult; if (!_levelHandler->IsReforged()) { forceY *= 0.85f; } player->_speed.X = 0.0f; player->_speed.Y = (_levelHandler->IsReforged() ? -1.0f : -0.7f); if (player->_activeModifier == Player::Modifier::None) { if (player->_copterFramesLeft > 1.0f) { player->_copterFramesLeft = 1.0f; } player->_externalForce.Y -= forceY; player->_externalForceCooldown = 10.0f; player->_controllable = true; player->SetState(ActorState::CanJump, false); player->EndDamagingMove(); } // TODO: Check this player->AddScore(500); } } return true; }); } else { _cooldown -= timeMult; } } void PinballPaddle::OnUpdateHitbox() { if (_currentAnimation != nullptr) { AABBInner = AABBf( _pos.X - _currentAnimation->Base->FrameDimensions.X * (IsFacingLeft() ? 0.7f : 0.3f), _pos.Y - _currentAnimation->Base->FrameDimensions.Y * 0.1f, _pos.X + _currentAnimation->Base->FrameDimensions.X * (IsFacingLeft() ? 0.3f : 0.7f), _pos.Y + _currentAnimation->Base->FrameDimensions.Y * 0.3f ); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PinballPaddle.h000066400000000000000000000007621512772601700267110ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Solid { /** @brief Pinball paddle */ class PinballPaddle : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: PinballPaddle(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; private: float _cooldown; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/Pole.cpp000066400000000000000000000141371512772601700254510ustar00rootroot00000000000000#include "Pole.h" #include "../../ILevelHandler.h" #include "../Weapons/ShotBase.h" #include "../Weapons/FreezerShot.h" #include "../Weapons/Thunderbolt.h" #include "../Weapons/TNT.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Solid { Pole::Pole() : _fall(FallDirection::None), _angleVel(0.0f), _angleVelLast(0.0f), _fallTime(0.0f), _bouncesLeft(BouncesMax) { } void Pole::Preload(const ActorActivationDetails& details) { std::uint8_t theme = details.Params[0]; switch (theme) { default: case 0: PreloadMetadataAsync("Pole/Carrotus"_s); break; case 1: PreloadMetadataAsync("Pole/Diamondus"_s); break; case 2: PreloadMetadataAsync("Pole/DiamondusTree"_s); break; case 3: PreloadMetadataAsync("Pole/Jungle"_s); break; case 4: PreloadMetadataAsync("Pole/Psych"_s); break; } } Task Pole::OnActivatedAsync(const ActorActivationDetails& details) { std::uint8_t theme = details.Params[0]; std::int16_t x = *(std::int16_t*)&details.Params[1]; std::int16_t y = *(std::int16_t*)&details.Params[3]; _pos.X += x; _pos.Y += y; _renderer.setLayer(_renderer.layer() - 20); SetState(ActorState::CanBeFrozen | ActorState::ApplyGravitation, false); bool isSolid = true; switch (theme) { default: case 0: async_await RequestMetadataAsync("Pole/Carrotus"_s); break; case 1: async_await RequestMetadataAsync("Pole/Diamondus"_s); break; case 2: async_await RequestMetadataAsync("Pole/DiamondusTree"_s); isSolid = false; break; case 3: async_await RequestMetadataAsync("Pole/Jungle"_s); break; case 4: async_await RequestMetadataAsync("Pole/Psych"_s); break; } if (isSolid) { SetState(ActorState::IsSolidObject, true); } SetAnimation(AnimState::Default); async_return true; } void Pole::OnUpdate(float timeMult) { constexpr float FallMultiplier = 0.0036f; constexpr float Bounce = -0.2f; ActorBase::OnUpdate(timeMult); if (_fall != FallDirection::Left && _fall != FallDirection::Right) { return; } _fallTime += timeMult; if (_fall == FallDirection::Right) { if (_angleVel > 0 && IsPositionBlocked()) { if (_bouncesLeft > 0) { if (_bouncesLeft == BouncesMax) { _angleVelLast = _angleVel; PlaySfx("FallEnd"_s, 0.8f); } _bouncesLeft--; _angleVel = Bounce * _bouncesLeft * _angleVelLast; } else { _fall = FallDirection::Fallen; if (_fallTime < 10.0f) { SetState(ActorState::IsSolidObject, false); } } } else { _angleVel += FallMultiplier * timeMult; _renderer.setRotation(_renderer.rotation() + _angleVel * timeMult); SetState(ActorState::IsDirty, true); } } else if (_fall == FallDirection::Left) { if (_angleVel < 0 && IsPositionBlocked()) { if (_bouncesLeft > 0) { if (_bouncesLeft == BouncesMax) { _angleVelLast = _angleVel; PlaySfx("FallEnd"_s, 0.8f); } _bouncesLeft--; _angleVel = Bounce * _bouncesLeft * _angleVelLast; } else { _fall = FallDirection::Fallen; if (_fallTime < 10.0f) { SetState(ActorState::IsSolidObject, false); } } } else { _angleVel -= FallMultiplier * timeMult; _renderer.setRotation(_renderer.rotation() + _angleVel * timeMult); SetState(ActorState::IsDirty, true); } } } void Pole::OnPacketReceived(MemoryStream& packet) { FallDirection fall = (FallDirection)packet.ReadValue(); Fall(fall); } bool Pole::OnHandleCollision(std::shared_ptr other) { if (auto* shotBase = runtime_cast(other.get())) { if (shotBase->GetStrength() > 0) { FallDirection fallDirection; if (auto* thunderbolt = runtime_cast(shotBase)) { fallDirection = (_pos.X < shotBase->GetPos().X ? FallDirection::Left : FallDirection::Right); } else { fallDirection = (shotBase->GetSpeed().X < 0.0f ? FallDirection::Left : FallDirection::Right); } Fall(fallDirection); shotBase->DecreaseHealth(1); return true; } else if (auto* freezerShot = runtime_cast(shotBase)) { shotBase->DecreaseHealth(INT32_MAX); return true; } } else if (auto* tnt = runtime_cast(other.get())) { Fall(tnt->GetPos().X > _pos.X ? FallDirection::Left : FallDirection::Right); return true; } return false; } bool Pole::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } void Pole::Fall(FallDirection dir) { if (_fall != FallDirection::None) { return; } _fall = dir; SetState(ActorState::IsInvulnerable | ActorState::IsSolidObject, true); PlaySfx("FallStart"_s, 0.6f); SendPacket(arrayView({ (std::uint8_t)_fall })); } bool Pole::IsPositionBlocked() { constexpr float Ratio1 = 0.96f; constexpr float Ratio2 = 0.8f; constexpr float Ratio3 = 0.6f; constexpr float Ratio4 = 0.3f; float angle = _renderer.rotation() - fPiOver2; float rx = cosf(angle); float ry = sinf(angle); float radius = (float)_currentAnimation->Base->FrameDimensions.Y; TileCollisionParams params = { TileDestructType::None, true }; if (_fallTime > 20) { // Check radius 1 { float x = _pos.X + (rx * Ratio1 * radius); float y = _pos.Y + (ry * Ratio1 * radius); AABBf aabb = AABBf(x - 3, y - 3, x + 7, y + 7); if (!_levelHandler->IsPositionEmpty(this, aabb, params)) { return true; } } // Check radius 2 { float x = _pos.X + (rx * Ratio2 * radius); float y = _pos.Y + (ry * Ratio2 * radius); AABBf aabb = AABBf(x - 3, y - 3, x + 7, y + 7); if (!_levelHandler->IsPositionEmpty(this, aabb, params)) { return true; } } } // Check radius 3 { float x = _pos.X + (rx * Ratio3 * radius); float y = _pos.Y + (ry * Ratio3 * radius); AABBf aabb = AABBf(x - 3, y - 3, x + 7, y + 7); if (!_levelHandler->IsPositionEmpty(this, aabb, params)) { return true; } } // Check radius 4 { float x = _pos.X + (rx * Ratio4 * radius); float y = _pos.Y + (ry * Ratio4 * radius); AABBf aabb = AABBf(x - 3, y - 3, x + 7, y + 7); if (!_levelHandler->IsPositionEmpty(this, aabb, params)) { return true; } } return false; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/Pole.h000066400000000000000000000016701512772601700251140ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Solid { /** @brief Pole */ class Pole : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: enum class FallDirection { None, Right, Left, Fallen }; Pole(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; FallDirection GetFallDirection() const { return _fall; } static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnPacketReceived(MemoryStream& packet) override; private: static constexpr std::int32_t BouncesMax = 3; FallDirection _fall; float _angleVel; float _angleVelLast; float _fallTime; std::int32_t _bouncesLeft = BouncesMax; void Fall(FallDirection dir); bool IsPositionBlocked(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PowerUpMorphMonitor.cpp000066400000000000000000000105241512772601700305250ustar00rootroot00000000000000#include "PowerUpMorphMonitor.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Solid { PowerUpMorphMonitor::PowerUpMorphMonitor() { } void PowerUpMorphMonitor::Preload(const ActorActivationDetails& details) { MorphType morphType = (MorphType)details.Params[0]; switch (morphType) { case MorphType::Swap2: PreloadMetadataAsync("Object/PowerUp/Swap2"_s); break; case MorphType::Swap3: PreloadMetadataAsync("Object/PowerUp/Swap3"_s); break; case MorphType::ToBird: PreloadMetadataAsync("Object/PowerUp/Bird"_s); break; default: PreloadMetadataAsync("Object/PowerUp/Empty"_s); break; } } Task PowerUpMorphMonitor::OnActivatedAsync(const ActorActivationDetails& details) { _morphType = (MorphType)details.Params[0]; SetState(ActorState::CanBeFrozen, true); Movable = true; switch (_morphType) { case MorphType::Swap2: async_await RequestMetadataAsync("Object/PowerUp/Swap2"_s); break; case MorphType::Swap3: async_await RequestMetadataAsync("Object/PowerUp/Swap3"_s); break; case MorphType::ToBird: async_await RequestMetadataAsync("Object/PowerUp/Bird"_s); break; default: async_await RequestMetadataAsync("Object/PowerUp/Empty"_s); break; } SetAnimation(AnimState::Default); auto players = _levelHandler->GetPlayers(); for (auto* player : players) { std::optional playerType = GetTargetType(player->GetPlayerType()); if (playerType) { switch (*playerType) { case PlayerType::Jazz: PreloadMetadataAsync("Interactive/PlayerJazz"_s); break; case PlayerType::Spaz: PreloadMetadataAsync("Interactive/PlayerSpaz"_s); break; case PlayerType::Lori: PreloadMetadataAsync("Interactive/PlayerLori"_s); break; } } } async_return true; } void PowerUpMorphMonitor::OnUpdateHitbox() { SolidObjectBase::OnUpdateHitbox(); // Mainly to fix the power up in `tube1.j2l` AABBInner.L += 2.0f; AABBInner.R -= 2.0f; } bool PowerUpMorphMonitor::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return SolidObjectBase::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { Player* owner = shotBase->GetOwner(); WeaponType weaponType = shotBase->GetWeaponType(); if (owner != nullptr && shotBase->GetStrength() > 0) { DestroyAndApplyToPlayer(owner); shotBase->DecreaseHealth(INT32_MAX); } else if (weaponType == WeaponType::Freezer) { _frozenTimeLeft = 10.0f * FrameTimer::FramesPerSecond; shotBase->DecreaseHealth(INT32_MAX); } return true; } else if (auto* tnt = runtime_cast(other.get())) { Player* owner = tnt->GetOwner(); if (owner != nullptr) { DestroyAndApplyToPlayer(owner); } return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DestroyAndApplyToPlayer(player); return true; } } return SolidObjectBase::OnHandleCollision(std::move(other)); } bool PowerUpMorphMonitor::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } bool PowerUpMorphMonitor::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); return SolidObjectBase::OnPerish(collider); } void PowerUpMorphMonitor::DestroyAndApplyToPlayer(Player* player) { std::optional playerType = GetTargetType(player->GetPlayerType()); if (playerType) { if (!player->MorphTo(*playerType)) { player->MorphTo(PlayerType::Jazz); } DecreaseHealth(INT32_MAX, player); PlaySfx("Break"_s); } } std::optional PowerUpMorphMonitor::GetTargetType(PlayerType currentType) { switch (_morphType) { case MorphType::Swap2: if (currentType != PlayerType::Jazz) { return PlayerType::Jazz; } else { return PlayerType::Spaz; } case MorphType::Swap3: if (currentType == PlayerType::Spaz) { return PlayerType::Lori; } else if (currentType == PlayerType::Lori) { return PlayerType::Jazz; } else { return PlayerType::Spaz; } case MorphType::ToBird: // TODO: Implement Birds return std::nullopt; default: return std::nullopt; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PowerUpMorphMonitor.h000066400000000000000000000015331512772601700301720ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" #include "../../PlayerType.h" namespace Jazz2::Actors::Solid { /** @brief Power-up morph monitor */ class PowerUpMorphMonitor : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: PowerUpMorphMonitor(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; static void Preload(const ActorActivationDetails& details); void DestroyAndApplyToPlayer(Player* player); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: enum class MorphType { Swap2, Swap3, ToBird }; MorphType _morphType; std::optional GetTargetType(PlayerType currentType); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PowerUpShieldMonitor.cpp000066400000000000000000000071361512772601700306550ustar00rootroot00000000000000#include "PowerUpShieldMonitor.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Solid { PowerUpShieldMonitor::PowerUpShieldMonitor() { } void PowerUpShieldMonitor::Preload(const ActorActivationDetails& details) { ShieldType shieldType = (ShieldType)details.Params[0]; switch (shieldType) { case ShieldType::Fire: PreloadMetadataAsync("Object/PowerUp/ShieldFire"_s); PreloadMetadataAsync("Weapon/ShieldFire"_s); break; case ShieldType::Water: PreloadMetadataAsync("Object/PowerUp/ShieldWater"_s); PreloadMetadataAsync("Weapon/ShieldWater"_s); break; case ShieldType::Laser: PreloadMetadataAsync("Object/PowerUp/ShieldLaser"_s); break; case ShieldType::Lightning: PreloadMetadataAsync("Object/PowerUp/ShieldLightning"_s); PreloadMetadataAsync("Weapon/ShieldLightning"_s); break; default: PreloadMetadataAsync("Object/PowerUp/Empty"_s); break; } } Task PowerUpShieldMonitor::OnActivatedAsync(const ActorActivationDetails& details) { _shieldType = (ShieldType)details.Params[0]; SetState(ActorState::CanBeFrozen, true); Movable = true; switch (_shieldType) { case ShieldType::Fire: async_await RequestMetadataAsync("Object/PowerUp/ShieldFire"_s); break; case ShieldType::Water: async_await RequestMetadataAsync("Object/PowerUp/ShieldWater"_s); break; case ShieldType::Laser: async_await RequestMetadataAsync("Object/PowerUp/ShieldLaser"_s); break; case ShieldType::Lightning: async_await RequestMetadataAsync("Object/PowerUp/ShieldLightning"_s); break; default: async_await RequestMetadataAsync("Object/PowerUp/Empty"_s); break; } SetAnimation(AnimState::Default); async_return true; } void PowerUpShieldMonitor::OnUpdateHitbox() { SolidObjectBase::OnUpdateHitbox(); // Mainly to fix the power up in `tube1.j2l` AABBInner.L += 2.0f; AABBInner.R -= 2.0f; } bool PowerUpShieldMonitor::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return SolidObjectBase::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { Player* owner = shotBase->GetOwner(); WeaponType weaponType = shotBase->GetWeaponType(); if (owner != nullptr && shotBase->GetStrength() > 0) { DestroyAndApplyToPlayer(owner); shotBase->DecreaseHealth(INT32_MAX); } else if (weaponType == WeaponType::Freezer) { _frozenTimeLeft = 10.0f * FrameTimer::FramesPerSecond; shotBase->DecreaseHealth(INT32_MAX); } return true; } else if (auto* tnt = runtime_cast(other.get())) { Player* owner = tnt->GetOwner(); if (owner != nullptr) { DestroyAndApplyToPlayer(owner); } return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DestroyAndApplyToPlayer(player); return true; } } return SolidObjectBase::OnHandleCollision(std::move(other)); } bool PowerUpShieldMonitor::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } bool PowerUpShieldMonitor::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); return SolidObjectBase::OnPerish(collider); } void PowerUpShieldMonitor::DestroyAndApplyToPlayer(Player* player) { if (player->SetShield(_shieldType, 30.0f * FrameTimer::FramesPerSecond)) { PlaySfx("Break"_s); DecreaseHealth(INT32_MAX, player); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PowerUpShieldMonitor.h000066400000000000000000000013371512772601700303170ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" #include "../../ShieldType.h" namespace Jazz2::Actors::Solid { /** @brief Power-up shield monitor */ class PowerUpShieldMonitor : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: PowerUpShieldMonitor(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; static void Preload(const ActorActivationDetails& details); void DestroyAndApplyToPlayer(Player* player); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: ShieldType _shieldType; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PowerUpWeaponMonitor.cpp000066400000000000000000000112431512772601700306700ustar00rootroot00000000000000#include "PowerUpWeaponMonitor.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" #include "../../../nCine/Base/Random.h" namespace Jazz2::Actors::Solid { PowerUpWeaponMonitor::PowerUpWeaponMonitor() { } void PowerUpWeaponMonitor::Preload(const ActorActivationDetails& details) { WeaponType weaponType = (WeaponType)details.Params[0]; switch (weaponType) { case WeaponType::Blaster: PreloadMetadataAsync("Object/PowerUp/Blaster"_s); break; case WeaponType::Bouncer: PreloadMetadataAsync("Object/PowerUp/Bouncer"_s); break; case WeaponType::Freezer: PreloadMetadataAsync("Object/PowerUp/Freezer"_s); break; case WeaponType::Seeker: PreloadMetadataAsync("Object/PowerUp/Seeker"_s); break; case WeaponType::RF: PreloadMetadataAsync("Object/PowerUp/RF"_s); break; case WeaponType::Toaster: PreloadMetadataAsync("Object/PowerUp/Toaster"_s); break; //case WeaponType::TNT: TODO case WeaponType::Pepper: PreloadMetadataAsync("Object/PowerUp/Pepper"_s); break; case WeaponType::Electro: PreloadMetadataAsync("Object/PowerUp/Electro"_s); break; //case WeaponType::Thunderbolt: TODO default: PreloadMetadataAsync("Object/PowerUp/Empty"_s); break; } } Task PowerUpWeaponMonitor::OnActivatedAsync(const ActorActivationDetails& details) { _weaponType = (WeaponType)details.Params[0]; SetState(ActorState::CanBeFrozen, true); Movable = true; switch (_weaponType) { case WeaponType::Blaster: async_await RequestMetadataAsync("Object/PowerUp/Blaster"_s); break; case WeaponType::Bouncer: async_await RequestMetadataAsync("Object/PowerUp/Bouncer"_s); break; case WeaponType::Freezer: async_await RequestMetadataAsync("Object/PowerUp/Freezer"_s); break; case WeaponType::Seeker: async_await RequestMetadataAsync("Object/PowerUp/Seeker"_s); break; case WeaponType::RF: async_await RequestMetadataAsync("Object/PowerUp/RF"_s); break; case WeaponType::Toaster: async_await RequestMetadataAsync("Object/PowerUp/Toaster"_s); break; //case WeaponType::TNT: TODO case WeaponType::Pepper: async_await RequestMetadataAsync("Object/PowerUp/Pepper"_s); break; case WeaponType::Electro: async_await RequestMetadataAsync("Object/PowerUp/Electro"_s); break; //case WeaponType::Thunderbolt: TODO default: async_await RequestMetadataAsync("Object/PowerUp/Empty"_s); break; } if (_weaponType == WeaponType::Blaster) { auto players = _levelHandler->GetPlayers(); PlayerType playerType = (_levelHandler->IsLocalSession() && !players.empty() ? players[0]->GetPlayerType() : PlayerType::Jazz); switch (playerType) { case PlayerType::Jazz: case PlayerType::Spaz: case PlayerType::Lori: SetAnimation((AnimState)playerType); break; default: SetAnimation((AnimState)PlayerType::Jazz); break; } } else { SetAnimation(AnimState::Default); } async_return true; } void PowerUpWeaponMonitor::OnUpdateHitbox() { SolidObjectBase::OnUpdateHitbox(); // Mainly to fix the power up in `tube1.j2l` AABBInner.L += 2.0f; AABBInner.R -= 2.0f; } bool PowerUpWeaponMonitor::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return SolidObjectBase::OnHandleCollision(other); } if (auto* shotBase = runtime_cast(other.get())) { Player* owner = shotBase->GetOwner(); WeaponType weaponType = shotBase->GetWeaponType(); if (owner != nullptr && shotBase->GetStrength() > 0) { DestroyAndApplyToPlayer(owner); shotBase->DecreaseHealth(INT32_MAX); } else if (weaponType == WeaponType::Freezer) { _frozenTimeLeft = 10.0f * FrameTimer::FramesPerSecond; shotBase->DecreaseHealth(INT32_MAX); } return true; } else if (auto* tnt = runtime_cast(other.get())) { Player* owner = tnt->GetOwner(); if (owner != nullptr) { DestroyAndApplyToPlayer(owner); } return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DestroyAndApplyToPlayer(player); return true; } } return SolidObjectBase::OnHandleCollision(std::move(other)); } bool PowerUpWeaponMonitor::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } bool PowerUpWeaponMonitor::OnPerish(ActorBase* collider) { CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); return SolidObjectBase::OnPerish(collider); } void PowerUpWeaponMonitor::DestroyAndApplyToPlayer(Player* player) { player->AddAmmo(_weaponType, 25); player->AddWeaponUpgrade(_weaponType, 0x01); DecreaseHealth(INT32_MAX, player); PlaySfx("Break"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PowerUpWeaponMonitor.h000066400000000000000000000013011512772601700303270ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Solid { /** @brief Power-up weapon monitor */ class PowerUpWeaponMonitor : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: PowerUpWeaponMonitor(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; static void Preload(const ActorActivationDetails& details); void DestroyAndApplyToPlayer(Player* player); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; private: WeaponType _weaponType; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PushableBox.cpp000066400000000000000000000036131512772601700267630ustar00rootroot00000000000000#include "PushableBox.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../../../nCine/Base/FrameTimer.h" namespace Jazz2::Actors::Solid { PushableBox::PushableBox() { } void PushableBox::Preload(const ActorActivationDetails& details) { uint8_t theme = details.Params[0]; switch (theme) { default: case 0: PreloadMetadataAsync("Object/PushBoxRock"_s); break; case 1: PreloadMetadataAsync("Object/PushBoxCrate"_s); break; } } Task PushableBox::OnActivatedAsync(const ActorActivationDetails& details) { std::uint8_t theme = details.Params[0]; SetState(ActorState::CanBeFrozen, true); Movable = true; switch (theme) { default: case 0: async_await RequestMetadataAsync("Object/PushBoxRock"); break; case 1: async_await RequestMetadataAsync("Object/PushBoxCrate"); break; } SetAnimation(AnimState::Default); async_return true; } bool PushableBox::OnHandleCollision(std::shared_ptr other) { if (auto* shotBase = runtime_cast(other.get())) { WeaponType weaponType = shotBase->GetWeaponType(); if (weaponType == WeaponType::Blaster || weaponType == WeaponType::RF || weaponType == WeaponType::Seeker || weaponType == WeaponType::Pepper) { shotBase->DecreaseHealth(INT32_MAX); } else if (weaponType == WeaponType::Freezer) { _frozenTimeLeft = 10.0f * FrameTimer::FramesPerSecond; shotBase->DecreaseHealth(INT32_MAX); } else if (weaponType == WeaponType::Toaster) { _frozenTimeLeft = std::min(1.0f, _frozenTimeLeft); shotBase->DecreaseHealth(INT32_MAX); } else if (weaponType != WeaponType::Electro) { shotBase->TriggerRicochet(this); } return true; } return SolidObjectBase::OnHandleCollision(other); } float PushableBox::GetIceShrapnelScale() const { return 1.4f; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/PushableBox.h000066400000000000000000000007661512772601700264360ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Solid { /** @brief Pushable box */ class PushableBox : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: PushableBox(); static void Preload(const ActorActivationDetails& details); bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; float GetIceShrapnelScale() const override; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/SpikeBall.cpp000066400000000000000000000127061512772601700264200ustar00rootroot00000000000000#include "SpikeBall.h" #include "../../ContentResolver.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Actors::Solid { SpikeBall::SpikeBall() { } void SpikeBall::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("MovingPlatform/SpikeBall"_s); } Task SpikeBall::OnActivatedAsync(const ActorActivationDetails& details) { std::uint8_t length = details.Params[2]; _speed = *(std::int8_t*)&details.Params[1] * 0.0072f; std::uint8_t sync = details.Params[0]; _isSwing = details.Params[3] != 0; _phase = sync * fPiOver2 - _speed * _levelHandler->GetElapsedFrames(); _shade = details.Params[4] != 0; _originPos = _pos; _originLayer = _renderer.layer() - 12; SetState(ActorState::IsInvulnerable, true); SetState(ActorState::CanBeFrozen | ActorState::CollideWithTileset | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("MovingPlatform/SpikeBall"_s); SetAnimation((AnimState)0); auto& resolver = ContentResolver::Get(); for (std::int32_t i = 0; i < length; i++) { ChainPiece& piece = _pieces.emplace_back(); if (!resolver.IsHeadless()) { piece.Command = std::make_unique(RenderCommand::Type::Sprite); piece.Command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); piece.Command->GetMaterial().SetBlendingEnabled(true); piece.Command->GetMaterial().ReserveUniformsDataMemory(); piece.Command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = piece.Command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } async_return true; } void SpikeBall::OnUpdate(float timeMult) { _phase -= _speed * timeMult; if (_speed > 0.0f) { if (_phase < -fTwoPi) { _phase += fTwoPi; } } else if (_speed < 0.0f) { if (_phase > fTwoPi) { _phase -= fTwoPi; } } float scale = 1.0f; MoveInstantly(GetPhasePosition((std::int32_t)_pieces.size(), &scale), MoveType::Absolute | MoveType::Force); for (std::int32_t i = 0; i < _pieces.size(); i++) { _pieces[i].Pos = GetPhasePosition(i, &_pieces[i].Scale); } _canHurtPlayer = (std::abs(1.0f - scale) < 0.06f); _renderer.setScale(scale); _renderer.setLayer(_originLayer + (std::uint16_t)(scale * 20)); if (_shade) { _renderer.setColor(scale < 1.0f ? Colorf(scale, scale, scale, 1.0f) : Colorf::White); } } void SpikeBall::OnUpdateHitbox() { EnemyBase::OnUpdateHitbox(); AABBInner.L += 8.0f; AABBInner.T += 8.0f; AABBInner.R -= 8.0f; AABBInner.B -= 8.0f; } bool SpikeBall::OnDraw(RenderQueue& renderQueue) { if (!_pieces.empty()) { auto* chainAnim = _metadata->FindAnimation((AnimState)1); if (chainAnim != nullptr && chainAnim->Base->TextureDiffuse != nullptr) { Vector2i texSize = chainAnim->Base->TextureDiffuse->GetSize(); for (std::int32_t i = 0; i < _pieces.size(); i++) { auto command = _pieces[i].Command.get(); float scale = _pieces[i].Scale; std::int32_t curAnimFrame = chainAnim->FrameOffset + (i % chainAnim->FrameCount); std::int32_t col = curAnimFrame % chainAnim->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / chainAnim->Base->FrameConfiguration.X; float texScaleX = (float(chainAnim->Base->FrameDimensions.X) / float(texSize.X)); float texBiasX = (float(chainAnim->Base->FrameDimensions.X * col) / float(texSize.X)); float texScaleY = (float(chainAnim->Base->FrameDimensions.Y) / float(texSize.Y)); float texBiasY = (float(chainAnim->Base->FrameDimensions.Y * row) / float(texSize.Y)); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue((float)chainAnim->Base->FrameDimensions.X, (float)chainAnim->Base->FrameDimensions.Y); if (_shade) { instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector((scale < 1.0f ? Colorf(scale, scale, scale, 1.0f) : Colorf::White).Data()); } else { instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); } auto& pos = _pieces[i].Pos; command->SetTransformation(Matrix4x4f::Translation(pos.X - chainAnim->Base->FrameDimensions.X / 2, pos.Y - chainAnim->Base->FrameDimensions.Y / 2, 0.0f)); command->SetLayer(_originLayer + (uint16_t)(scale * 20)); command->GetMaterial().SetTexture(*chainAnim->Base->TextureDiffuse.get()); renderQueue.AddCommand(command); } } } return EnemyBase::OnDraw(renderQueue); } Vector2f SpikeBall::GetPhasePosition(std::int32_t distance, float* scale) { float effectivePhase = _phase; if (_isSwing) { effectivePhase = fPiOver2 + sinf(effectivePhase) * fPiOver2; } else if (_pieces.size() > 4 && distance > 0 && distance < _pieces.size()) { float shift = sinf(fPi * (float)(distance + 1) / _pieces.size()) * _speed * 4.0f; effectivePhase -= shift; } float multiX = cosf(effectivePhase); float multiY = sinf(effectivePhase); if (scale != nullptr) { *scale = 1.0f + multiX * 0.4f * distance / _pieces.size(); } return Vector2f( std::round(_originPos.X), std::round(_originPos.Y + multiY * distance * 12.0f) ); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/SpikeBall.h000066400000000000000000000017121512772601700260600ustar00rootroot00000000000000#pragma once #include "../Enemies/EnemyBase.h" namespace Jazz2::Actors::Solid { /** @brief Spike ball */ class SpikeBall : public Enemies::EnemyBase { DEATH_RUNTIME_OBJECT(Enemies::EnemyBase); public: SpikeBall(); static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnDraw(RenderQueue& renderQueue) override; private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct ChainPiece { Vector2f Pos; float Scale; std::unique_ptr Command; }; #endif bool _shade; float _speed; bool _isSwing; float _phase; Vector2f _originPos; std::uint16_t _originLayer; SmallVector _pieces; Vector2f GetPhasePosition(std::int32_t distance, float* scale); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/TriggerCrate.cpp000066400000000000000000000050401512772601700271250ustar00rootroot00000000000000#include "TriggerCrate.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Explosion.h" #include "../Player.h" #include "../Weapons/ShotBase.h" #include "../Weapons/TNT.h" namespace Jazz2::Actors::Solid { TriggerCrate::TriggerCrate() { } void TriggerCrate::Preload(const ActorActivationDetails& details) { PreloadMetadataAsync("Object/TriggerCrate"_s); } Task TriggerCrate::OnActivatedAsync(const ActorActivationDetails& details) { _triggerId = details.Params[0]; if (details.Params[2] != 0) { _newState = TriggerCrateState::Toggle; } else { _newState = (details.Params[1] != 0 ? TriggerCrateState::On : TriggerCrateState::Off); } Movable = true; async_await RequestMetadataAsync("Object/TriggerCrate"_s); SetAnimation(AnimState::Default); async_return true; } bool TriggerCrate::OnHandleCollision(std::shared_ptr other) { if (_health == 0) { return SolidObjectBase::OnHandleCollision(std::move(other)); } if (auto* shotBase = runtime_cast(other.get())) { WeaponType weaponType = shotBase->GetWeaponType(); if (_levelHandler->IsReforged() && (weaponType == WeaponType::RF || weaponType == WeaponType::Seeker || weaponType == WeaponType::Pepper || weaponType == WeaponType::Electro)) { DecreaseHealth(shotBase->GetStrength(), shotBase); shotBase->DecreaseHealth(INT32_MAX); } else { shotBase->TriggerRicochet(this); } return true; } else if (auto* tnt = runtime_cast(other.get())) { DecreaseHealth(INT32_MAX, tnt); return true; } else if (auto* player = runtime_cast(other.get())) { if (player->CanBreakSolidObjects()) { DecreaseHealth(INT32_MAX, player); return true; } } return SolidObjectBase::OnHandleCollision(std::move(other)); } bool TriggerCrate::CanCauseDamage(ActorBase* collider) { return _levelHandler->IsReforged() || runtime_cast(collider); } bool TriggerCrate::OnPerish(ActorBase* collider) { if (_newState == TriggerCrateState::Toggle) { // Toggle _levelHandler->SetTrigger(_triggerId, !_levelHandler->GetTrigger(_triggerId)); } else { // Turn off/on _levelHandler->SetTrigger(_triggerId, _newState == TriggerCrateState::On); } PlaySfx("Break"_s); CreateParticleDebrisOnPerish(ParticleDebrisEffect::Standard, Vector2f::Zero); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::SmokeBrown); return SolidObjectBase::OnPerish(collider); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Solid/TriggerCrate.h000066400000000000000000000013311512772601700265710ustar00rootroot00000000000000#pragma once #include "../SolidObjectBase.h" namespace Jazz2::Actors::Solid { /** @brief State of @ref TriggerCrate */ enum class TriggerCrateState { Off, On, Toggle }; /** @brief Trigger crate */ class TriggerCrate : public SolidObjectBase { DEATH_RUNTIME_OBJECT(SolidObjectBase); public: TriggerCrate(); bool OnHandleCollision(std::shared_ptr other) override; bool CanCauseDamage(ActorBase* collider) override; static void Preload(const ActorActivationDetails& details); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; bool OnPerish(ActorBase* collider) override; private: TriggerCrateState _newState; std::uint8_t _triggerId; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/SolidObjectBase.cpp000066400000000000000000000065231512772601700264740ustar00rootroot00000000000000#include "SolidObjectBase.h" #include "../ILevelHandler.h" #include "Player.h" using namespace Jazz2::Tiles; using namespace nCine; namespace Jazz2::Actors { SolidObjectBase::SolidObjectBase() : IsOneWay(false), Movable(false), _pushingSpeedX(0.0f), _pushingTime(0.0f) { SetState(ActorState::CollideWithSolidObjects | ActorState::CollideWithSolidObjectsBelow | ActorState::IsSolidObject | ActorState::SkipPerPixelCollisions, true); } SolidObjectBase::~SolidObjectBase() { auto players = _levelHandler->GetPlayers(); for (auto* player : players) { player->CancelCarryingObject(this); } } float SolidObjectBase::Push(bool left, float timeMult) { if (Movable) { if (_pushingTime > 0.0f) { _pushingTime = PushDecayTime; return _pushingSpeedX; } float speedX = (left ? -PushSpeed : PushSpeed); if (TryPushInternal(timeMult, speedX)) { _pushingSpeedX = speedX; _pushingTime = PushDecayTime; return speedX; } } return 0.0f; } void SolidObjectBase::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); Vector2f lastPos = _pos; if (_pushingTime > 0.0f) { if (_frozenTimeLeft <= 0.0f) { _pushingTime -= timeMult; } if (!TryPushInternal(timeMult, _pushingSpeedX)) { _pushingTime = 0.0f; } } if (_pushingTime > 0.0f) { Vector2f diff = _pos - lastPos; AABBf aabb = AABBInner; aabb.T -= 1.0f; auto players = _levelHandler->GetPlayers(); for (auto* player : players) { if (player->GetCarryingObject() == this) { AABBf aabb3 = aabb; aabb3.T -= 1.0f; if (aabb3.Overlaps(player->AABBInner) && player->GetState(ActorState::ApplyGravitation)) { // Try to adjust Y, because it collides with the box sometimes bool success = false; Vector2f playerPos = player->GetPos(); Vector2f adjustedPos = Vector2f(playerPos.X + diff.X, playerPos.Y + diff.Y); for (std::int32_t i = 0; i < 8; i++) { if (player->MoveInstantly(adjustedPos, MoveType::Absolute)) { success = true; break; } adjustedPos.Y -= 0.5f * timeMult; } if (!success) { player->MoveInstantly(Vector2f(0.0f, diff.Y), MoveType::Relative); } player->UpdateCarryingObject(this); } else { player->CancelCarryingObject(); } } else if (aabb.Overlaps(player->AABBInner) && player->GetSpeed().Y >= diff.Y * timeMult && !player->CanMoveVertically()) { player->UpdateCarryingObject(this); } } } else { // If it's not frozen, reset carrying state auto players = _levelHandler->GetPlayers(); for (auto* player : players) { player->CancelCarryingObject(this); } } } bool SolidObjectBase::TryPushInternal(float timeMult, float speedX) { float farX = (speedX < 0.0f ? AABBInner.L : AABBInner.R); float farY = (AABBInner.T + AABBInner.B) * 0.5f; TileCollisionParams params = { TileDestructType::None, _speed.Y >= 0.0f }; ActorState prevState = GetState(); SetState(ActorState::CollideWithSolidObjectsBelow, false); bool success = _levelHandler->IsPositionEmpty(this, AABBf(farX - 1.0f, farY - 1.0f, farX + 1.0f, farY + 1.0f), params); SetState(prevState); if (success) { for (std::int32_t i = 0; i >= -2; i -= 2) { if (MoveInstantly(Vector2f(speedX * timeMult, i * timeMult), MoveType::Relative, params)) { return true; } } } return false; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/SolidObjectBase.h000066400000000000000000000011741512772601700261360ustar00rootroot00000000000000#pragma once #include "ActorBase.h" namespace Jazz2::Actors { /** @brief Base class of a (pushable) solid object */ class SolidObjectBase : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: SolidObjectBase(); ~SolidObjectBase(); bool IsOneWay; bool Movable; float Push(bool left, float timeMult); protected: /** @{ @name Constants */ static constexpr float PushSpeed = 0.5f; static constexpr float PushDecayTime = 6.0f; /** @} */ void OnUpdate(float timeMult) override; private: float _pushingSpeedX; float _pushingTime; bool TryPushInternal(float timeMult, float speedX); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/000077500000000000000000000000001512772601700244025ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/BlasterShot.cpp000066400000000000000000000067541512772601700273540ustar00rootroot00000000000000#include "BlasterShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { BlasterShot::BlasterShot() : _fired(0) { } Task BlasterShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/Blaster"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x01) != 0) { _timeLeft = 25; state |= (AnimState)1; _strength = 2; } else { _timeLeft = 22; _strength = 1; } SetAnimation(state); _renderer.setBlendingPreset(DrawableNode::BlendingPreset::ADDITIVE); async_return true; } void BlasterShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); constexpr float baseSpeed = 12.0f; if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setDrawEnabled(false); } void BlasterShot::OnUpdate(float timeMult) { int n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::Blaster, _strength }; for (int i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); if (_timeLeft <= 0.0f) { PlaySfx("WallPoof"_s); } _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } } void BlasterShot::OnUpdateHitbox() { AABBInner = AABBf(_pos.X - 3, _pos.Y - 2, _pos.X + 3, _pos.Y + 4); } void BlasterShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.Brightness = 0.6f; light.RadiusNear = 5.0f; light.RadiusFar = 20.0f; } } bool BlasterShot::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Small); return ShotBase::OnPerish(collider); } void BlasterShot::OnHitWall(float timeMult) { auto events = _levelHandler->EventMap(); if (events != nullptr) { std::uint8_t* eventParams; switch (events->GetEventByPosition(_pos.X + _speed.X, _pos.Y + _speed.Y, &eventParams)) { case EventType::ModifierRicochet: TriggerRicochet(nullptr); return; } } DecreaseHealth(INT32_MAX); PlaySfx("WallPoof"_s); } void BlasterShot::OnRicochet() { ShotBase::OnRicochet(); _renderer.setRotation(atan2f(_speed.Y, _speed.X)); PlaySfx("Ricochet"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/BlasterShot.h000066400000000000000000000015521512772601700270100ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Blaster (shot) */ class BlasterShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: BlasterShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Blaster; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/BouncerShot.cpp000066400000000000000000000067551512772601700273560ustar00rootroot00000000000000#include "BouncerShot.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { BouncerShot::BouncerShot() : _fired(0), _hitLimit(0.0f), _targetSpeedX(0.0f) { } Task BouncerShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; async_await RequestMetadataAsync("Weapon/Bouncer"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x1) != 0) { _timeLeft = 130; _strength = 2; state |= (AnimState)1; PlaySfx("FireUpgraded"_s, 1.0f, 0.5f); } else { _timeLeft = 90; _strength = 1; PlaySfx("Fire"_s, 1.0f, 0.5f); } SetAnimation(state); async_return true; } void BouncerShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); constexpr float baseSpeed = 10.0f; if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _elasticity = 0.9f; _renderer.setDrawEnabled(false); } void BouncerShot::OnUpdate(float timeMult) { TileCollisionParams params = { TileDestructType::Weapon, _speed.Y >= 0.0f, WeaponType::Bouncer, _strength }; TryStandardMovement(timeMult, params); if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); if (_hitLimit > 0.0f) { _hitLimit -= timeMult; } if ((_upgrades & 0x1) != 0 && _targetSpeedX != 0.0f) { if (_speed.X != _targetSpeedX) { float step = timeMult * 0.2f; if (std::abs(_speed.X - _targetSpeedX) < step) { _speed.X = _targetSpeedX; } else { _speed.X += step * ((_targetSpeedX < _speed.X) ? -1 : 1); } } } _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute); _renderer.setDrawEnabled(true); } } void BouncerShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.Brightness = 0.2f; light.RadiusNear = 0.0f; light.RadiusFar = 12.0f; } } bool BouncerShot::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::SmallDark); return ShotBase::OnPerish(collider); } void BouncerShot::OnHitWall(float timeMult) { if (_hitLimit > 3.0f) { DecreaseHealth(INT32_MAX); return; } _hitLimit += 2.0f; PlaySfx("Bounce"_s, 0.5f); } void BouncerShot::OnHitFloor(float timeMult) { if (_hitLimit > 3.0f) { DecreaseHealth(INT32_MAX); return; } _hitLimit += 2.0f; PlaySfx("Bounce"_s, 0.5f); } void BouncerShot::OnHitCeiling(float timeMult) { if (_hitLimit > 3.0f) { DecreaseHealth(INT32_MAX); return; } _hitLimit += 2.0f; PlaySfx("Bounce"_s, 0.5f); } void BouncerShot::OnRicochet() { _speed.X = -_speed.X; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/BouncerShot.h000066400000000000000000000017141512772601700270110ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Bouncer (shot) */ class BouncerShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: BouncerShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Bouncer; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnHitFloor(float timeMult) override; void OnHitCeiling(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; float _targetSpeedX; float _hitLimit; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ElectroShot.cpp000066400000000000000000000115251512772601700273450ustar00rootroot00000000000000#include "ElectroShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../../Tiles/TileMap.h" #include "../Enemies/EnemyBase.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { ElectroShot::ElectroShot() : _fired(0), _currentStep(0.0f), _particleSpawnTime(0.0f) { } Task ElectroShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; _strength = 4; _timeLeft = 55; SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/Electro"_s); SetAnimation(AnimState::Idle); PlaySfx("Fire"_s); _renderer.setDrawEnabled(false); async_return true; } void ElectroShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); float baseSpeed = ((_upgrades & 0x1) != 0 ? 5.0f : 4.0f); if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; } void ElectroShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon | TileDestructType::IgnoreSolidTiles, false, WeaponType::Electro, _strength }; for (std::int32_t i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); } else if (_fired > 2) { _particleSpawnTime -= timeMult; if (_particleSpawnTime <= 0.0f) { _particleSpawnTime += 1.0f; auto tilemap = _levelHandler->TileMap(); if (tilemap != nullptr) { auto* res = _metadata->FindAnimation((AnimState)1); // Particle if (res != nullptr && res->Base->TextureDiffuse != nullptr) { auto& resBase = res->Base; Vector2i texSize = resBase->TextureDiffuse->GetSize(); for (int i = 0; i < 6; i++) { float angle = (_currentStep * 0.3f + i * 0.6f); if (IsFacingLeft()) { angle = -angle; } float size = (8.0f + _currentStep * 0.2f); float dist = (2.0f + _currentStep * 0.01f); float dx = dist * cosf(angle); float dy = dist * sinf(angle); Tiles::TileMap::DestructibleDebris debris = {}; debris.Pos = Vector2f(_pos.X + dx, _pos.Y + dy); debris.Depth = _renderer.layer(); debris.Size = Vector2f(size, size); debris.Scale = 1.0f; debris.ScaleSpeed = -0.1f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.1f; debris.Angle = angle; debris.Time = 60.0f; std::int32_t curAnimFrame = ((_upgrades & 0x1) != 0 ? 2 : 0) + Random().Fast(0, 2); std::int32_t col = curAnimFrame % resBase->FrameConfiguration.X; std::int32_t row = curAnimFrame / resBase->FrameConfiguration.X; debris.TexScaleX = (float(resBase->FrameDimensions.X) / float(texSize.X)); debris.TexBiasX = (float(resBase->FrameDimensions.X * col) / float(texSize.X)); debris.TexScaleY = (float(resBase->FrameDimensions.Y) / float(texSize.Y)); debris.TexBiasY = (float(resBase->FrameDimensions.Y * row) / float(texSize.Y)); debris.DiffuseTexture = resBase->TextureDiffuse.get(); tilemap->CreateDebris(debris); } } } } _currentStep += timeMult; } } void ElectroShot::OnUpdateHitbox() { UpdateHitbox(4, 4); } void ElectroShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.4f + 0.016f * _currentStep; light.Brightness = 0.2f + 0.02f * _currentStep; light.RadiusNear = 0.0f; light.RadiusFar = 12.0f + 0.4f * _currentStep; } } bool ElectroShot::OnHandleCollision(std::shared_ptr other) { if (auto* enemyBase = runtime_cast(other.get())) { if (enemyBase->IsInvulnerable() || !enemyBase->CanCollideWithShots) { return false; } } return ShotBase::OnHandleCollision(std::move(other)); } bool ElectroShot::OnPerish(ActorBase* collider) { return ShotBase::OnPerish(collider); } void ElectroShot::OnHitWall(float timeMult) { } void ElectroShot::OnRicochet() { } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ElectroShot.h000066400000000000000000000017421512772601700270120ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Electro (shot) */ class ElectroShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: ElectroShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Electro; } bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; float _currentStep; float _particleSpawnTime; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/FreezerShot.cpp000066400000000000000000000111371512772601700273510ustar00rootroot00000000000000#include "FreezerShot.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Explosion.h" #include "../Solid/TriggerCrate.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { FreezerShot::FreezerShot() : _fired(0), _particlesTime(1.0f) { } Task FreezerShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); _strength = 0; async_await RequestMetadataAsync("Weapon/Freezer"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x01) != 0) { _timeLeft = 38; state |= (AnimState)1; PlaySfx("FireUpgraded"_s); // TODO: Add better upgraded effect _renderer.setScale(1.2f); } else { _timeLeft = 44; PlaySfx("Fire"_s); } SetAnimation(state); async_return true; } void FreezerShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); float baseSpeed = ((_upgrades & 0x01) != 0 ? 8.0f : 6.0f); if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setDrawEnabled(false); } void FreezerShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, _speed.Y >= 0.0f, WeaponType::Freezer, _strength }; for (std::int32_t i = 0; i < n; i++) { TryMovement(timeMult / n, params); } ShotBase::OnUpdate(timeMult); // TODO: Add particles if (_timeLeft <= 0.0f) { PlaySfx("WallPoof"_s); } _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } _particlesTime -= timeMult; if (_particlesTime <= 0.0f) { _particlesTime += 1.0f; auto tileMap = _levelHandler->TileMap(); auto resBase = _currentAnimation->Base; if (tileMap != nullptr && _pos.Y < _levelHandler->GetWaterLevel() && resBase->TextureDiffuse != nullptr) { Vector2i texSize = resBase->TextureDiffuse->GetSize(); float dx = Random().FastFloat(-8.0f, 8.0f); float dy = Random().FastFloat(-3.0f, 3.0f); constexpr float currentSize = 1.0f; Tiles::TileMap::DestructibleDebris debris = { }; debris.Pos = Vector2f(_pos.X + dx, _pos.Y + dy); debris.Depth = _renderer.layer(); debris.Size = Vector2f(currentSize, currentSize); debris.Acceleration = Vector2f(0.0f, _levelHandler->GetGravity()); debris.Scale = 1.2f, debris.Alpha = 1.0f; debris.Time = 300.0f; debris.TexScaleX = (currentSize / float(texSize.X)); debris.TexBiasX = (((float)(_renderer.CurrentFrame % resBase->FrameConfiguration.X) / resBase->FrameConfiguration.X) + (((resBase->FrameDimensions.X * 0.5f) + dx) / float(texSize.X))); debris.TexScaleY = (currentSize / float(texSize.Y)); debris.TexBiasY = (((float)(_renderer.CurrentFrame / resBase->FrameConfiguration.X) / resBase->FrameConfiguration.Y) + (((resBase->FrameDimensions.Y * 0.5f) + dy) / float(texSize.Y))); debris.DiffuseTexture = resBase->TextureDiffuse.get(); debris.Flags = Tiles::TileMap::DebrisFlags::Disappear; tileMap->CreateDebris(debris); } } } void FreezerShot::OnUpdateHitbox() { // TODO: This is a quick fix for player cannot freeze springs AABBInner = AABBf( _pos.X - 4, _pos.Y - 2, _pos.X + 4, _pos.Y + 6 ); } void FreezerShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.Brightness = 0.2f; light.RadiusNear = 0.0f; light.RadiusFar = 20.0f; } } bool FreezerShot::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::SmokeWhite); return ShotBase::OnPerish(collider); } void FreezerShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); PlaySfx("WallPoof"_s); } void FreezerShot::OnRicochet() { DecreaseHealth(INT32_MAX); PlaySfx("WallPoof"_s); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/FreezerShot.h000066400000000000000000000020201512772601700270050ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Freezer (shot) */ class FreezerShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: FreezerShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Freezer; } /** @brief Returns duration of freeze effect */ float FrozenDuration() const { return ((_upgrades & 0x1) != 0 ? 280.0f : 180.0f); } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; float _particlesTime; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/PepperShot.cpp000066400000000000000000000061041512772601700272000ustar00rootroot00000000000000#include "PepperShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Explosion.h" #include "../Player.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { PepperShot::PepperShot() : _fired(0) { } Task PepperShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; _strength = 1; SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/Pepper"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x01) != 0) { _timeLeft = Random().NextFloat(32.0f, 40.0f); state |= (AnimState)1; } else { _timeLeft = Random().NextFloat(26.0f, 36.0f); } SetAnimation(state); PlaySfx("Fire"_s); _renderer.setBlendingPreset(DrawableNode::BlendingPreset::ADDITIVE); async_return true; } void PepperShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); float baseSpeed = ((_upgrades & 0x1) != 0 ? Random().NextFloat(5.0f, 7.2f) : Random().NextFloat(3.0f, 7.0f)); if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setDrawEnabled(false); } void PepperShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::Pepper, _strength }; for (std::int32_t i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } } void PepperShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2 && (_upgrades & 0x1) != 0) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 1.0f; light.Brightness = 1.0f; light.RadiusNear = 4.0f; light.RadiusFar = 16.0f; } } bool PepperShot::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Pepper); return ShotBase::OnPerish(collider); } void PepperShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void PepperShot::OnRicochet() { ShotBase::OnRicochet(); _renderer.setRotation(atan2f(_speed.Y, _speed.X)); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/PepperShot.h000066400000000000000000000015041512772601700266440ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Pepper (shot) */ class PepperShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: PepperShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Pepper; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/RFShot.cpp000066400000000000000000000067051512772601700262630ustar00rootroot00000000000000#include "RFShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../Explosion.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { RFShot::RFShot() : _fired(0), _smokeTimer(3.0f) { } Task RFShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; _strength = 2; SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/RF"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x1) != 0) { _timeLeft = 24; state |= (AnimState)1; } else { _timeLeft = 30; } SetAnimation(state); PlaySfx("Fire"_s, 0.4f); async_return true; } void RFShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); float baseSpeed = ((_upgrades & 0x1) != 0 ? 6.4f : 6.0f); if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setDrawEnabled(false); } void RFShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::RF, _strength }; for (std::int32_t i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); if (_smokeTimer > 0.0f) { _smokeTimer -= timeMult; } else { Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer() + 2), Explosion::Type::TinyBlue); _smokeTimer = 6.0f; } _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } } void RFShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light1 = lights.emplace_back(); light1.Pos = _pos; light1.Intensity = 0.4f; light1.Brightness = 0.2f; light1.RadiusNear = 0.0f; light1.RadiusFar = 60.0f; auto& light2 = lights.emplace_back(); light2.Pos = _pos; light2.Intensity = 0.8f; light2.Brightness = 0.8f; light2.RadiusNear = 3.0f; light2.RadiusFar = 14.0f; } } bool RFShot::OnPerish(ActorBase* collider) { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 36.0f, [this](ActorBase* actor) { if (auto* player = runtime_cast(actor)) { bool pushLeft = (_pos.X > player->GetPos().X); player->AddExternalForce(pushLeft ? -4.0f : 4.0f, 0.0f); } return true; }); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), (_upgrades & 0x1) != 0 ? Explosion::Type::RFUpgraded : Explosion::Type::RF); PlaySfx("Explode"_s, 0.6f); return ShotBase::OnPerish(collider); } void RFShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void RFShot::OnRicochet() { } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/RFShot.h000066400000000000000000000015111512772601700257160ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief RF (shot) */ class RFShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: RFShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::RF; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; float _smokeTimer; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/SeekerShot.cpp000066400000000000000000000104521512772601700271640ustar00rootroot00000000000000#include "SeekerShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../Explosion.h" #include "../Enemies/EnemyBase.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" #include using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { SeekerShot::SeekerShot() : _fired(0), _followRecomputeTime(0.0f) { } Task SeekerShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/Seeker"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x1) != 0) { _timeLeft = 188.0f; _defaultRecomputeTime = 6.0f; _strength = 3; state |= (AnimState)1; } else { _timeLeft = 144.0f; _defaultRecomputeTime = 10.0f; _strength = 2; } SetAnimation(state); PlaySfx("Fire"_s); async_return true; } void SeekerShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); // Upgraded rockets are slower float baseSpeed = ((_upgrades & 0x1) != 0 ? 1.7f : 1.85f); if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X * 0.06f) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X * 0.06f) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setDrawEnabled(false); } void SeekerShot::OnUpdate(float timeMult) { TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::Seeker, _strength }; TryMovement(timeMult, params); if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } FollowNeareastEnemy(timeMult); _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } ShotBase::OnUpdate(timeMult); } void SeekerShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.RadiusNear = 3.0f; light.RadiusFar = 10.0f; } } bool SeekerShot::OnPerish(ActorBase* collider) { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 36.0f, [this](ActorBase* actor) { if (auto* player = runtime_cast(actor)) { bool pushLeft = (_pos.X > player->GetPos().X); player->AddExternalForce(pushLeft ? -8.0f : 8.0f, 0.0f); } return true; }); Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Large); return ShotBase::OnPerish(collider); } void SeekerShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } void SeekerShot::OnRicochet() { } void SeekerShot::FollowNeareastEnemy(float timeMult) { if (_followRecomputeTime > 0.0f) { _followRecomputeTime -= timeMult; return; } Vector2f targetPos = Vector2f(FLT_MAX, FLT_MAX); float targetDistance = FLT_MAX; // Max. distance is ~8 tiles _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 260.0f, [this, &targetPos, &targetDistance](ActorBase* actor) { if (actor->CanCauseDamage(this)) { Vector2f newPos = actor->GetPos(); float distance = (_pos - newPos).Length(); if (distance < 260.0f && distance < targetDistance) { targetPos = newPos; targetDistance = distance; } } return true; }); if (targetDistance < 260.0f) { Vector2f speed = (Vector2f(_speed.X, _speed.Y) + (targetPos - _pos).Normalized() * 2.0f).Normalized(); _speed.X = speed.X * 2.6f; _speed.Y = speed.Y * 2.6f; } if (_speed.X < 0.0f) { SetFacingLeft(true); _renderer.setRotation(atan2f(-_speed.Y, -_speed.X)); } else { SetFacingLeft(false); _renderer.setRotation(atan2f(_speed.Y, _speed.X)); } _followRecomputeTime = _defaultRecomputeTime; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/SeekerShot.h000066400000000000000000000016561512772601700266370ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Seeker (shot) */ class SeekerShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: SeekerShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Seeker; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; float _defaultRecomputeTime; float _followRecomputeTime; void FollowNeareastEnemy(float timeMult); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShieldFireShot.cpp000066400000000000000000000056531512772601700277730ustar00rootroot00000000000000#include "ShieldFireShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { ShieldFireShot::ShieldFireShot() : _fired(0) { } Task ShieldFireShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/ShieldFire"_s); _timeLeft = 30; _strength = 1; SetAnimation(AnimState::Idle); _renderer.setBlendingPreset(DrawableNode::BlendingPreset::ADDITIVE); async_return true; } void ShieldFireShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); constexpr float baseSpeed = 8.0f; if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setAlphaF(0.8f); _renderer.setDrawEnabled(false); PlaySfx("Fire"_s, 0.8f); } void ShieldFireShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::Blaster, _strength }; for (std::int32_t i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } } void ShieldFireShot::OnUpdateHitbox() { AABBInner = AABBf(_pos.X - 3, _pos.Y - 2, _pos.X + 3, _pos.Y + 4); } void ShieldFireShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.8f; light.Brightness = 0.6f; light.RadiusNear = 0.0f; light.RadiusFar = 50.0f; } } bool ShieldFireShot::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::SmokeGray); return ShotBase::OnPerish(collider); } void ShieldFireShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShieldFireShot.h000066400000000000000000000015261512772601700274330ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Fire shield (shot) */ class ShieldFireShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: ShieldFireShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Toaster; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; private: Vector2f _gunspotPos; std::int32_t _fired; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShieldLightningShot.cpp000066400000000000000000000064001512772601700310200ustar00rootroot00000000000000#include "ShieldLightningShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { ShieldLightningShot::ShieldLightningShot() : _fired(0) { } ShieldLightningShot::~ShieldLightningShot() { #if defined(WITH_AUDIO) if (_noise != nullptr) { _noise->stop(); _noise = nullptr; } #endif } Task ShieldLightningShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/ShieldLightning"_s); _timeLeft = 30; _strength = 2; SetAnimation(AnimState::Idle); _renderer.setBlendingPreset(DrawableNode::BlendingPreset::ADDITIVE); async_return true; } void ShieldLightningShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = angle * (isFacingLeft ? -1 : 1); constexpr float baseSpeed = 7.0f; if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setRotation(angle); _renderer.setDrawEnabled(false); #if defined(WITH_AUDIO) _noise = PlaySfx("Fire"_s, 0.5f, 6.0f); #endif } void ShieldLightningShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::Blaster, _strength }; for (std::int32_t i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } #if defined(WITH_AUDIO) if (_noise != nullptr) { _noise->setPosition(Vector3f(_pos.X, _pos.Y, 0.8f)); } #endif } void ShieldLightningShot::OnUpdateHitbox() { AABBInner = AABBf(_pos.X - 3, _pos.Y - 2, _pos.X + 3, _pos.Y + 4); } void ShieldLightningShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = 0.2f; light.Brightness = 0.2f; light.RadiusNear = 5.0f; light.RadiusFar = 40.0f; } } bool ShieldLightningShot::OnPerish(ActorBase* collider) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Small); return ShotBase::OnPerish(collider); } void ShieldLightningShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShieldLightningShot.h000066400000000000000000000017131512772601700304670ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Lightning shield (shot) */ class ShieldLightningShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: ShieldLightningShot(); ~ShieldLightningShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Blaster; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; private: Vector2f _gunspotPos; std::int32_t _fired; #if defined(WITH_AUDIO) std::shared_ptr _noise; #endif }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShieldWaterShot.cpp000066400000000000000000000052701512772601700301630ustar00rootroot00000000000000#include "ShieldWaterShot.h" #include "../../ILevelHandler.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../Explosion.h" #include "../../../nCine/Base/FrameTimer.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/CommonConstants.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { ShieldWaterShot::ShieldWaterShot() : _fired(0) { } Task ShieldWaterShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); SetState(ActorState::SkipPerPixelCollisions, true); SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/ShieldWater"_s); _timeLeft = 35; _strength = 2; SetAnimation(AnimState::Idle); _renderer.setBlendingPreset(DrawableNode::BlendingPreset::ADDITIVE); async_return true; } void ShieldWaterShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float angleRel = (angle + Random().NextFloat(-0.2f, 0.2f)) * (isFacingLeft ? -1 : 1); constexpr float baseSpeed = 7.0f; if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - cosf(angleRel) * baseSpeed; } else { _speed.X = std::max(0.0f, speed.X) + cosf(angleRel) * baseSpeed; } _speed.Y = sinf(angleRel) * baseSpeed; _renderer.setAlphaF(0.7f); _renderer.setDrawEnabled(false); PlaySfx("Fire"_s); } void ShieldWaterShot::OnUpdate(float timeMult) { std::int32_t n = (timeMult > 0.9f ? 2 : 1); TileCollisionParams params = { TileDestructType::Weapon, false, WeaponType::Blaster, _strength }; for (std::int32_t i = 0; i < n && params.WeaponStrength > 0; i++) { TryMovement(timeMult / n, params); } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { DecreaseHealth(INT32_MAX); return; } ShotBase::OnUpdate(timeMult); _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setDrawEnabled(true); } } void ShieldWaterShot::OnUpdateHitbox() { AABBInner = AABBf(_pos.X - 3, _pos.Y - 2, _pos.X + 3, _pos.Y + 4); } bool ShieldWaterShot::OnPerish(ActorBase* collider) { if (_timeLeft > 0.0f) { Explosion::Create(_levelHandler, Vector3i((std::int32_t)(_pos.X + _speed.X), (std::int32_t)(_pos.Y + _speed.Y), _renderer.layer() + 2), Explosion::Type::Small); } return ShotBase::OnPerish(collider); } void ShieldWaterShot::OnHitWall(float timeMult) { DecreaseHealth(INT32_MAX); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShieldWaterShot.h000066400000000000000000000014241512772601700276250ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Water shield (shot) */ class ShieldWaterShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: ShieldWaterShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Blaster; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnPerish(ActorBase* collider) override; void OnHitWall(float timeMult) override; private: Vector2f _gunspotPos; std::int32_t _fired; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShotBase.cpp000066400000000000000000000046231512772601700266230ustar00rootroot00000000000000#include "ShotBase.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../Events/EventMap.h" #include "../Player.h" #include "../SolidObjectBase.h" #include "../Enemies/EnemyBase.h" #include "../../../nCine/Base/Random.h" #include "../../../nCine/Application.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { ShotBase::ShotBase() : _timeLeft(0), _upgrades(0), _strength(0), _lastRicochet(nullptr) { } Task ShotBase::OnActivatedAsync(const ActorActivationDetails& details) { SetState(ActorState::CanBeFrozen, false); async_return true; } Player* ShotBase::GetOwner() { return runtime_cast(_owner.get()); } WeaponType ShotBase::GetWeaponType() { return WeaponType::Unknown; } void ShotBase::TriggerRicochet(ActorBase* other) { TimeStamp now = TimeStamp::now(); if (other == nullptr) { if ((now - _lastRicochetTime).seconds() > 1.0f) { _lastRicochet = nullptr; _lastRicochetTime = now; OnRicochet(); } } else { if (_lastRicochet != other) { _lastRicochet = other; _lastRicochetTime = now; OnRicochet(); } else if ((now - _lastRicochetTime).seconds() < 1.0f) { DecreaseHealth(INT32_MAX); } } } void ShotBase::OnUpdate(float timeMult) { _timeLeft -= timeMult; if (_timeLeft <= 0.0f) { DecreaseHealth(INT32_MAX); } } bool ShotBase::OnHandleCollision(std::shared_ptr other) { if (auto* enemyBase = runtime_cast(other.get())) { if (enemyBase->CanCollideWithShots) { DecreaseHealth(INT32_MAX); } } return false; } void ShotBase::OnRicochet() { MoveInstantly(Vector2f(_speed.X * -0.4f, _speed.Y * -0.4f), MoveType::Relative | MoveType::Force); _speed.Y = _speed.Y * -0.9f + Random().NextFloat(-2.0f, 2.0f); _speed.X = _speed.X * -0.9f + Random().NextFloat(-2.0f, 2.0f); } void ShotBase::TryMovement(float timeMult, TileCollisionParams& params) { float accelY = (_internalForceY + _externalForce.Y) * timeMult; _speed.X = std::clamp(_speed.X, -16.0f, 16.0f); _speed.Y = std::clamp(_speed.Y + accelY, -16.0f, 16.0f); float effectiveSpeedX = _speed.X + _externalForce.X * timeMult; float effectiveSpeedY = _speed.Y + 0.5f * accelY; effectiveSpeedX *= timeMult; effectiveSpeedY *= timeMult; if (!MoveInstantly(Vector2f(effectiveSpeedX, effectiveSpeedY), MoveType::Relative, params)) { OnHitWall(timeMult); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ShotBase.h000066400000000000000000000024401512772601700262630ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" #include "../../WeaponType.h" #include "../../../nCine/Base/TimeStamp.h" namespace Jazz2::Actors { class Player; } namespace Jazz2::Actors::Weapons { /** @brief Base class of a shot from a player's weapon */ class ShotBase : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: ShotBase(); bool OnHandleCollision(std::shared_ptr other) override; /** @brief Returns strength (damage) */ inline std::int32_t GetStrength() { return _strength; } /** @brief Returns owner of the shot */ Player* GetOwner(); /** @brief Returns weapon type */ virtual WeaponType GetWeaponType(); /** @brief Triggers shot ricochet */ void TriggerRicochet(ActorBase* other); protected: #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring std::shared_ptr _owner; float _timeLeft; std::uint8_t _upgrades; std::int32_t _strength; ActorBase* _lastRicochet; #endif Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; /** @brief Called on shot ricochet */ virtual void OnRicochet(); void TryMovement(float timeMult, Tiles::TileCollisionParams& params); private: TimeStamp _lastRicochetTime; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/TNT.cpp000066400000000000000000000076631512772601700255670ustar00rootroot00000000000000#include "TNT.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../Player.h" #include "../Explosion.h" #include "../Enemies/EnemyBase.h" #include "../Solid/GenericContainer.h" #include "../Solid/PowerUpMorphMonitor.h" #include "../Solid/PowerUpShieldMonitor.h" #include "../Solid/PowerUpWeaponMonitor.h" #include "../Solid/TriggerCrate.h" #include "../Environment/BirdCage.h" #include "../Solid/Pole.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { TNT::TNT() : _timeLeft(0.0f), _lightIntensity(0.0f), _isExploded(false) { } Task TNT::OnActivatedAsync(const ActorActivationDetails& details) { _timeLeft = 200.0f; _preexplosionTime = (int)_timeLeft / 16; SetState(ActorState::CollideWithTileset | ActorState::CollideWithOtherActors | ActorState::CollideWithSolidObjects | ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/TNT"_s); SetAnimation(AnimState::Idle); auto* tiles = _levelHandler->TileMap(); if (tiles != nullptr) { AABBf aabb = AABBf(_pos.X - 34.0f, _pos.Y - 34.0f, _pos.X + 34.0f, _pos.Y + 34.0f); TileCollisionParams params = { TileDestructType::Special | TileDestructType::Weapon | TileDestructType::IgnoreSolidTiles, false, WeaponType::TNT, 8 }; if (tiles->CanBeDestroyed(aabb, params)) { _timeLeft = 40.0f; } } async_return true; } bool TNT::OnHandleCollision(std::shared_ptr other) { if (auto* tnt = runtime_cast(other.get())) { if (tnt->_isExploded && _timeLeft > 35.0f) { _timeLeft = 35.0f; } } return ActorBase::OnHandleCollision(std::move(other)); } Player* TNT::GetOwner() { return runtime_cast(_owner.get()); } void TNT::OnFire(const std::shared_ptr& owner) { _owner = owner; } void TNT::OnUpdate(float timeMult) { ActorBase::OnUpdate(timeMult); if (_timeLeft > 0.0f) { _timeLeft -= timeMult; if (_timeLeft > 35.0f) { _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 64.0f, [this](ActorBase* actor) { if (actor->CanCauseDamage(this)) { _timeLeft = 35.0f; return false; } return true; }); } else if (_timeLeft < 30.0f) { std::int32_t fraction = (std::int32_t)_timeLeft / 16; if (_preexplosionTime != fraction) { _preexplosionTime = fraction; _renderer.setScale(5.0f); if (_noise == nullptr) { _noise = PlaySfx(Random().NextBool() ? "Bell1"_s : "Bell2"_s); } else if (!_noise->isPlaying()) { _noise->play(); } } _renderer.setScale(_renderer.scale() - timeMult * 0.36f); } } else if (!_isExploded) { _isExploded = true; _lightIntensity = 0.8f; SetTransition(AnimState::TransitionActivate, false, [this]() { DecreaseHealth(INT32_MAX); }); _renderer.setScale(1.0f); PlaySfx("Explosion"_s); _levelHandler->FindCollisionActorsByRadius(_pos.X, _pos.Y, 96.0f, [this](ActorBase* actor) { actor->OnHandleCollision(shared_from_this()); return true; }); auto* tiles = _levelHandler->TileMap(); if (tiles != nullptr) { AABBf aabb = AABBf(_pos.X - 72.0f, _pos.Y - 72.0f, _pos.X + 72.0f, _pos.Y + 72.0f); TileCollisionParams params = { TileDestructType::Special | TileDestructType::Weapon | TileDestructType::IgnoreSolidTiles, false, WeaponType::TNT, 8 }; tiles->IsTileEmpty(aabb, params); if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } } } else { _lightIntensity -= timeMult * 0.02f; _renderer.setScale(_renderer.scale() + timeMult * 0.01f); } } void TNT::OnEmitLights(SmallVectorImpl& lights) { if (_lightIntensity > 0.0f) { auto& light = lights.emplace_back(); light.Pos = _pos; light.Intensity = _lightIntensity; light.Brightness = _lightIntensity * 0.6f; light.RadiusNear = 5.0f; light.RadiusFar = 120.0f; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/TNT.h000066400000000000000000000014671512772601700252300ustar00rootroot00000000000000#pragma once #include "../ActorBase.h" namespace Jazz2::Actors::Weapons { /** @brief TNT */ class TNT : public ActorBase { DEATH_RUNTIME_OBJECT(ActorBase); public: TNT(); bool OnHandleCollision(std::shared_ptr other) override; /** @brief Returns owner of the TNT */ Player* GetOwner(); /** @brief Called when the TNT is deployed */ void OnFire(const std::shared_ptr& owner); protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnEmitLights(SmallVectorImpl& lights) override; private: std::shared_ptr _owner; float _timeLeft; float _lightIntensity; bool _isExploded; std::int32_t _preexplosionTime; std::shared_ptr _noise; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/Thunderbolt.cpp000066400000000000000000000107721512772601700274070ustar00rootroot00000000000000#include "Thunderbolt.h" #include "../../ILevelHandler.h" #include "../Player.h" #include "../Enemies/EnemyBase.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { Thunderbolt::Thunderbolt() : _hit(false), _lightProgress(0.0f), _firedUp(false) { } Task Thunderbolt::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _upgrades = details.Params[0]; _initialLayer = _renderer.layer(); _strength = 2; _health = INT32_MAX; SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/Thunderbolt"_s); SetAnimation((AnimState)(Random().NextBool() ? 1 : 0)); async_return true; } void Thunderbolt::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { angle += Random().FastFloat(-0.16f, 0.16f); float distance = (isFacingLeft ? -140.0f : 140.0f); _farPoint = Vector2f(gunspotPos.X + cosf(angle) * distance, gunspotPos.Y + sinf(angle) * distance); _owner = owner; SetFacingLeft(isFacingLeft); MoveInstantly(gunspotPos, MoveType::Absolute | MoveType::Force); OnUpdateHitbox(); if (Random().NextBool()) { _renderer.setFlippedY(true); } _renderer.setRotation(angle); if (auto* player = runtime_cast(owner.get())) { _firedUp = player->_wasUpPressed; } } void Thunderbolt::OnUpdate(float timeMult) { if (auto* player = runtime_cast(_owner.get())) { if (_firedUp != player->_wasUpPressed || IsFacingLeft() != player->IsFacingLeft()) { _hit = true; _strength = 0; DecreaseHealth(INT32_MAX); return; } if (!_firedUp) { Vector3i initialPos; Vector2f gunspotPos; float angle; player->GetFirePointAndAngle(initialPos, gunspotPos, angle); float scale = 1.0f - std::abs(gunspotPos.X - _pos.X) / _currentAnimation->Base->FrameDimensions.X; MoveInstantly(gunspotPos, MoveType::Absolute | MoveType::Force); _renderer.setScale(Vector2f(scale, 1.0f)); float anglePrev = _renderer.rotation(); if (IsFacingLeft()) { angle = atan2f(_pos.Y - _farPoint.Y, _pos.X - _farPoint.X); } else { angle = atan2f(_farPoint.Y - _pos.Y, _farPoint.X - _pos.X); } angle = lerp(anglePrev, angle, 0.4f * timeMult); if (std::abs(anglePrev - angle) > 0.06f) { _renderer.setRotation(angle); } else { angle = _renderer.rotation(); } float distance = (IsFacingLeft() ? -140.0f : 140.0f); _farPoint = Vector2f(gunspotPos.X + cosf(angle) * distance, gunspotPos.Y + sinf(angle) * distance); } } if (_hit) { _strength = 0; } else if (_strength > 0) { TileCollisionParams params = { TileDestructType::Weapon | TileDestructType::IgnoreSolidTiles, false, WeaponType::Thunderbolt, _strength }; _levelHandler->IsPositionEmpty(this, AABBInner, params); if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } if (params.WeaponStrength <= 0) { _hit = true; _strength = 0; } } _lightProgress += timeMult * 0.123f; _renderer.setLayer((uint16_t)(_initialLayer - _lightProgress * 10.0f)); } void Thunderbolt::OnUpdateHitbox() { constexpr float Size = 10.0f; if (_farPoint.X != 0.0f && _farPoint.Y != 0.0f) { AABBInner = AABBf(_pos, _farPoint); AABBInner.L -= Size; AABBInner.T -= Size; AABBInner.R += Size; AABBInner.B += Size; } } void Thunderbolt::OnEmitLights(SmallVectorImpl& lights) { constexpr std::int32_t LightCount = 4; if (_lightProgress < fPi) { float lightIntensity = sinf(_lightProgress) * 0.2f; for (std::int32_t i = -1; i <= LightCount; i++) { float dist = (float)i / LightCount; auto& light = lights.emplace_back(); light.Pos = Vector2f(lerp(_pos.X, _farPoint.X, dist), lerp(_pos.Y, _farPoint.Y, dist)); light.Intensity = lightIntensity; light.Brightness = lightIntensity * (0.1f + (1.0f - dist) * 0.4f); light.RadiusNear = 20.0f; light.RadiusFar = 100.0f; } } } void Thunderbolt::OnAnimationFinished() { ShotBase::OnAnimationFinished(); DecreaseHealth(INT32_MAX); } bool Thunderbolt::OnHandleCollision(std::shared_ptr other) { if (auto* enemyBase = runtime_cast(other.get())) { if (enemyBase->CanCollideWithShots) { _hit = true; } } return false; } void Thunderbolt::OnHitWall(float timeMult) { } void Thunderbolt::OnRicochet() { } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/Thunderbolt.h000066400000000000000000000017541512772601700270540ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Thunderbolt (shot) */ class Thunderbolt : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: Thunderbolt(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); bool OnHandleCollision(std::shared_ptr other) override; WeaponType GetWeaponType() override { return WeaponType::Thunderbolt; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; void OnAnimationFinished() override; void OnHitWall(float timeMult) override; void OnRicochet() override; private: bool _hit; float _lightProgress; Vector2f _farPoint; std::uint16_t _initialLayer; bool _firedUp; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ToasterShot.cpp000066400000000000000000000067071512772601700273770ustar00rootroot00000000000000#include "ToasterShot.h" #include "../Player.h" #include "../../ILevelHandler.h" #include "../../Tiles/TileMap.h" #include "../../../nCine/Base/Random.h" using namespace Jazz2::Tiles; namespace Jazz2::Actors::Weapons { ToasterShot::ToasterShot() : _fired(0) { } Task ToasterShot::OnActivatedAsync(const ActorActivationDetails& details) { async_await ShotBase::OnActivatedAsync(details); _strength = 1; _upgrades = details.Params[0]; SetState(ActorState::ApplyGravitation, false); async_await RequestMetadataAsync("Weapon/Toaster"_s); AnimState state = AnimState::Idle; if ((_upgrades & 0x01) != 0) { _timeLeft = 80.0f; state |= (AnimState)1; } else { _timeLeft = 60.0f; } SetAnimation(state); async_return true; } void ToasterShot::OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft) { _owner = owner; SetFacingLeft(isFacingLeft); _gunspotPos = gunspotPos; float ax = cosf(angle); float ay = sinf(angle); constexpr float baseSpeed = 1.2f; if (isFacingLeft) { _speed.X = std::min(0.0f, speed.X) - ax * (baseSpeed + Random().NextFloat(0.0f, 0.2f)); } else { _speed.X = std::max(0.0f, speed.X) + ax * (baseSpeed + Random().NextFloat(0.0f, 0.2f)); } _speed.X += ay * Random().NextFloat(-0.5f, 0.5f); if (isFacingLeft) { _speed.Y = -ay * (baseSpeed + Random().NextFloat(0.0f, 0.2f)); } else { _speed.Y = ay * (baseSpeed + Random().NextFloat(0.0f, 0.2f)); } _speed.Y += ax * Random().NextFloat(-0.5f, 0.5f); _renderer.setDrawEnabled(false); } void ToasterShot::OnUpdate(float timeMult) { OnUpdateHitbox(); if (_pos.Y >= _levelHandler->GetWaterLevel()) { DecreaseHealth(INT32_MAX); return; } auto tiles = _levelHandler->TileMap(); TileCollisionParams params = { TileDestructType::Weapon, _speed.Y >= 0.0f, WeaponType::Toaster, _strength }; if (tiles == nullptr || tiles->IsTileEmpty(AABBInner, params)) { MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force, params); } else { MoveInstantly(Vector2f(_speed.X * timeMult, _speed.Y * timeMult), MoveType::Relative | MoveType::Force, params); MoveInstantly(Vector2f(-_speed.X * timeMult, -_speed.Y * timeMult), MoveType::Relative | MoveType::Force, params); if ((_upgrades & 0x1) == 0) { DecreaseHealth(INT32_MAX); } } if (params.TilesDestroyed > 0) { if (auto* player = runtime_cast(_owner.get())) { player->AddScore(params.TilesDestroyed * 50); } } _fired++; if (_fired == 2) { MoveInstantly(_gunspotPos, (_upgrades & 0x1) != 0 ? MoveType::Absolute : (MoveType::Absolute | MoveType::Force)); _renderer.setDrawEnabled(true); } ShotBase::OnUpdate(timeMult); } void ToasterShot::OnUpdateHitbox() { AABBInner = AABBf(_pos.X - 3, _pos.Y - 3, _pos.X + 3, _pos.Y + 3); } void ToasterShot::OnEmitLights(SmallVectorImpl& lights) { if (_fired >= 2) { auto& light1 = lights.emplace_back(); light1.Pos = _pos; light1.Intensity = 0.85f; light1.Brightness = 0.6f; light1.RadiusNear = 0.0f; light1.RadiusFar = 30.0f; auto& light2 = lights.emplace_back(); light2.Pos = _pos; light2.Intensity = 0.2f; light2.RadiusNear = 0.0f; light2.RadiusFar = 140.0f; } } void ToasterShot::OnRicochet() { _speed.Y = _speed.Y * -0.2f * (Random().Next() % 100 - 50); _speed.X = _speed.X * -0.2f + (Random().Next() % 100 - 50) * 0.02f; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Actors/Weapons/ToasterShot.h000066400000000000000000000014201512772601700270270ustar00rootroot00000000000000#pragma once #include "ShotBase.h" namespace Jazz2::Actors::Weapons { /** @brief Toaster (shot) */ class ToasterShot : public ShotBase { DEATH_RUNTIME_OBJECT(ShotBase); public: ToasterShot(); /** @brief Called when the shot is fired */ void OnFire(const std::shared_ptr& owner, Vector2f gunspotPos, Vector2f speed, float angle, bool isFacingLeft); WeaponType GetWeaponType() override { return WeaponType::Toaster; } protected: Task OnActivatedAsync(const ActorActivationDetails& details) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; void OnEmitLights(SmallVectorImpl& lights) override; void OnRicochet() override; private: Vector2f _gunspotPos; std::int32_t _fired; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/AnimState.h000066400000000000000000000046511512772601700235770ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Well-known animation state */ enum class AnimState { // Bits 0, 1: Horizontal speed (none, low, med, high) Idle = 0x00000000, Walk = 0x00000001, Run = 0x00000002, Dash = 0x00000003, // Bits 2, 3: Vertical speed (none, upwards, downwards, suspended) VIdle = 0x00000000, Jump = 0x00000004, Fall = 0x00000008, Hook = 0x0000000c, // Bit 4: Shoot Shoot = 0x00000010, // Bits 5-9: Multiple special stances that cannot occur together // but still have unique bits due to complications in determining // the current actor state Crouch = 0x00000020, Lookup = 0x00000040, Dizzy = 0x00000080, Buttstomp = 0x00000100, Uppercut = 0x00000200, Airboard = 0x00000400, Hurt = 0x00000800, Swim = 0x00001000, Copter = 0x00002000, Push = 0x00004000, Swing = 0x00008000, Freefall = 0x00010000, Lift = 0x00020000, Spring = 0x0040000, // 30th bit: Transition range TransitionRunToIdle = 0x40000000, TransitionRunToDash = 0x40000001, TransitionFallToIdle = 0x40000002, TransitionIdleToJump = 0x40000003, TransitionShootToIdle = 0x40000004, TransitionHookShootToHook = 0x40000005, TransitionCopterShootToCopter = 0x40000006, TransitionUppercutA = 0x40000007, TransitionUppercutB = 0x40000008, TransitionUppercutEnd = 0x40000009, TransitionButtstompStart = 0x4000000A, TransitionPoleH = 0x4000000B, TransitionPoleV = 0x4000000C, TransitionPoleHSlow = 0x4000000D, TransitionPoleVSlow = 0x4000000E, TransitionDeath = 0x4000000F, TransitionTurn = 0x40000010, //0x40000011 //0x40000012 TransitionWarpIn = 0x40000013, TransitionWarpOut = 0x40000014, //0x40000015 TransitionEndOfLevel = 0x40000016, TransitionWarpInFreefall = 0x40000017, TransitionWarpOutFreefall = 0x40000018, TransitionIdleBored = 0x40000019, TransitionDashToIdle = 0x40000020, TransitionIdleToShoot = 0x40000021, TransitionButtstompEnd = 0x40000022, TransitionLiftStart = 0x40000023, TransitionLiftEnd = 0x40000024, TransitionLedge = 0x40000025, TransitionLedgeClimb = 0x40000026, TransitionFallShootToFall = 0x40000030, TransitionFromFrog = 0x40000040, // Aliases for common object states overlapping player states Activated = 0x00000020, TransitionActivate = 0x4F000000, TransitionAttack = 0x4F000001, TransitionAttackEnd = 0x4F000002, Uninitialized = -1, Default = 0 }; DEATH_ENUM_FLAGS(AnimState); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/AnimationLoopMode.h000066400000000000000000000005701512772601700252640ustar00rootroot00000000000000#pragma once namespace Jazz2 { /** @brief Animation loop mode */ enum class AnimationLoopMode { /** @brief The animation is played once and then remains in its last frame */ Once, /** @brief The animation is looped --- when reaching the last frame, it begins again at the first one */ Loop, /** @brief A fixed, single frame is displayed */ FixedSingle }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Collisions/000077500000000000000000000000001512772601700236515ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Collisions/DynamicTree.cpp000066400000000000000000000442501512772601700265660ustar00rootroot00000000000000// MIT License // // Copyright (c) 2019 Erin Catto // // 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. #include "DynamicTree.h" #include namespace Jazz2::Collisions { DynamicTree::DynamicTree() { _root = NullNode; _nodeCapacity = DefaultNodeCapacity; _nodeCount = 0; _nodes = new TreeNode[_nodeCapacity]; // Build a linked list for the free list. for (std::int32_t i = 0; i < _nodeCapacity - 1; ++i) { _nodes[i].Next = i + 1; _nodes[i].Height = -1; } _nodes[_nodeCapacity - 1].Next = NullNode; _nodes[_nodeCapacity - 1].Height = -1; _freeList = 0; _insertionCount = 0; } DynamicTree::~DynamicTree() { delete[] _nodes; } // Allocate a node from the pool. Grow the pool if necessary. std::int32_t DynamicTree::AllocateNode() { // Expand the node pool as needed. if (_freeList == NullNode) { //b2Assert(m_nodeCount == m_nodeCapacity); // The free list is empty. Rebuild a bigger pool. TreeNode* oldNodes = _nodes; _nodeCapacity *= 2; _nodes = new TreeNode[_nodeCapacity]; std::memcpy(_nodes, oldNodes, _nodeCount * sizeof(TreeNode)); delete[] oldNodes; // Build a linked list for the free list. The parent // pointer becomes the "next" pointer. for (std::int32_t i = _nodeCount; i < _nodeCapacity - 1; ++i) { _nodes[i].Next = i + 1; _nodes[i].Height = -1; } _nodes[_nodeCapacity - 1].Next = NullNode; _nodes[_nodeCapacity - 1].Height = -1; _freeList = _nodeCount; } // Peel a node off the free list. std::int32_t nodeId = _freeList; _freeList = _nodes[nodeId].Next; _nodes[nodeId].Parent = NullNode; _nodes[nodeId].Child1 = NullNode; _nodes[nodeId].Child2 = NullNode; _nodes[nodeId].Height = 0; _nodes[nodeId].UserData = nullptr; _nodes[nodeId].Moved = false; ++_nodeCount; return nodeId; } // Return a node to the pool. void DynamicTree::FreeNode(std::int32_t nodeId) { //b2Assert(0 <= nodeId && nodeId < m_nodeCapacity); //b2Assert(0 < m_nodeCount); _nodes[nodeId].Next = _freeList; _nodes[nodeId].Height = -1; _freeList = nodeId; --_nodeCount; } // Create a proxy in the tree as a leaf node. We return the index // of the node instead of a pointer so that we can grow // the node pool. std::int32_t DynamicTree::CreateProxy(const AABBf& aabb, void* userData) { std::int32_t proxyId = AllocateNode(); // Fatten the aabb _nodes[proxyId].Aabb.L = aabb.L - AabbExtension; _nodes[proxyId].Aabb.T = aabb.T - AabbExtension; _nodes[proxyId].Aabb.R = aabb.R + AabbExtension; _nodes[proxyId].Aabb.B = aabb.B + AabbExtension; _nodes[proxyId].UserData = userData; _nodes[proxyId].Height = 0; _nodes[proxyId].Moved = true; InsertLeaf(proxyId); return proxyId; } void DynamicTree::DestroyProxy(std::int32_t proxyId) { //b2Assert(0 <= proxyId && proxyId < m_nodeCapacity); //b2Assert(m_nodes[proxyId].IsLeaf()); RemoveLeaf(proxyId); FreeNode(proxyId); } bool DynamicTree::MoveProxy(std::int32_t proxyId, const AABBf& aabb, Vector2f displacement) { //b2Assert(0 <= proxyId && proxyId < m_nodeCapacity); //b2Assert(m_nodes[proxyId].IsLeaf()); // Extend AABB AABBf fatAABB; fatAABB.L = aabb.L - AabbExtension; fatAABB.T = aabb.T - AabbExtension; fatAABB.R = aabb.R + AabbExtension; fatAABB.B = aabb.B + AabbExtension; // Predict AABB movement Vector2f d = AabbMultiplier * displacement; if (d.X < 0.0f) { fatAABB.L += d.X; } else { fatAABB.R += d.X; } if (d.Y < 0.0f) { fatAABB.T += d.Y; } else { fatAABB.B += d.Y; } const AABBf& treeAABB = _nodes[proxyId].Aabb; if (treeAABB.Contains(aabb)) { // The tree AABB still contains the object, but it might be too large. // Perhaps the object was moving fast but has since gone to sleep. // The huge AABB is larger than the new fat AABB. AABBf hugeAABB; hugeAABB.L = fatAABB.L - 4.0f * AabbExtension; hugeAABB.T = fatAABB.T - 4.0f * AabbExtension; hugeAABB.R = fatAABB.R + 4.0f * AabbExtension; hugeAABB.B = fatAABB.B + 4.0f * AabbExtension; if (hugeAABB.Contains(treeAABB)) { // The tree AABB contains the object AABB and the tree AABB is // not too large. No tree update needed. return false; } // Otherwise the tree AABB is huge and needs to be shrunk } RemoveLeaf(proxyId); _nodes[proxyId].Aabb = fatAABB; InsertLeaf(proxyId); _nodes[proxyId].Moved = true; return true; } void DynamicTree::InsertLeaf(std::int32_t leaf) { ++_insertionCount; if (_root == NullNode) { _root = leaf; _nodes[_root].Parent = NullNode; return; } // Find the best sibling for this node AABBf leafAABB = _nodes[leaf].Aabb; std::int32_t index = _root; while (!_nodes[index].IsLeaf()) { std::int32_t child1 = _nodes[index].Child1; std::int32_t child2 = _nodes[index].Child2; float area = _nodes[index].Aabb.GetPerimeter(); AABBf combinedAABB = AABBf::Combine(_nodes[index].Aabb, leafAABB); float combinedArea = combinedAABB.GetPerimeter(); // Cost of creating a new parent for this node and the new leaf float cost = 2.0f * combinedArea; // Minimum cost of pushing the leaf further down the tree float inheritanceCost = 2.0f * (combinedArea - area); // Cost of descending into child1 float cost1; if (_nodes[child1].IsLeaf()) { AABBf aabb = AABBf::Combine(leafAABB, _nodes[child1].Aabb); cost1 = aabb.GetPerimeter() + inheritanceCost; } else { AABBf aabb = AABBf::Combine(leafAABB, _nodes[child1].Aabb); float oldArea = _nodes[child1].Aabb.GetPerimeter(); float newArea = aabb.GetPerimeter(); cost1 = (newArea - oldArea) + inheritanceCost; } // Cost of descending into child2 float cost2; if (_nodes[child2].IsLeaf()) { AABBf aabb = AABBf::Combine(leafAABB, _nodes[child2].Aabb); cost2 = aabb.GetPerimeter() + inheritanceCost; } else { AABBf aabb = AABBf::Combine(leafAABB, _nodes[child2].Aabb); float oldArea = _nodes[child2].Aabb.GetPerimeter(); float newArea = aabb.GetPerimeter(); cost2 = newArea - oldArea + inheritanceCost; } // Descend according to the minimum cost. if (cost < cost1 && cost < cost2) { break; } // Descend if (cost1 < cost2) { index = child1; } else { index = child2; } } std::int32_t sibling = index; // Create a new parent. std::int32_t oldParent = _nodes[sibling].Parent; std::int32_t newParent = AllocateNode(); _nodes[newParent].Parent = oldParent; _nodes[newParent].UserData = nullptr; _nodes[newParent].Aabb = AABBf::Combine(leafAABB, _nodes[sibling].Aabb); _nodes[newParent].Height = _nodes[sibling].Height + 1; if (oldParent != NullNode) { // The sibling was not the root. if (_nodes[oldParent].Child1 == sibling) { _nodes[oldParent].Child1 = newParent; } else { _nodes[oldParent].Child2 = newParent; } _nodes[newParent].Child1 = sibling; _nodes[newParent].Child2 = leaf; _nodes[sibling].Parent = newParent; _nodes[leaf].Parent = newParent; } else { // The sibling was the root. _nodes[newParent].Child1 = sibling; _nodes[newParent].Child2 = leaf; _nodes[sibling].Parent = newParent; _nodes[leaf].Parent = newParent; _root = newParent; } // Walk back up the tree fixing heights and AABBs index = _nodes[leaf].Parent; while (index != NullNode) { index = Balance(index); std::int32_t child1 = _nodes[index].Child1; std::int32_t child2 = _nodes[index].Child2; //b2Assert(child1 != NullNode); //b2Assert(child2 != NullNode); _nodes[index].Height = 1 + std::max(_nodes[child1].Height, _nodes[child2].Height); _nodes[index].Aabb = AABBf::Combine(_nodes[child1].Aabb, _nodes[child2].Aabb); index = _nodes[index].Parent; } //Validate(); } void DynamicTree::RemoveLeaf(std::int32_t leaf) { if (leaf == _root) { _root = NullNode; return; } std::int32_t parent = _nodes[leaf].Parent; std::int32_t grandParent = _nodes[parent].Parent; std::int32_t sibling; if (_nodes[parent].Child1 == leaf) { sibling = _nodes[parent].Child2; } else { sibling = _nodes[parent].Child1; } if (grandParent != NullNode) { // Destroy parent and connect sibling to grandParent. if (_nodes[grandParent].Child1 == parent) { _nodes[grandParent].Child1 = sibling; } else { _nodes[grandParent].Child2 = sibling; } _nodes[sibling].Parent = grandParent; FreeNode(parent); // Adjust ancestor bounds. std::int32_t index = grandParent; while (index != NullNode) { index = Balance(index); std::int32_t child1 = _nodes[index].Child1; std::int32_t child2 = _nodes[index].Child2; _nodes[index].Aabb = AABBf::Combine(_nodes[child1].Aabb, _nodes[child2].Aabb); _nodes[index].Height = 1 + std::max(_nodes[child1].Height, _nodes[child2].Height); index = _nodes[index].Parent; } } else { _root = sibling; _nodes[sibling].Parent = NullNode; FreeNode(parent); } //Validate(); } // Perform a left or right rotation if node A is imbalanced. // Returns the new root index. std::int32_t DynamicTree::Balance(std::int32_t iA) { //b2Assert(iA != NullNode); TreeNode* A = &_nodes[iA]; if (A->IsLeaf() || A->Height < 2) { return iA; } std::int32_t iB = A->Child1; std::int32_t iC = A->Child2; //b2Assert(0 <= iB && iB < m_nodeCapacity); //b2Assert(0 <= iC && iC < m_nodeCapacity); TreeNode* B = &_nodes[iB]; TreeNode* C = &_nodes[iC]; std::int32_t balance = C->Height - B->Height; // Rotate C up if (balance > 1) { std::int32_t iF = C->Child1; std::int32_t iG = C->Child2; TreeNode* F = &_nodes[iF]; TreeNode* G = &_nodes[iG]; //b2Assert(0 <= iF && iF < m_nodeCapacity); //b2Assert(0 <= iG && iG < m_nodeCapacity); // Swap A and C C->Child1 = iA; C->Parent = A->Parent; A->Parent = iC; // A's old parent should point to C if (C->Parent != NullNode) { if (_nodes[C->Parent].Child1 == iA) { _nodes[C->Parent].Child1 = iC; } else { //b2Assert(m_nodes[C->parent].child2 == iA); _nodes[C->Parent].Child2 = iC; } } else { _root = iC; } // Rotate if (F->Height > G->Height) { C->Child2 = iF; A->Child2 = iG; G->Parent = iA; A->Aabb = AABBf::Combine(B->Aabb, G->Aabb); C->Aabb = AABBf::Combine(A->Aabb, F->Aabb); A->Height = 1 + std::max(B->Height, G->Height); C->Height = 1 + std::max(A->Height, F->Height); } else { C->Child2 = iG; A->Child2 = iF; F->Parent = iA; A->Aabb = AABBf::Combine(B->Aabb, F->Aabb); C->Aabb = AABBf::Combine(A->Aabb, G->Aabb); A->Height = 1 + std::max(B->Height, F->Height); C->Height = 1 + std::max(A->Height, G->Height); } return iC; } // Rotate B up if (balance < -1) { std::int32_t iD = B->Child1; std::int32_t iE = B->Child2; TreeNode* D = &_nodes[iD]; TreeNode* E = &_nodes[iE]; //b2Assert(0 <= iD && iD < m_nodeCapacity); //b2Assert(0 <= iE && iE < m_nodeCapacity); // Swap A and B B->Child1 = iA; B->Parent = A->Parent; A->Parent = iB; // A's old parent should point to B if (B->Parent != NullNode) { if (_nodes[B->Parent].Child1 == iA) { _nodes[B->Parent].Child1 = iB; } else { //b2Assert(m_nodes[B->parent].child2 == iA); _nodes[B->Parent].Child2 = iB; } } else { _root = iB; } // Rotate if (D->Height > E->Height) { B->Child2 = iD; A->Child1 = iE; E->Parent = iA; A->Aabb = AABBf::Combine(C->Aabb, E->Aabb); B->Aabb = AABBf::Combine(A->Aabb, D->Aabb); A->Height = 1 + std::max(C->Height, E->Height); B->Height = 1 + std::max(A->Height, D->Height); } else { B->Child2 = iE; A->Child1 = iD; D->Parent = iA; A->Aabb = AABBf::Combine(C->Aabb, D->Aabb); B->Aabb = AABBf::Combine(A->Aabb, E->Aabb); A->Height = 1 + std::max(C->Height, D->Height); B->Height = 1 + std::max(A->Height, E->Height); } return iB; } return iA; } std::int32_t DynamicTree::GetHeight() const { if (_root == NullNode) { return 0; } return _nodes[_root].Height; } // float DynamicTree::GetAreaRatio() const { if (_root == NullNode) { return 0.0f; } const TreeNode* root = &_nodes[_root]; float rootArea = root->Aabb.GetPerimeter(); float totalArea = 0.0f; for (std::int32_t i = 0; i < _nodeCapacity; ++i) { const TreeNode* node = _nodes + i; if (node->Height < 0) { // Free node in pool continue; } totalArea += node->Aabb.GetPerimeter(); } return totalArea / rootArea; } // Compute the height of a sub-tree. std::int32_t DynamicTree::ComputeHeight(std::int32_t nodeId) const { //b2Assert(0 <= nodeId && nodeId < m_nodeCapacity); TreeNode* node = &_nodes[nodeId]; if (node->IsLeaf()) { return 0; } std::int32_t height1 = ComputeHeight(node->Child1); std::int32_t height2 = ComputeHeight(node->Child2); return 1 + std::max(height1, height2); } std::int32_t DynamicTree::ComputeHeight() const { std::int32_t height = ComputeHeight(_root); return height; } void DynamicTree::ValidateStructure(std::int32_t index) const { /* if (index == NullNode) { return; } if (index == _root) { //b2Assert(_nodes[index].Parent == NullNode); } const TreeNode* node = &_nodes[index]; std::int32_t child1 = node->Child1; std::int32_t child2 = node->Child2; if (node->IsLeaf()) { //b2Assert(child1 == NullNode); //b2Assert(child2 == NullNode); //b2Assert(node->Height == 0); return; } //b2Assert(0 <= child1 && child1 < _nodeCapacity); //b2Assert(0 <= child2 && child2 < _nodeCapacity); //b2Assert(_nodes[child1].Parent == index); //b2Assert(_nodes[child2].Parent == index); ValidateStructure(child1); ValidateStructure(child2); */ } /*void DynamicTree::ValidateMetrics(std::int32_t index) const { if (index == NullNode) { return; } const TreeNode* node = &_nodes[index]; std::int32_t child1 = node->Child1; std::int32_t child2 = node->Child2; if (node->IsLeaf()) { //b2Assert(child1 == NullNode); //b2Assert(child2 == NullNode); //b2Assert(node->Height == 0); return; } //b2Assert(0 <= child1 && child1 < _nodeCapacity); //b2Assert(0 <= child2 && child2 < _nodeCapacity); std::int32_t height1 = _nodes[child1].Height; std::int32_t height2 = _nodes[child2].Height; std::int32_t height = 1 + std::max(height1, height2); //b2Assert(node->Height == height); //AABBf aabb = AABBf::Combine(_nodes[child1].Aabb, _nodes[child2].Aabb); //b2Assert(aabb.lowerBound == node->Aabb.lowerBound); //b2Assert(aabb.upperBound == node->Aabb.upperBound); ValidateMetrics(child1); ValidateMetrics(child2); }*/ void DynamicTree::Validate() const { #if defined(b2DEBUG) ValidateStructure(_root); //ValidateMetrics(_root); std::int32_t freeCount = 0; std::int32_t freeIndex = _freeList; while (freeIndex != NullNode) { //b2Assert(0 <= freeIndex && freeIndex < _nodeCapacity); freeIndex = _nodes[freeIndex].next; ++freeCount; } //b2Assert(GetHeight() == ComputeHeight()); //b2Assert(_nodeCount + freeCount == _nodeCapacity); #endif } std::int32_t DynamicTree::GetMaxBalance() const { std::int32_t maxBalance = 0; for (std::int32_t i = 0; i < _nodeCapacity; ++i) { const TreeNode* node = &_nodes[i]; if (node->Height <= 1) { continue; } //b2Assert(!node->IsLeaf()); std::int32_t child1 = node->Child1; std::int32_t child2 = node->Child2; std::int32_t balance = std::abs(_nodes[child2].Height - _nodes[child1].Height); maxBalance = std::max(maxBalance, balance); } return maxBalance; } void DynamicTree::RebuildBottomUp() { std::unique_ptr nodes = std::make_unique(_nodeCount); std::int32_t count = 0; // Build array of leaves. Free the rest. for (std::int32_t i = 0; i < _nodeCapacity; ++i) { if (_nodes[i].Height < 0) { // free node in pool continue; } if (_nodes[i].IsLeaf()) { _nodes[i].Parent = NullNode; nodes[count] = i; ++count; } else { FreeNode(i); } } while (count > 1) { float minCost = FLT_MAX; std::int32_t iMin = -1, jMin = -1; for (std::int32_t i = 0; i < count; ++i) { AABBf aabbi = _nodes[nodes[i]].Aabb; for (std::int32_t j = i + 1; j < count; ++j) { AABBf aabbj = _nodes[nodes[j]].Aabb; AABBf b = AABBf::Combine(aabbi, aabbj); float cost = b.GetPerimeter(); if (cost < minCost) { iMin = i; jMin = j; minCost = cost; } } } std::int32_t index1 = nodes[iMin]; std::int32_t index2 = nodes[jMin]; TreeNode* child1 = &_nodes[index1]; TreeNode* child2 = &_nodes[index2]; std::int32_t parentIndex = AllocateNode(); TreeNode* parent = &_nodes[parentIndex]; parent->Child1 = index1; parent->Child2 = index2; parent->Height = 1 + std::max(child1->Height, child2->Height); parent->Aabb = AABBf::Combine(child1->Aabb, child2->Aabb); parent->Parent = NullNode; child1->Parent = parentIndex; child2->Parent = parentIndex; nodes[jMin] = nodes[count - 1]; nodes[iMin] = parentIndex; --count; } _root = nodes[0]; //std::free(nodes); Validate(); } void DynamicTree::ShiftOrigin(Vector2f newOrigin) { // Build array of leaves. Free the rest. for (std::int32_t i = 0; i < _nodeCapacity; ++i) { _nodes[i].Aabb.L -= newOrigin.X; _nodes[i].Aabb.T -= newOrigin.Y; _nodes[i].Aabb.R -= newOrigin.X; _nodes[i].Aabb.B -= newOrigin.Y; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Collisions/DynamicTree.h000066400000000000000000000227111512772601700262310ustar00rootroot00000000000000// MIT License // // Copyright (c) 2019 Erin Catto // // 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. #pragma once #include "../../nCine/Primitives/AABB.h" #include "../../nCine/Primitives/Vector2.h" #include using namespace Death::Containers; namespace Jazz2::Collisions { using nCine::AABBf; using nCine::Vector2f; /** @{ @name Constants */ /** @brief Invalid node */ constexpr std::int32_t NullNode = -1; /** @brief Length units per meter */ constexpr float LengthUnitsPerMeter = 1.0f; /** @brief AABB size extension to fat AABB */ constexpr float AabbExtension = 0.1f * LengthUnitsPerMeter; /** @brief AABB movement multiplier */ constexpr float AabbMultiplier = 4.0f; /** @} */ /** @brief Node in the dynamic tree The client does not interact with this directly. */ struct TreeNode { /** @brief Enlarged AABB */ AABBf Aabb; /** @brief Opaque pointer to user-supplied data */ void* UserData; union { /** @brief Node ID of parent node */ std::int32_t Parent; /** @brief Node ID of next node */ std::int32_t Next; }; /** @brief Node ID of the first child */ std::int32_t Child1; /** @brief Node ID of the second child */ std::int32_t Child2; /** @brief Height (leaf = 0, free node = -1) */ std::int32_t Height; /** @brief Whether node has been moved */ bool Moved; /** @brief Returns whether the node is leaf */ bool IsLeaf() const { return (Child1 == NullNode); } }; /** @brief Dynamic AABB tree broad-phase A dynamic tree arranges data in a binary tree to accelerate queries such as volume queries and ray casts. Leafs are proxies with an AABB. In the tree we expand the proxy AABB by @ref AabbExtension so that the proxy AABB is bigger than the client object. This allows the client object to move by small amounts without triggering a tree update. Nodes are pooled and relocatable, so we use node indices rather than pointers. */ class DynamicTree { public: /** @brief Constructing the tree initializes the node pool */ DynamicTree(); /** @brief Destroys the tree, freeing the node pool */ ~DynamicTree(); /** @brief Creates a proxy */ std::int32_t CreateProxy(const AABBf& aabb, void* userData); /** @brief Destroys a proxy */ void DestroyProxy(std::int32_t proxyId); /** * @brief Moves a proxy with a swepted AABB * * If the proxy has moved outside of its fattened AABB, then the proxy is removed from * the tree and re-inserted. Otherwise the function returns immediately. * * @return `true` if the proxy was re-inserted. */ bool MoveProxy(std::int32_t proxyId, const AABBf& aabb1, Vector2f displacement); /** * @brief Returns proxy user data. * * @return @return the proxy user data or `0` if the id is invalid. */ void* GetUserData(std::int32_t proxyId) const; /** @brief Returns `true` if a proxy has been moved */ bool WasMoved(std::int32_t proxyId) const; /** @brief Clears moved status of a proxy */ void ClearMoved(std::int32_t proxyId); /** @brief Returns the fat AABB for a proxy */ const AABBf& GetFatAABB(std::int32_t proxyId) const; /** @brief Queries an AABB for overlapping proxies, the callback is called for each proxy that overlaps the supplied AABB */ template void Query(T* callback, const AABBf& aabb) const; // @brief Ray-cast against the proxies in the tree // // This relies on the callback to perform a exact ray-cast in the case were the proxy contains a shape. // The callback also performs the any collision filtering. This has performance // roughly equal to @f$ k * log(n) @f$, where k is the number of collisions and n is the // number of proxies in the tree. // @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). // @param callback a callback class that is called for each proxy that is hit by the ray. // //template //void RayCast(T* callback, const b2RayCastInput& input) const; /** @brief Validates this tree --- for testing only */ void Validate() const; /** @brief Computes the height of the binary tree in @f$ \mathcal{O}(n) @f$ time, should not be called often */ std::int32_t GetHeight() const; /** @brief Returns the maximum balance of an node in the tree * * The balance is the difference in height of the two children of a node. */ std::int32_t GetMaxBalance() const; /** @brief Returns the ratio of the sum of the node areas to the root area */ float GetAreaRatio() const; /** @brief Builds an optimal tree, very expensive --- for testing only */ void RebuildBottomUp(); /** * @brief Shifts the world origin * * Useful for large worlds. The shift formula is: `position -= newOrigin` * * @param newOrigin the new origin with respect to the old origin */ void ShiftOrigin(Vector2f newOrigin); private: static constexpr std::int32_t DefaultNodeCapacity = /*16*/128; std::int32_t AllocateNode(); void FreeNode(std::int32_t node); void InsertLeaf(std::int32_t node); void RemoveLeaf(std::int32_t node); std::int32_t Balance(std::int32_t index); std::int32_t ComputeHeight() const; std::int32_t ComputeHeight(std::int32_t nodeId) const; void ValidateStructure(std::int32_t index) const; //void ValidateMetrics(std::int32_t index) const; std::int32_t _root; TreeNode* _nodes; std::int32_t _nodeCount; std::int32_t _nodeCapacity; std::int32_t _freeList; std::int32_t _insertionCount; }; inline void* DynamicTree::GetUserData(std::int32_t proxyId) const { //b2Assert(0 <= proxyId && proxyId < m_nodeCapacity); return _nodes[proxyId].UserData; } inline bool DynamicTree::WasMoved(std::int32_t proxyId) const { //b2Assert(0 <= proxyId && proxyId < m_nodeCapacity); return _nodes[proxyId].Moved; } inline void DynamicTree::ClearMoved(std::int32_t proxyId) { //b2Assert(0 <= proxyId && proxyId < m_nodeCapacity); _nodes[proxyId].Moved = false; } inline const AABBf& DynamicTree::GetFatAABB(std::int32_t proxyId) const { //b2Assert(0 <= proxyId && proxyId < m_nodeCapacity); return _nodes[proxyId].Aabb; } template inline void DynamicTree::Query(T* callback, const AABBf& aabb) const { SmallVector stack; stack.push_back(_root); while (!stack.empty()) { std::int32_t nodeId = stack.pop_back_val(); if (nodeId == NullNode) { continue; } const TreeNode* node = &_nodes[nodeId]; if (node->Aabb.Overlaps(aabb)) { if (node->IsLeaf()) { bool proceed = callback->OnCollisionQuery(nodeId); if (!proceed) { return; } } else { stack.push_back(node->Child1); stack.push_back(node->Child2); } } } } /*template inline void DynamicTree::RayCast(T* callback, const b2RayCastInput& input) const { b2Vec2 p1 = input.p1; b2Vec2 p2 = input.p2; b2Vec2 r = p2 - p1; b2Assert(r.LengthSquared() > 0.0f); r.Normalize(); // v is perpendicular to the segment. b2Vec2 v = b2Cross(1.0f, r); b2Vec2 abs_v = b2Abs(v); // Separating axis for segment (Gino, p80). // |dot(v, p1 - c)| > dot(|v|, h) float maxFraction = input.maxFraction; // Build a bounding box for the segment. b2AABB segmentAABB; { b2Vec2 t = p1 + maxFraction * (p2 - p1); segmentAABB.lowerBound = b2Min(p1, t); segmentAABB.upperBound = b2Max(p1, t); } b2GrowableStack stack; stack.Push(m_root); while (stack.GetCount() > 0) { int32 nodeId = stack.Pop(); if (nodeId == b2_nullNode) { continue; } const TreeNode* node = m_nodes + nodeId; if (b2TestOverlap(node->aabb, segmentAABB) == false) { continue; } // Separating axis for segment (Gino, p80). // |dot(v, p1 - c)| > dot(|v|, h) b2Vec2 c = node->aabb.GetCenter(); b2Vec2 h = node->aabb.GetExtents(); float separation = b2Abs(b2Dot(v, p1 - c)) - b2Dot(abs_v, h); if (separation > 0.0f) { continue; } if (node->IsLeaf()) { b2RayCastInput subInput; subInput.p1 = input.p1; subInput.p2 = input.p2; subInput.maxFraction = maxFraction; float value = callback->RayCastCallback(subInput, nodeId); if (value == 0.0f) { // The client has terminated the ray cast. return; } if (value > 0.0f) { // Update segment bounding box. maxFraction = value; b2Vec2 t = p1 + maxFraction * (p2 - p1); segmentAABB.lowerBound = b2Min(p1, t); segmentAABB.upperBound = b2Max(p1, t); } } else { stack.Push(node->child1); stack.Push(node->child2); } } }*/ }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Collisions/DynamicTreeBroadPhase.cpp000066400000000000000000000073601512772601700305200ustar00rootroot00000000000000// MIT License // // Copyright (c) 2019 Erin Catto // // 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. #include "DynamicTreeBroadPhase.h" namespace Jazz2::Collisions { DynamicTreeBroadPhase::DynamicTreeBroadPhase() { _proxyCount = 0; _pairCapacity = DefaultPairCapacity; _pairCount = 0; _pairBuffer = new CollisionPair[_pairCapacity]; _moveCapacity = DefaultMoveCapacity; _moveCount = 0; _moveBuffer = new std::int32_t[_moveCapacity]; } DynamicTreeBroadPhase::~DynamicTreeBroadPhase() { delete[] _moveBuffer; delete[] _pairBuffer; } int32_t DynamicTreeBroadPhase::CreateProxy(const AABBf& aabb, void* userData) { std::int32_t proxyId = _tree.CreateProxy(aabb, userData); ++_proxyCount; BufferMove(proxyId); return proxyId; } void DynamicTreeBroadPhase::DestroyProxy(std::int32_t proxyId) { UnBufferMove(proxyId); --_proxyCount; _tree.DestroyProxy(proxyId); } void DynamicTreeBroadPhase::MoveProxy(std::int32_t proxyId, const AABBf& aabb, Vector2f displacement) { // NOTE: Touch proxy everytime, because it's called only when something changes /*bool buffer =*/ _tree.MoveProxy(proxyId, aabb, displacement); //if (buffer) { BufferMove(proxyId); //} } void DynamicTreeBroadPhase::TouchProxy(std::int32_t proxyId) { BufferMove(proxyId); } void DynamicTreeBroadPhase::BufferMove(std::int32_t proxyId) { if (_moveCount == _moveCapacity) { std::int32_t* oldBuffer = _moveBuffer; _moveCapacity *= 2; _moveBuffer = new std::int32_t[_moveCapacity]; std::memcpy(_moveBuffer, oldBuffer, _moveCount * sizeof(std::int32_t)); delete[] oldBuffer; } _moveBuffer[_moveCount] = proxyId; ++_moveCount; } void DynamicTreeBroadPhase::UnBufferMove(std::int32_t proxyId) { for (std::int32_t i = 0; i < _moveCount; ++i) { if (_moveBuffer[i] == proxyId) { _moveBuffer[i] = NullNode; } } } // This is called from b2DynamicTree::Query when we are gathering pairs. bool DynamicTreeBroadPhase::OnCollisionQuery(std::int32_t proxyId) { // A proxy cannot form a pair with itself. if (proxyId == _queryProxyId) { return true; } const bool moved = _tree.WasMoved(proxyId); if (moved && proxyId > _queryProxyId) { // Both proxies are moving. Avoid duplicate pairs. return true; } // Grow the pair buffer as needed. if (_pairCount == _pairCapacity) { CollisionPair* oldBuffer = _pairBuffer; _pairCapacity = _pairCapacity + (_pairCapacity >> 1); _pairBuffer = new CollisionPair[_pairCapacity]; std::memcpy(_pairBuffer, oldBuffer, _pairCount * sizeof(CollisionPair)); delete[] oldBuffer; } _pairBuffer[_pairCount].ProxyIdA = std::min(proxyId, _queryProxyId); _pairBuffer[_pairCount].ProxyIdB = std::max(proxyId, _queryProxyId); ++_pairCount; return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Collisions/DynamicTreeBroadPhase.h000066400000000000000000000165161512772601700301700ustar00rootroot00000000000000// MIT License // // Copyright (c) 2019 Erin Catto // // 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. #pragma once #include "DynamicTree.h" namespace Jazz2::Collisions { /** @brief Collided pair of objects found by collision detection */ struct CollisionPair { /** @brief Proxy ID of the first node */ std::int32_t ProxyIdA; /** @brief Proxy ID of the second node */ std::int32_t ProxyIdB; }; /** @brief Broad-phase for collision detection The broad-phase is used for computing pairs and performing volume queries and ray casts. This broad-phase does not persist pairs. Instead, this reports potentially new pairs. It is up to the client to consume the new pairs and to track subsequent overlap. */ class DynamicTreeBroadPhase { friend class DynamicTree; public: DynamicTreeBroadPhase(); ~DynamicTreeBroadPhase(); /** * @brief Creates a proxy with an initial AABB * * Pairs are not reported until @ref UpdatePairs() is called */ std::int32_t CreateProxy(const AABBf& aabb, void* userData); /** @brief Destroys a proxy */ void DestroyProxy(std::int32_t proxyId); /** * @brief Moves a proxy with a swepted AABB * * If the proxy has moved outside of its fattened AABB, then the proxy is removed from * the tree and re-inserted. Otherwise the function returns immediately. * * @return `true` if the proxy was re-inserted. */ void MoveProxy(std::int32_t proxyId, const AABBf& aabb, Vector2f displacement); /** @brief Triggers a re-processing of it's pairs on the next call to @ref UpdatePairs() */ void TouchProxy(std::int32_t proxyId); /** @brief Returns the fat AABB for a proxy */ const AABBf& GetFatAABB(std::int32_t proxyId) const; /** @brief Returns a user data from a proxy */ void* GetUserData(std::int32_t proxyId) const; /** @brief Tests overlap of fat AABBs */ bool TestOverlap(std::int32_t proxyIdA, std::int32_t proxyIdB) const; /** @brief Returns the number of proxies */ std::int32_t GetProxyCount() const; /** @brief Updates the pairs */ template void UpdatePairs(T* callback); /** * @brief Queries an AABB for overlapping proxies * * The callback class is called for each proxy that overlaps the supplied AABB. */ template void Query(T* callback, const AABBf& aabb) const; // Ray-cast against the proxies in the tree. This relies on the callback // to perform a exact ray-cast in the case were the proxy contains a shape. // The callback also performs the any collision filtering. This has performance // roughly equal to k * log(n), where k is the number of collisions and n is the // number of proxies in the tree. // @param input the ray-cast input data. The ray extends from p1 to p1 + maxFraction * (p2 - p1). // @param callback a callback class that is called for each proxy that is hit by the ray. //template //void RayCast(T* callback, const b2RayCastInput& input) const; /** @brief Returns the height of the embedded tree */ std::int32_t GetTreeHeight() const; /** @brief Returns the balance of the embedded tree */ std::int32_t GetTreeBalance() const; /** @brief Returns the quality metric of the embedded tree */ float GetTreeQuality() const; /** * @brief Shifts the world origin * * Useful for large worlds. The shift formula is: `position -= newOrigin` * * @param newOrigin the new origin with respect to the old origin */ void ShiftOrigin(Vector2f newOrigin); private: static constexpr std::int32_t DefaultPairCapacity = 16; static constexpr std::int32_t DefaultMoveCapacity = /*16*/64; DynamicTree _tree; std::int32_t _proxyCount; std::int32_t* _moveBuffer; std::int32_t _moveCapacity; std::int32_t _moveCount; CollisionPair* _pairBuffer; std::int32_t _pairCapacity; std::int32_t _pairCount; std::int32_t _queryProxyId; void BufferMove(std::int32_t proxyId); void UnBufferMove(std::int32_t proxyId); bool OnCollisionQuery(std::int32_t proxyId); }; inline void* DynamicTreeBroadPhase::GetUserData(std::int32_t proxyId) const { return _tree.GetUserData(proxyId); } inline bool DynamicTreeBroadPhase::TestOverlap(std::int32_t proxyIdA, std::int32_t proxyIdB) const { const AABBf& aabbA = _tree.GetFatAABB(proxyIdA); const AABBf& aabbB = _tree.GetFatAABB(proxyIdB); return aabbA.Overlaps(aabbB); } inline const AABBf& DynamicTreeBroadPhase::GetFatAABB(std::int32_t proxyId) const { return _tree.GetFatAABB(proxyId); } inline std::int32_t DynamicTreeBroadPhase::GetProxyCount() const { return _proxyCount; } inline std::int32_t DynamicTreeBroadPhase::GetTreeHeight() const { return _tree.GetHeight(); } inline std::int32_t DynamicTreeBroadPhase::GetTreeBalance() const { return _tree.GetMaxBalance(); } inline float DynamicTreeBroadPhase::GetTreeQuality() const { return _tree.GetAreaRatio(); } template void DynamicTreeBroadPhase::UpdatePairs(T* callback) { // Reset pair buffer _pairCount = 0; // Perform tree queries for all moving proxies. for (std::int32_t i = 0; i < _moveCount; ++i) { _queryProxyId = _moveBuffer[i]; if (_queryProxyId == NullNode) { continue; } // We have to query the tree with the fat AABB so that // we don't fail to create a pair that may touch later. const AABBf& fatAABB = _tree.GetFatAABB(_queryProxyId); // Query tree, create pairs and add them pair buffer. _tree.Query(this, fatAABB); } // Send pairs to caller for (std::int32_t i = 0; i < _pairCount; ++i) { CollisionPair* primaryPair = &_pairBuffer[i]; void* userDataA = _tree.GetUserData(primaryPair->ProxyIdA); void* userDataB = _tree.GetUserData(primaryPair->ProxyIdB); callback->OnPairAdded(userDataA, userDataB); } // Clear move flags for (std::int32_t i = 0; i < _moveCount; ++i) { std::int32_t proxyId = _moveBuffer[i]; if (proxyId == NullNode) { continue; } _tree.ClearMoved(proxyId); } // Reset move buffer _moveCount = 0; } template inline void DynamicTreeBroadPhase::Query(T* callback, const AABBf& aabb) const { _tree.Query(callback, aabb); } /*template inline void DynamicTreeBroadPhase::RayCast(T* callback, const b2RayCastInput& input) const { _tree.RayCast(callback, input); }*/ inline void DynamicTreeBroadPhase::ShiftOrigin(Vector2f newOrigin) { _tree.ShiftOrigin(newOrigin); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/000077500000000000000000000000001512772601700243445ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/AnimSetMapping.cpp000066400000000000000000002301041512772601700277240ustar00rootroot00000000000000#include "AnimSetMapping.h" namespace Jazz2::Compatibility { AnimSetMapping::AnimSetMapping(JJ2Version version) : _version(version), _currentItem(0), _currentSet(0), _currentOrdinal(0) { } AnimSetMapping AnimSetMapping::GetAnimMapping(JJ2Version version) { AnimSetMapping m(version); if (version == JJ2Version::PlusExtension) { m.SkipItems(5); // Unimplemented weapon m.Add("Pickup"_s, "fast_fire_lori"_s); m.Add("UI"_s, "blaster_upgraded_lori"_s); m.NextSet(); m.DiscardItems(4); // Beta version sprites m.NextSet(); m.Add("Object"_s, "crate_ammo_pepper"_s); m.Add("Object"_s, "crate_ammo_electro"_s); m.Add("Object"_s, "powerup_shield_laser"_s); m.Add("Object"_s, "powerup_unknown"_s); m.Add("Object"_s, "powerup_empty"_s); m.Add("Object"_s, "powerup_upgrade_blaster_lori"_s); m.Add("Common"_s, "SugarRushStars"_s); m.SkipItems(); // Carrotade m.NextSet(); // 3 m.DiscardItems(3); // Lori's continue animations m.NextSet(); // 4 //m.Add("UI"_s, "font_medium"_s); //m.Add("UI"_s, "font_small"_s); //m.Add("UI"_s, "font_large"_s); m.DiscardItems(3); //m.Add("UI"_s, "logo_plus"_s, skipNormalMap: true); m.DiscardItems(1); m.NextSet(); // 5 m.Add("Object"_s, "powerup_swap_characters_lori"_s); //m.Add("UI"_s, "logo_plus_large"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); //m.Add("UI"_s, "logo_plus_small"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(2); m.NextSet(); // 6 m.DiscardItems(5); // Reticles } else if (version != JJ2Version::Unknown) { bool isFull = ((version & JJ2Version::SharewareDemo) != JJ2Version::SharewareDemo); // set 0 (all) m.Add("Unknown"_s, "flame_blue"_s); m.Add("Common"_s, "bomb"_s); m.Add("Common"_s, "smoke_poof"_s); m.Add("Common"_s, "explosion_rf"_s); m.Add("Common"_s, "explosion_small"_s); m.Add("Common"_s, "explosion_large"_s); m.Add("Common"_s, "smoke_circling_gray"_s); m.Add("Common"_s, "smoke_circling_brown"_s); m.Add("Weapon"_s, "shield_water"_s); //m.Add("Unknown"_s, "brown_thing"_s); m.DiscardItems(1); m.Add("Common"_s, "explosion_pepper"_s); m.Add("Weapon"_s, "shield_lightning"_s); m.Add("Weapon"_s, "shield_lightning_trail"_s); m.Add("Unknown"_s, "flame_red"_s); m.Add("Weapon"_s, "shield_fire"_s); m.Add("Weapon"_s, "flare_diag_downleft"_s); m.Add("Weapon"_s, "flare_hor"_s); m.Add("Weapon"_s, "bullet_blaster"_s); m.Add("UI"_s, "blaster_upgraded_jazz"_s); m.Add("UI"_s, "blaster_upgraded_spaz"_s); m.Add("Weapon"_s, "bullet_blaster_upgraded"_s); //m.Add("Weapon"_s, "bullet_blaster_upgraded_ver"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_blaster_ver"_s); m.DiscardItems(1); m.Add("Weapon"_s, "bullet_bouncer"_s); m.Add("Pickup"_s, "ammo_bouncer_upgraded"_s); m.Add("Pickup"_s, "ammo_bouncer"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded"_s); m.Add("Weapon"_s, "bullet_freezer_hor"_s); m.Add("Pickup"_s, "ammo_freezer_upgraded"_s); m.Add("Pickup"_s, "ammo_freezer"_s); m.Add("Weapon"_s, "bullet_freezer_upgraded_hor"_s); //m.Add("Weapon"_s, "bullet_freezer_ver"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_freezer_upgraded_ver"_s); m.DiscardItems(1); m.Add("Pickup"_s, "ammo_seeker_upgraded"_s); m.Add("Pickup"_s, "ammo_seeker"_s); //m.Add("Weapon"_s, "bullet_seeker_ver_down"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_seeker_diag_downright"_s); m.DiscardItems(1); m.Add("Weapon"_s, "bullet_seeker_hor"_s); //m.Add("Weapon"_s, "bullet_seeker_ver_up"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_seeker_diag_upright"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_seeker_upgraded_ver_down"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_seeker_upgraded_diag_downright"_s); m.DiscardItems(1); m.Add("Weapon"_s, "bullet_seeker_upgraded_hor"_s); //m.Add("Weapon"_s, "bullet_seeker_upgraded_ver_up"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_seeker_upgraded_diag_upright"_s); m.DiscardItems(1); m.Add("Weapon"_s, "bullet_rf_hor"_s); //m.Add("Weapon"_s, "bullet_rf_diag_downright"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_rf_upgraded_diag_downright"_s); m.DiscardItems(1); m.Add("Pickup"_s, "ammo_rf_upgraded"_s); m.Add("Pickup"_s, "ammo_rf"_s); m.Add("Weapon"_s, "bullet_rf_upgraded_hor"_s); //m.Add("Weapon"_s, "bullet_rf_upgraded_ver"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_rf_upgraded_diag_upright"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_rf_ver"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_rf_diag_upright"_s); m.DiscardItems(1); m.Add("Weapon"_s, "bullet_toaster"_s); m.Add("Pickup"_s, "ammo_toaster_upgraded"_s); m.Add("Pickup"_s, "ammo_toaster"_s); m.Add("Weapon"_s, "bullet_toaster_upgraded"_s); m.Add("Weapon"_s, "bullet_tnt"_s); m.Add("Weapon"_s, "bullet_fireball_hor"_s); m.Add("Pickup"_s, "ammo_pepper_upgraded"_s); m.Add("Pickup"_s, "ammo_pepper"_s); m.Add("Weapon"_s, "bullet_fireball_upgraded_hor"_s); //m.Add("Weapon"_s, "bullet_fireball_ver"_s); m.DiscardItems(1); //m.Add("Weapon"_s, "bullet_fireball_upgraded_ver"_s); m.DiscardItems(1); m.Add("Weapon"_s, "bullet_bladegun"_s); m.Add("Pickup"_s, "ammo_electro_upgraded"_s); m.Add("Pickup"_s, "ammo_electro"_s); m.Add("Weapon"_s, "bullet_bladegun_upgraded"_s); m.Add("Common"_s, "explosion_tiny"_s); m.Add("Common"_s, "explosion_freezer_maybe"_s); m.Add("Common"_s, "explosion_tiny_black"_s); m.Add("Weapon"_s, "bullet_fireball_upgraded_hor_2"_s); m.Add("Weapon"_s, "flare_hor_2"_s); m.Add("Unknown"_s, "green_explosion"_s); m.Add("Weapon"_s, "bullet_bladegun_alt"_s); m.Add("Weapon"_s, "bullet_tnt_explosion"_s); m.Add("Object"_s, "container_ammo_shrapnel_1"_s); m.Add("Object"_s, "container_ammo_shrapnel_2"_s); m.Add("Common"_s, "explosion_upwards"_s); m.Add("Common"_s, "explosion_bomb"_s); m.Add("Common"_s, "smoke_circling_white"_s); if (isFull) { m.NextSet(); m.Add("Bat"_s, "idle"_s); m.Add("Bat"_s, "resting"_s); m.Add("Bat"_s, "takeoff_1"_s); m.Add("Bat"_s, "takeoff_2"_s); m.Add("Bat"_s, "roost"_s); m.NextSet(); m.Add("Bee"_s, "swarm"_s); m.NextSet(); m.Add("Bee"_s, "swarm_2"_s); m.NextSet(); m.Add("Object"_s, "pushbox_crate"_s); m.NextSet(); m.Add("Object"_s, "pushbox_rock"_s); m.NextSet(); //m.Add("Unknown"_s, "diamondus_tileset_tree"_s); m.DiscardItems(1); m.NextSet(); m.Add("Bilsy"_s, "throw_fireball"_s); m.Add("Bilsy"_s, "appear"_s); m.Add("Bilsy"_s, "vanish"_s); m.Add("Bilsy"_s, "bullet_fireball"_s); m.Add("Bilsy"_s, "idle"_s); } m.NextSet(); m.Add("Birdy"_s, "charge_diag_downright"_s); m.Add("Birdy"_s, "charge_ver"_s); m.Add("Birdy"_s, "charge_diag_upright"_s); m.Add("Birdy"_s, "caged"_s); m.Add("Birdy"_s, "cage_destroyed"_s); m.Add("Birdy"_s, "die"_s); m.Add("Birdy"_s, "feather_green"_s); m.Add("Birdy"_s, "feather_red"_s); m.Add("Birdy"_s, "feather_green_and_red"_s); m.Add("Birdy"_s, "fly"_s); m.Add("Birdy"_s, "hurt"_s); m.Add("Birdy"_s, "idle_worm"_s); m.Add("Birdy"_s, "idle_turn_head_left"_s); m.Add("Birdy"_s, "idle_look_left"_s); m.Add("Birdy"_s, "idle_turn_head_left_back"_s); m.Add("Birdy"_s, "idle_turn_head_right"_s); m.Add("Birdy"_s, "idle_look_right"_s); m.Add("Birdy"_s, "idle_turn_head_right_back"_s); m.Add("Birdy"_s, "idle"_s); m.Add("Birdy"_s, "corpse"_s); if (isFull) { m.NextSet(); //m.Add("Unimplemented"_s, "bonus_birdy"_s); // Unused m.DiscardItems(1); m.NextSet(); // set 10 (all) m.Add("Platform"_s, "ball"_s); m.Add("Platform"_s, "ball_chain"_s); m.NextSet(); m.Add("Object"_s, "bonus_active"_s); m.Add("Object"_s, "bonus_inactive"_s); // Unused } m.NextSet(); m.Add("UI"_s, "boss_health_bar"_s, JJ2DefaultPalette::Sprite, true); m.NextSet(); m.Add("Bridge"_s, "rope"_s); m.Add("Bridge"_s, "stone"_s); m.Add("Bridge"_s, "vine"_s); m.Add("Bridge"_s, "stone_red"_s); m.Add("Bridge"_s, "log"_s); m.Add("Bridge"_s, "gem"_s); m.Add("Bridge"_s, "lab"_s); m.NextSet(); m.Add("Bubba"_s, "spew_fireball"_s); m.Add("Bubba"_s, "corpse"_s); m.Add("Bubba"_s, "jump"_s); m.Add("Bubba"_s, "jump_fall"_s); m.Add("Bubba"_s, "fireball"_s); m.Add("Bubba"_s, "hop"_s); m.Add("Bubba"_s, "tornado"_s); m.Add("Bubba"_s, "tornado_start"_s); m.Add("Bubba"_s, "tornado_end"_s); m.NextSet(); m.Add("Bee"_s, "bee"_s); m.Add("Bee"_s, "bee_turn"_s); if (isFull) { m.NextSet(); m.Add("Unimplemented"_s, "butterfly"_s); m.NextSet(); m.Add("Pole"_s, "carrotus"_s); m.NextSet(); m.Add("Cheshire"_s, "platform_appear"_s); m.Add("Cheshire"_s, "platform_vanish"_s); m.Add("Cheshire"_s, "platform_idle"_s); m.Add("Cheshire"_s, "platform_invisible"_s); } m.NextSet(); m.Add("Cheshire"_s, "hook_appear"_s); m.Add("Cheshire"_s, "hook_vanish"_s); m.Add("Cheshire"_s, "hook_idle"_s); m.Add("Cheshire"_s, "hook_invisible"_s); m.NextSet(); // set 20 (all) m.Add("Caterpillar"_s, "exhale_start"_s); m.Add("Caterpillar"_s, "exhale"_s); m.Add("Caterpillar"_s, "disoriented"_s); m.Add("Caterpillar"_s, "idle"_s); m.Add("Caterpillar"_s, "inhale_start"_s); m.Add("Caterpillar"_s, "inhale"_s); m.Add("Caterpillar"_s, "smoke"_s); if (isFull) { m.NextSet(); m.Add("BirdyYellow"_s, "charge_diag_downright_placeholder"_s); m.Add("BirdyYellow"_s, "charge_ver"_s); m.Add("BirdyYellow"_s, "charge_diag_upright"_s); m.Add("BirdyYellow"_s, "caged"_s); m.Add("BirdyYellow"_s, "cage_destroyed"_s); m.Add("BirdyYellow"_s, "die"_s); m.Add("BirdyYellow"_s, "feather_blue"_s); m.Add("BirdyYellow"_s, "feather_yellow"_s); m.Add("BirdyYellow"_s, "feather_blue_and_yellow"_s); m.Add("BirdyYellow"_s, "fly"_s); m.Add("BirdyYellow"_s, "hurt"_s); m.Add("BirdyYellow"_s, "idle_worm"_s); m.Add("BirdyYellow"_s, "idle_turn_head_left"_s); m.Add("BirdyYellow"_s, "idle_look_left"_s); m.Add("BirdyYellow"_s, "idle_turn_head_left_back"_s); m.Add("BirdyYellow"_s, "idle_turn_head_right"_s); m.Add("BirdyYellow"_s, "idle_look_right"_s); m.Add("BirdyYellow"_s, "idle_turn_head_right_back"_s); m.Add("BirdyYellow"_s, "idle"_s); m.Add("BirdyYellow"_s, "corpse"_s); } m.NextSet(); m.Add("Common"_s, "water_bubble_1"_s); m.Add("Common"_s, "water_bubble_2"_s); m.Add("Common"_s, "water_bubble_3"_s); m.Add("Common"_s, "water_splash"_s); m.NextSet(); m.Add("Jazz"_s, "gameover_continue"_s); m.Add("Jazz"_s, "gameover_idle"_s); m.Add("Jazz"_s, "gameover_end"_s); m.Add("Spaz"_s, "gameover_continue"_s); m.Add("Spaz"_s, "gameover_idle"_s); m.Add("Spaz"_s, "gameover_end"_s); if (isFull) { m.NextSet(); m.Add("Demon"_s, "idle"_s); m.Add("Demon"_s, "attack_start"_s); m.Add("Demon"_s, "attack"_s); m.Add("Demon"_s, "attack_end"_s); } m.NextSet(); m.DiscardItems(4); // Green rectangles m.Add("Common"_s, "ice_block"_s); if (isFull) { m.NextSet(); m.Add("Devan"_s, "bullet_small"_s); m.Add("Devan"_s, "remote_idle"_s); m.Add("Devan"_s, "remote_fall_warp_out"_s); m.Add("Devan"_s, "remote_fall"_s); m.Add("Devan"_s, "remote_fall_rotate"_s); m.Add("Devan"_s, "remote_fall_warp_in"_s); m.Add("Devan"_s, "remote_warp_out"_s); m.NextSet(); m.Add("Devan"_s, "demon_spew_fireball"_s); m.Add("Devan"_s, "disoriented"_s); m.Add("Devan"_s, "freefall"_s); m.Add("Devan"_s, "disoriented_start"_s); m.Add("Devan"_s, "demon_fireball"_s); m.Add("Devan"_s, "demon_fly"_s); m.Add("Devan"_s, "demon_transform_start"_s); m.Add("Devan"_s, "demon_transform_end"_s); m.Add("Devan"_s, "disarmed_idle"_s); m.Add("Devan"_s, "demon_turn"_s); m.Add("Devan"_s, "disarmed_warp_in"_s); m.Add("Devan"_s, "disoriented_warp_out"_s); m.Add("Devan"_s, "disarmed"_s); m.Add("Devan"_s, "crouch"_s); m.Add("Devan"_s, "shoot"_s); m.Add("Devan"_s, "disarmed_gun"_s); m.Add("Devan"_s, "jump"_s); m.Add("Devan"_s, "bullet"_s); m.Add("Devan"_s, "run"_s); m.Add("Devan"_s, "run_end"_s); m.Add("Devan"_s, "jump_end"_s); m.Add("Devan"_s, "idle"_s); m.Add("Devan"_s, "warp_in"_s); m.Add("Devan"_s, "warp_out"_s); } m.NextSet(); m.Add("Pole"_s, "diamondus"_s); if (isFull) { m.NextSet(); m.Add("Doggy"_s, "attack"_s); m.Add("Doggy"_s, "walk"_s); m.NextSet(); // set 30 (all) //m.Add("Unimplemented"_s, "door"_s); // Unused //m.Add("Unimplemented"_s, "door_enter_jazz_spaz"_s); // Unused m.DiscardItems(2); } m.NextSet(); m.Add("Dragonfly"_s, "idle"_s); if (isFull) { m.NextSet(); m.Add("Dragon"_s, "attack"_s); m.Add("Dragon"_s, "idle"_s); m.Add("Dragon"_s, "turn"_s); m.NextSet(1, JJ2Version::BaseGame | JJ2Version::HH); m.NextSet(2, JJ2Version::TSF | JJ2Version::CC); } m.NextSet(4); m.Add("Eva"_s, "blink"_s); m.Add("Eva"_s, "idle"_s); m.Add("Eva"_s, "kiss_start"_s); m.Add("Eva"_s, "kiss_end"_s); m.NextSet(); m.Add("UI"_s, "icon_birdy"_s); m.Add("UI"_s, "icon_birdy_yellow"_s); m.Add("UI"_s, "icon_frog"_s); m.Add("UI"_s, "icon_jazz"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "UI"_s, "icon_lori"_s); m.Add("UI"_s, "icon_spaz"_s); if (isFull) { m.NextSet(2); // set 41 (1.24) / set 40 (1.23) m.Add("FatChick"_s, "attack"_s); m.Add("FatChick"_s, "walk"_s); m.NextSet(); m.Add("Fencer"_s, "attack"_s); m.Add("Fencer"_s, "idle"_s); m.NextSet(); m.Add("Fish"_s, "attack"_s); m.Add("Fish"_s, "idle"_s); } m.NextSet(); m.Add("CTF"_s, "arrow"_s); m.Add("CTF"_s, "base"_s); m.Add("CTF"_s, "lights"_s); m.Add("CTF"_s, "flag_blue"_s); m.Add("UI"_s, "ctf_flag_blue"_s); m.Add("CTF"_s, "base_eva"_s); m.Add("CTF"_s, "base_eva_cheer"_s); m.Add("CTF"_s, "flag_red"_s); m.Add("UI"_s, "ctf_flag_red"_s); if (isFull) { m.NextSet(); m.DiscardItems(1); // Strange green circles } m.NextSet(); //m.Add("UI"_s, "font_medium"_s); //m.Add("UI"_s, "font_small"_s); //m.Add("UI"_s, "font_large"_s); m.DiscardItems(3); //m.Add("UI"_s, "logo"_s, skipNormalMap: true); m.DiscardItems(1); //m.Add(JJ2Version::CC, "UI"_s, "cc_logo"_s); m.DiscardItems(1, JJ2Version::CC); m.NextSet(); m.Add("Frog"_s, "fall_land"_s); m.Add("Frog"_s, "hurt"_s); m.Add("Frog"_s, "idle"_s); m.Add("Jazz"_s, "transform_frog"_s); m.Add("Frog"_s, "fall"_s); m.Add("Frog"_s, "jump_start"_s); m.Add("Frog"_s, "crouch"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "transform_frog"_s); m.Add("Frog"_s, "tongue_diag_upright"_s); m.Add("Frog"_s, "tongue_hor"_s); m.Add("Frog"_s, "tongue_ver"_s); m.Add("Spaz"_s, "transform_frog"_s); m.Add("Frog"_s, "run"_s); if (isFull) { m.NextSet(); m.Add("Platform"_s, "carrotus_fruit"_s); m.Add("Platform"_s, "carrotus_fruit_chain"_s); m.NextSet(); //m.Add("Pickup"_s, "gem_gemring"_s, keepIndexed: true); m.DiscardItems(1); m.NextSet(); // set 50 (1.24) / set 49 (1.23) m.Add("Unimplemented"_s, "boxing_glove_stiff"_s); m.Add("Unimplemented"_s, "boxing_glove_stiff_idle"_s); m.Add("Unimplemented"_s, "boxing_glove_normal"_s); m.Add("Unimplemented"_s, "boxing_glove_normal_idle"_s); m.Add("Unimplemented"_s, "boxing_glove_relaxed"_s); m.Add("Unimplemented"_s, "boxing_glove_relaxed_idle"_s); m.NextSet(); m.Add("Platform"_s, "carrotus_grass"_s); m.Add("Platform"_s, "carrotus_grass_chain"_s); } m.NextSet(); m.Add("MadderHatter"_s, "cup"_s); m.Add("MadderHatter"_s, "hat"_s); m.Add("MadderHatter"_s, "attack"_s); m.Add("MadderHatter"_s, "bullet_spit"_s); m.Add("MadderHatter"_s, "walk"_s); if (isFull) { m.NextSet(); m.Add("Helmut"_s, "idle"_s); m.Add("Helmut"_s, "walk"_s); } m.NextSet(2); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unknown_disoriented"_s); m.Add("Jazz"_s, "airboard"_s); m.Add("Jazz"_s, "airboard_turn"_s); m.Add("Jazz"_s, "buttstomp_end"_s); m.Add("Jazz"_s, "corpse"_s); m.Add("Jazz"_s, "die"_s); m.Add("Jazz"_s, "crouch_start"_s); m.Add("Jazz"_s, "crouch"_s); m.Add("Jazz"_s, "crouch_shoot"_s); m.Add("Jazz"_s, "crouch_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_door_enter"_s); m.Add("Jazz"_s, "vine_walk"_s); m.Add("Jazz"_s, "eol"_s); m.Add("Jazz"_s, "fall"_s); m.Add("Jazz"_s, "buttstomp"_s); m.Add("Jazz"_s, "fall_end"_s); m.Add("Jazz"_s, "shoot"_s); m.Add("Jazz"_s, "shoot_ver"_s); m.Add("Jazz"_s, "shoot_end"_s); m.Add("Jazz"_s, "transform_frog_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "ledge_climb"_s); m.Add("Jazz"_s, "vine_shoot_start"_s); m.Add("Jazz"_s, "vine_shoot_up_end"_s); m.Add("Jazz"_s, "vine_shoot_up"_s); m.Add("Jazz"_s, "vine_idle"_s); m.Add("Jazz"_s, "vine_idle_flavor"_s); m.Add("Jazz"_s, "vine_shoot_end"_s); m.Add("Jazz"_s, "vine_shoot"_s); m.Add("Jazz"_s, "copter"_s); m.Add("Jazz"_s, "copter_shoot_start"_s); m.Add("Jazz"_s, "copter_shoot"_s); m.Add("Jazz"_s, "pole_h"_s); m.Add("Jazz"_s, "hurt"_s); m.Add("Jazz"_s, "idle_flavor_1"_s); m.Add("Jazz"_s, "idle_flavor_2"_s); m.Add("Jazz"_s, "idle_flavor_3"_s); m.Add("Jazz"_s, "idle_flavor_4"_s); m.Add("Jazz"_s, "idle_flavor_5"_s); m.Add("Jazz"_s, "vine_shoot_up_start"_s); m.Add("Jazz"_s, "fall_shoot"_s); m.Add("Jazz"_s, "jump_unknown_1"_s); m.Add("Jazz"_s, "jump_unknown_2"_s); m.Add("Jazz"_s, "jump"_s); m.Add("Jazz"_s, "ledge"_s); m.Add("Jazz"_s, "lift"_s); m.Add("Jazz"_s, "lift_end"_s); m.Add("Jazz"_s, "lift_start"_s); m.Add("Jazz"_s, "lookup_start"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_diag_upright"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_ver_up"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_diag_upleft_reverse"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_reverse"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_diag_downleft_reverse"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_ver_down"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_diag_downright"_s); m.DiscardItems(7, JJ2Version::BaseGame | JJ2Version::HH); m.Add("Jazz"_s, "dizzy_walk"_s); m.Add("Jazz"_s, "push"_s); m.Add("Jazz"_s, "shoot_start"_s); m.Add("Jazz"_s, "revup_start"_s); m.Add("Jazz"_s, "revup"_s); m.Add("Jazz"_s, "revup_end"_s); m.Add("Jazz"_s, "fall_diag"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unknown_mid_frame"_s); m.Add("Jazz"_s, "jump_diag"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_jump_shoot_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_jump_shoot_ver_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_jump_shoot_ver"_s); m.Add("Jazz"_s, "ball"_s); m.Add("Jazz"_s, "run"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_run_aim_diag"_s); m.Add("Jazz"_s, "dash_start"_s); m.Add("Jazz"_s, "dash"_s); m.Add("Jazz"_s, "dash_stop"_s); m.Add("Jazz"_s, "walk_stop"_s); m.Add("Jazz"_s, "run_stop"_s); m.Add("Jazz"_s, "spring"_s); m.Add("Jazz"_s, "idle"_s); m.Add("Jazz"_s, "uppercut"_s); m.Add("Jazz"_s, "uppercut_end"_s); m.Add("Jazz"_s, "uppercut_start"_s); m.Add("Jazz"_s, "dizzy"_s); m.Add("Jazz"_s, "swim_diag_downright"_s); m.Add("Jazz"_s, "swim_right"_s); m.Add("Jazz"_s, "swim_diag_right_to_downright"_s); m.Add("Jazz"_s, "swim_diag_right_to_upright"_s); m.Add("Jazz"_s, "swim_diag_upright"_s); m.Add("Jazz"_s, "swing"_s); m.Add("Jazz"_s, "warp_in"_s); m.Add("Jazz"_s, "warp_out_freefall"_s); m.Add("Jazz"_s, "freefall"_s); m.Add("Jazz"_s, "warp_in_freefall"_s); m.Add("Jazz"_s, "warp_out"_s); m.Add("Jazz"_s, "pole_v"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_crouch_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_crouch_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_fall"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_hurt"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_idle"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_jump"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_crouch_end_2"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_lookup_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_run"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_unarmed_stare"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Jazz"_s, "unused_lookup_start_2"_s); m.NextSet(); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_idle_flavor_2"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_jump_2"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_dash_2"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_rotate_2"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_ball_2"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_run_2"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unimplemented"_s, "bonus_jazz_idle_2"_s); m.DiscardItems(7, JJ2Version::BaseGame | JJ2Version::HH); //m.Add("Unimplemented"_s, "bonus_jazz_idle_flavor"_s); //m.Add("Unimplemented"_s, "bonus_jazz_jump"_s); //m.Add("Unimplemented"_s, "bonus_jazz_ball"_s); //m.Add("Unimplemented"_s, "bonus_jazz_run"_s); //m.Add("Unimplemented"_s, "bonus_jazz_dash"_s); //m.Add("Unimplemented"_s, "bonus_jazz_rotate"_s); //m.Add("Unimplemented"_s, "bonus_jazz_idle"_s); m.DiscardItems(7); if (isFull) { m.NextSet(2); m.Add("Pole"_s, "jungle"_s); } m.NextSet(); m.Add("LabRat"_s, "attack"_s); m.Add("LabRat"_s, "idle"_s); m.Add("LabRat"_s, "walk"_s); m.NextSet(); // set 60 (1.24) / set 59 (1.23) m.Add("Lizard"_s, "copter_attack"_s); m.Add("Lizard"_s, "bomb"_s); m.Add("Lizard"_s, "copter_idle"_s); m.Add("Lizard"_s, "copter"_s); m.Add("Lizard"_s, "walk"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "airboard"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "airboard_turn"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "buttstomp_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "corpse"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "die"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "crouch_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "crouch"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "crouch_shoot"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "crouch_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_walk"_s); //m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "eol"_s); m.DiscardItems(1, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "fall"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "buttstomp"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "fall_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "shoot"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "shoot_ver"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "shoot_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "transform_frog_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_shoot_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_shoot_up_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_shoot_up"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_idle"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_idle_flavor"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_shoot_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_shoot"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "copter"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "copter_shoot_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "copter_shoot"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "pole_h"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle_flavor_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle_flavor_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle_flavor_3"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle_flavor_4"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle_flavor_5"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "vine_shoot_up_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "fall_shoot"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_unknown_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_unknown_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "ledge"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "lift"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "lift_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "lift_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "lookup_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "dizzy_walk"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "push"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "shoot_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "revup_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "revup"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "revup_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "fall_diag"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_diag"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "ball"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "run"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "dash_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "dash"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "dash_stop"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "walk_stop"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "run_stop"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "spring"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "uppercut_placeholder_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "uppercut_placeholder_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "sidekick"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "dizzy"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "swim_diag_downright"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "swim_right"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "swim_diag_right_to_downright"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "swim_diag_right_to_upright"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "swim_diag_upright"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "swing"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "warp_in"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "warp_out_freefall"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "freefall"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "warp_in_freefall"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "warp_out"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "pole_v"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "idle_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "gun"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC); m.NextSet(); //m.Add("UI"_s, "multiplayer_char"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(1); //m.Add("UI"_s, "multiplayer_color"_s, JJ2DefaultPalette.Menu); m.DiscardItems(1); m.Add("UI"_s, "character_art_difficulty_jazz"_s, JJ2DefaultPalette::Menu, true); //m.Add(JJ2Version::TSF | JJ2Version::CC, "UI"_s, "character_art_difficulty_lori"_s, JJ2DefaultPalette::Menu, true); m.DiscardItems(1, JJ2Version::TSF | JJ2Version::CC); m.Add("UI"_s, "character_art_difficulty_spaz"_s, JJ2DefaultPalette::Menu, true); //m.Add("Unimplemented"_s, "key"_s, JJ2DefaultPalette::Menu, true); m.DiscardItems(1); //m.Add("UI"_s, "loading_bar"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(1); //m.Add("UI"_s, "multiplayer_mode"_s, JJ2DefaultPalette::Menu, true); m.DiscardItems(1); //m.Add("UI"_s, "character_name_jazz"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(1); //m.Add(JJ2Version::TSF | JJ2Version::CC, "UI"_s, "character_name_lori"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(1, JJ2Version::TSF | JJ2Version::CC); //m.Add("UI"_s, "character_name_spaz"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(1); //m.Add("UI"_s, "character_art_jazz"_s, JJ2DefaultPalette::Menu, true); m.DiscardItems(1); //m.Add(JJ2Version::TSF | JJ2Version::CC, "UI"_s, "character_art_lori"_s, JJ2DefaultPalette::Menu, true); m.DiscardItems(1, JJ2Version::TSF | JJ2Version::CC); //m.Add("UI"_s, "character_art_spaz"_s, JJ2DefaultPalette::Menu, true); m.DiscardItems(1); m.NextSet(); //m.Add("UI"_s, "font_medium_2"_s, JJ2DefaultPalette.Menu); //m.Add("UI"_s, "font_small_2"_s, JJ2DefaultPalette.Menu); //m.Add("UI"_s, "logo_large"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); //m.Add(JJ2Version::TSF | JJ2Version::CC, "UI"_s, "tsf_title"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); //m.Add(JJ2Version::CC, "UI"_s, "menu_snow"_s, JJ2DefaultPalette.Menu); //m.Add("UI"_s, "logo_small"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); //m.Add(JJ2Version::CC, "UI"_s, "cc_title"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); //m.Add(JJ2Version::CC, "UI"_s, "cc_title_small"_s, JJ2DefaultPalette.Menu, skipNormalMap: true); m.DiscardItems(8); if (isFull) { m.NextSet(2); m.Add("Monkey"_s, "banana"_s); m.Add("Monkey"_s, "banana_splat"_s); m.Add("Monkey"_s, "jump"_s); m.Add("Monkey"_s, "walk_start"_s); m.Add("Monkey"_s, "walk_end"_s); m.Add("Monkey"_s, "attack"_s); m.Add("Monkey"_s, "walk"_s); m.NextSet(); m.Add("Moth"_s, "green"_s); m.Add("Moth"_s, "gray"_s); m.Add("Moth"_s, "purple"_s); m.Add("Moth"_s, "pink"_s); } else { m.NextSet(); } m.NextSet(3); // set 71 (1.24) / set 67 (1.23) m.Add("Pickup"_s, "1up"_s); m.Add("Pickup"_s, "food_apple"_s); m.Add("Pickup"_s, "food_banana"_s); m.Add("Object"_s, "container_barrel"_s); m.Add("Common"_s, "poof_brown"_s); m.Add("Object"_s, "container_box_crush"_s); m.Add("Object"_s, "container_barrel_shrapnel_1"_s); m.Add("Object"_s, "container_barrel_shrapnel_2"_s); m.Add("Object"_s, "container_barrel_shrapnel_3"_s); m.Add("Object"_s, "container_barrel_shrapnel_4"_s); m.Add("Object"_s, "powerup_shield_water"_s); m.Add("Pickup"_s, "food_burger"_s); m.Add("Pickup"_s, "food_cake"_s); m.Add("Pickup"_s, "food_candy"_s); m.Add("Object"_s, "checkpoint"_s); m.Add("Pickup"_s, "food_cheese"_s); m.Add("Pickup"_s, "food_cherry"_s); m.Add("Pickup"_s, "food_chicken"_s); m.Add("Pickup"_s, "food_chips"_s); m.Add("Pickup"_s, "food_chocolate"_s); m.Add("Pickup"_s, "food_cola"_s); m.Add("Pickup"_s, "carrot"_s); m.Add("Pickup"_s, "gem"_s, JJ2DefaultPalette::Sprite, false, true); m.Add("Pickup"_s, "food_cucumber"_s); m.Add("Pickup"_s, "food_cupcake"_s); m.Add("Pickup"_s, "food_donut"_s); m.Add("Pickup"_s, "food_eggplant"_s); m.Add("Unknown"_s, "green_blast_thing"_s); m.Add("Object"_s, "exit_sign"_s); m.Add("Pickup"_s, "fast_fire_jazz"_s); m.Add("Pickup"_s, "fast_fire_spaz"_s); m.Add("Object"_s, "powerup_shield_fire"_s); m.Add("Pickup"_s, "food_fries"_s); m.Add("Pickup"_s, "fast_feet"_s); m.Add("Object"_s, "gem_giant"_s, JJ2DefaultPalette::Sprite, false, true); //m.Add("Pickup"_s, "gem2"_s, keepIndexed: true); m.DiscardItems(1); m.Add("Pickup"_s, "airboard"_s); m.Add("Pickup"_s, "coin_gold"_s); m.Add("Pickup"_s, "food_grapes"_s); m.Add("Pickup"_s, "food_ham"_s); m.Add("Pickup"_s, "carrot_fly"_s); m.Add("UI"_s, "heart"_s, JJ2DefaultPalette::Sprite, true); m.Add("Pickup"_s, "freeze_enemies"_s); m.Add("Pickup"_s, "food_ice_cream"_s); m.Add("Common"_s, "ice_break_shrapnel_1"_s); m.Add("Common"_s, "ice_break_shrapnel_2"_s); m.Add("Common"_s, "ice_break_shrapnel_3"_s); m.Add("Common"_s, "ice_break_shrapnel_4"_s); m.Add("Pickup"_s, "food_lemon"_s); m.Add("Pickup"_s, "food_lettuce"_s); m.Add("Pickup"_s, "food_lime"_s); m.Add("Object"_s, "powerup_shield_lightning"_s); m.Add("Object"_s, "trigger_crate"_s); m.Add("Pickup"_s, "food_milk"_s); m.Add("Object"_s, "crate_ammo_bouncer"_s); m.Add("Object"_s, "crate_ammo_freezer"_s); m.Add("Object"_s, "crate_ammo_seeker"_s); m.Add("Object"_s, "crate_ammo_rf"_s); m.Add("Object"_s, "crate_ammo_toaster"_s); m.Add("Object"_s, "crate_ammo_tnt"_s); m.Add("Object"_s, "powerup_upgrade_blaster_jazz"_s); m.Add("Object"_s, "powerup_upgrade_bouncer"_s); m.Add("Object"_s, "powerup_upgrade_freezer"_s); m.Add("Object"_s, "powerup_upgrade_seeker"_s); m.Add("Object"_s, "powerup_upgrade_rf"_s); m.Add("Object"_s, "powerup_upgrade_toaster"_s); m.Add("Object"_s, "powerup_upgrade_pepper"_s); m.Add("Object"_s, "powerup_upgrade_electro"_s); m.Add("Object"_s, "powerup_transform_birdy"_s); m.Add("Object"_s, "powerup_transform_birdy_yellow"_s); m.Add("Object"_s, "powerup_swap_characters"_s); m.Add("Pickup"_s, "food_orange"_s); m.Add("Pickup"_s, "carrot_invincibility"_s); m.Add("Pickup"_s, "food_peach"_s); m.Add("Pickup"_s, "food_pear"_s); m.Add("Pickup"_s, "food_soda"_s); m.Add("Pickup"_s, "food_pie"_s); m.Add("Pickup"_s, "food_pizza"_s); m.Add("Pickup"_s, "potion"_s); m.Add("Pickup"_s, "food_pretzel"_s); m.Add("Pickup"_s, "food_sandwich"_s); m.Add("Pickup"_s, "food_strawberry"_s); m.Add("Pickup"_s, "carrot_full"_s); m.Add("Object"_s, "powerup_upgrade_blaster_spaz"_s); m.Add("Pickup"_s, "coin_silver"_s); m.Add("Unknown"_s, "green_blast_thing_2"_s); m.Add("Common"_s, "generator"_s); m.Add("Pickup"_s, "stopwatch"_s); m.Add("Pickup"_s, "food_taco"_s); m.Add("Pickup"_s, "food_thing"_s); m.Add("Object"_s, "tnt"_s); m.Add("Pickup"_s, "food_hotdog"_s); m.Add("Pickup"_s, "food_watermelon"_s); m.Add("Object"_s, "container_crate_shrapnel_1"_s); m.Add("Object"_s, "container_crate_shrapnel_2"_s); if (isFull) { m.NextSet(); m.Add("Pinball"_s, "bumper_500"_s); m.Add("Pinball"_s, "bumper_500_hit"_s); m.Add("Pinball"_s, "bumper_carrot"_s); m.Add("Pinball"_s, "bumper_carrot_hit"_s); m.Add("Pinball"_s, "paddle_left"_s, JJ2DefaultPalette::Sprite, false, 1); //m.Add("Pinball"_s, "paddle_right"_s, JJ2DefaultPalette.ByIndex); m.DiscardItems(1); m.NextSet(); m.Add("Platform"_s, "lab"_s); m.Add("Platform"_s, "lab_chain"_s); m.NextSet(); m.Add("Pole"_s, "psych"_s); m.NextSet(); m.Add("Queen"_s, "scream"_s); m.Add("Queen"_s, "ledge"_s); m.Add("Queen"_s, "ledge_recover"_s); m.Add("Queen"_s, "idle"_s); m.Add("Queen"_s, "brick"_s); m.Add("Queen"_s, "fall"_s); m.Add("Queen"_s, "stomp"_s); m.Add("Queen"_s, "backstep"_s); m.NextSet(); m.Add("Rapier"_s, "attack"_s); m.Add("Rapier"_s, "attack_swing"_s); m.Add("Rapier"_s, "idle"_s); m.Add("Rapier"_s, "attack_start"_s); m.Add("Rapier"_s, "attack_end"_s); m.NextSet(); m.Add("Raven"_s, "attack"_s); m.Add("Raven"_s, "idle"_s); m.Add("Raven"_s, "turn"_s); m.NextSet(); m.Add("Robot"_s, "spike_ball"_s); m.Add("Robot"_s, "attack_start"_s); m.Add("Robot"_s, "attack"_s); m.Add("Robot"_s, "copter"_s); m.Add("Robot"_s, "idle"_s); m.Add("Robot"_s, "attack_end"_s); m.Add("Robot"_s, "shrapnel_1"_s); m.Add("Robot"_s, "shrapnel_2"_s); m.Add("Robot"_s, "shrapnel_3"_s); m.Add("Robot"_s, "shrapnel_4"_s); m.Add("Robot"_s, "shrapnel_5"_s); m.Add("Robot"_s, "shrapnel_6"_s); m.Add("Robot"_s, "shrapnel_7"_s); m.Add("Robot"_s, "shrapnel_8"_s); m.Add("Robot"_s, "shrapnel_9"_s); m.Add("Robot"_s, "run"_s); m.Add("Robot"_s, "copter_start"_s); m.Add("Robot"_s, "copter_end"_s); m.NextSet(); m.Add("Object"_s, "rolling_rock"_s); m.NextSet(); // set 80 (1.24) / set 76 (1.23) m.Add("TurtleRocket"_s, "downright"_s); m.Add("TurtleRocket"_s, "upright"_s); m.Add("TurtleRocket"_s, "smoke"_s); m.Add("TurtleRocket"_s, "upright_to_downright"_s); m.NextSet(3); m.Add("Skeleton"_s, "bone"_s); m.Add("Skeleton"_s, "skull"_s); m.Add("Skeleton"_s, "walk"_s); } else { m.NextSet(); } m.NextSet(); m.Add("Pole"_s, "diamondus_tree"_s); if (isFull) { m.NextSet(); m.Add("Common"_s, "snow"_s); m.NextSet(); m.Add("Bolly"_s, "rocket"_s); m.Add("Bolly"_s, "mace_chain"_s); m.Add("Bolly"_s, "bottom"_s); m.Add("Bolly"_s, "top"_s); m.Add("Bolly"_s, "puff"_s); m.Add("Bolly"_s, "mace"_s); m.Add("Bolly"_s, "turret"_s); m.Add("Bolly"_s, "crosshairs"_s); m.NextSet(); m.Add("Platform"_s, "sonic"_s); m.Add("Platform"_s, "sonic_chain"_s); m.NextSet(); m.Add("Sparks"_s, "idle"_s); } m.NextSet(); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unknown_disoriented"_s); m.Add("Spaz"_s, "airboard"_s); m.Add("Spaz"_s, "airboard_turn"_s); m.Add("Spaz"_s, "buttstomp_end"_s); m.Add("Spaz"_s, "corpse"_s); m.Add("Spaz"_s, "die"_s); m.Add("Spaz"_s, "crouch_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "crouch_shoot_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Spaz"_s, "crouch"_s); m.Add("Spaz"_s, "crouch_shoot"_s); m.Add("Spaz"_s, "crouch_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_door_enter"_s); m.Add("Spaz"_s, "vine_walk"_s); m.Add("Spaz"_s, "eol"_s); m.Add("Spaz"_s, "fall"_s); m.Add("Spaz"_s, "buttstomp"_s); m.Add("Spaz"_s, "fall_end"_s); m.Add("Spaz"_s, "shoot"_s); m.Add("Spaz"_s, "shoot_ver"_s); m.Add("Spaz"_s, "shoot_end"_s); m.Add("Spaz"_s, "transform_frog_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "ledge_climb"_s); m.Add("Spaz"_s, "vine_shoot_start"_s); m.Add("Spaz"_s, "vine_shoot_up_end"_s); m.Add("Spaz"_s, "vine_shoot_up"_s); m.Add("Spaz"_s, "vine_idle"_s); m.Add("Spaz"_s, "vine_idle_flavor"_s); m.Add("Spaz"_s, "vine_shoot_end"_s); m.Add("Spaz"_s, "vine_shoot"_s); m.Add("Spaz"_s, "copter"_s); m.Add("Spaz"_s, "copter_shoot_start"_s); m.Add("Spaz"_s, "copter_shoot"_s); m.Add("Spaz"_s, "pole_h"_s); m.Add("Spaz"_s, "hurt"_s); m.Add("Spaz"_s, "idle_flavor_1"_s); m.Add("Spaz"_s, "idle_flavor_2"_s); m.Add("Spaz"_s, "idle_flavor_3_placeholder"_s); m.Add("Spaz"_s, "idle_flavor_4"_s); m.Add("Spaz"_s, "idle_flavor_5"_s); m.Add("Spaz"_s, "vine_shoot_up_start"_s); m.Add("Spaz"_s, "fall_shoot"_s); m.Add("Spaz"_s, "jump_unknown_1"_s); m.Add("Spaz"_s, "jump_unknown_2"_s); m.Add("Spaz"_s, "jump"_s); m.Add("Spaz"_s, "ledge"_s); m.Add("Spaz"_s, "lift"_s); m.Add("Spaz"_s, "lift_end"_s); m.Add("Spaz"_s, "lift_start"_s); m.Add("Spaz"_s, "lookup_start"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_diag_upright"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_ver_up"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_diag_upleft_reverse"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_reverse"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_diag_downleft_reverse"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_ver_down"_s); //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_diag_downright"_s); m.DiscardItems(7, JJ2Version::BaseGame | JJ2Version::HH); m.Add("Spaz"_s, "dizzy_walk"_s); m.Add("Spaz"_s, "push"_s); m.Add("Spaz"_s, "shoot_start"_s); m.Add("Spaz"_s, "revup_start"_s); m.Add("Spaz"_s, "revup"_s); m.Add("Spaz"_s, "revup_end"_s); m.Add("Spaz"_s, "fall_diag"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unknown_mid_frame"_s); m.Add("Spaz"_s, "jump_diag"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_jump_shoot_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_jump_shoot_ver_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_jump_shoot_ver"_s); m.Add("Spaz"_s, "ball"_s); m.Add("Spaz"_s, "run"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_run_aim_diag"_s); m.Add("Spaz"_s, "dash_start"_s); m.Add("Spaz"_s, "dash"_s); m.Add("Spaz"_s, "dash_stop"_s); m.Add("Spaz"_s, "walk_stop"_s); m.Add("Spaz"_s, "run_stop"_s); m.Add("Spaz"_s, "spring"_s); m.Add("Spaz"_s, "idle"_s); m.Add("Spaz"_s, "sidekick"_s); m.Add("Spaz"_s, "sidekick_end"_s); m.Add("Spaz"_s, "sidekick_start"_s); m.Add("Spaz"_s, "dizzy"_s); m.Add("Spaz"_s, "swim_diag_downright"_s); m.Add("Spaz"_s, "swim_right"_s); m.Add("Spaz"_s, "swim_diag_right_to_downright"_s); m.Add("Spaz"_s, "swim_diag_right_to_upright"_s); m.Add("Spaz"_s, "swim_diag_upright"_s); m.Add("Spaz"_s, "swing"_s); m.Add("Spaz"_s, "warp_in"_s); m.Add("Spaz"_s, "warp_out_freefall"_s); m.Add("Spaz"_s, "freefall"_s); m.Add("Spaz"_s, "warp_in_freefall"_s); m.Add("Spaz"_s, "warp_out"_s); m.Add("Spaz"_s, "pole_v"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_crouch_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_crouch_end"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_fall"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_hurt"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_idle"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_jump"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_crouch_end_2"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_lookup_start"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_run"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_unarmed_stare"_s); m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Spaz"_s, "unused_lookup_start_2"_s); m.NextSet(); // set 90 (1.24) / set 86 (1.23) m.Add("Spaz"_s, "idle_flavor_3_start"_s); m.Add("Spaz"_s, "idle_flavor_3"_s); m.Add("Spaz"_s, "idle_flavor_3_bird"_s); m.Add("Spaz"_s, "idle_flavor_5_spaceship"_s); if (isFull) { m.NextSet(); //m.Add("Unimplemented"_s, "bonus_spaz_idle_flavor"_s); //m.Add("Unimplemented"_s, "bonus_spaz_jump"_s); //m.Add("Unimplemented"_s, "bonus_spaz_ball"_s); //m.Add("Unimplemented"_s, "bonus_spaz_run"_s); //m.Add("Unimplemented"_s, "bonus_spaz_dash"_s); //m.Add("Unimplemented"_s, "bonus_spaz_rotate"_s); //m.Add("Unimplemented"_s, "bonus_spaz_idle"_s); m.DiscardItems(7); m.NextSet(2); m.Add("Object"_s, "3d_spike"_s); m.Add("Object"_s, "3d_spike_chain"_s); m.NextSet(); //m.Add("Object"_s, "3d_spike_2"_s); //m.Add("Object"_s, "3d_spike_2_chain"_s); m.DiscardItems(2); m.NextSet(); m.Add("Platform"_s, "spike"_s); m.Add("Platform"_s, "spike_chain"_s); } else { m.NextSet(); } m.NextSet(); m.Add("Spring"_s, "spring_blue_ver"_s); m.Add("Spring"_s, "spring_blue_hor"_s); m.Add("Spring"_s, "spring_blue_ver_reverse"_s); m.Add("Spring"_s, "spring_green_ver_reverse"_s); m.Add("Spring"_s, "spring_red_ver_reverse"_s); m.Add("Spring"_s, "spring_green_ver"_s); m.Add("Spring"_s, "spring_green_hor"_s); m.Add("Spring"_s, "spring_red_ver"_s); m.Add("Spring"_s, "spring_red_hor"_s); m.NextSet(); m.Add("Common"_s, "steam_note"_s); if (isFull) { m.NextSet(); } m.NextSet(); m.Add("Sucker"_s, "walk_top"_s); m.Add("Sucker"_s, "inflated_deflate"_s); m.Add("Sucker"_s, "walk_ver_down"_s); m.Add("Sucker"_s, "fall"_s); m.Add("Sucker"_s, "inflated"_s); m.Add("Sucker"_s, "poof"_s); m.Add("Sucker"_s, "walk"_s); m.Add("Sucker"_s, "walk_ver_up"_s); if (isFull) { m.NextSet(); // set 100 (1.24) / set 96 (1.23) m.Add("TurtleTube"_s, "idle"_s); m.NextSet(); m.Add("TurtleBoss"_s, "attack_start"_s); m.Add("TurtleBoss"_s, "attack_end"_s); m.Add("TurtleBoss"_s, "shell"_s); m.Add("TurtleBoss"_s, "mace"_s); m.Add("TurtleBoss"_s, "idle"_s); m.Add("TurtleBoss"_s, "walk"_s); m.NextSet(); m.Add("TurtleTough"_s, "walk"_s); } m.NextSet(); m.Add("Turtle"_s, "attack"_s); m.Add("Turtle"_s, "idle_flavor"_s); m.Add("Turtle"_s, "turn_start"_s); m.Add("Turtle"_s, "turn_end"_s); m.Add("Turtle"_s, "shell_reverse"_s); m.Add("Turtle"_s, "shell"_s); m.Add("Turtle"_s, "idle"_s); m.Add("Turtle"_s, "walk"_s); if (isFull) { m.NextSet(); m.Add("Tweedle"_s, "magnet_start"_s); m.Add("Tweedle"_s, "spin"_s); m.Add("Tweedle"_s, "magnet_end"_s); m.Add("Tweedle"_s, "shoot_jazz"_s); m.Add("Tweedle"_s, "shoot_spaz"_s); m.Add("Tweedle"_s, "hurt"_s); m.Add("Tweedle"_s, "idle"_s); m.Add("Tweedle"_s, "magnet"_s); m.Add("Tweedle"_s, "walk"_s); m.NextSet(); m.Add("Uterus"_s, "closed_start"_s); m.Add("Crab"_s, "fall"_s); m.Add("Uterus"_s, "closed_idle"_s); m.Add("Uterus"_s, "idle"_s); m.Add("Crab"_s, "fall_end"_s); m.Add("Uterus"_s, "closed_end"_s); m.Add("Uterus"_s, "shield"_s); m.Add("Crab"_s, "walk"_s); m.NextSet(); m.DiscardItems(1); // Red dot m.Add("Object"_s, "vine"_s); m.NextSet(); m.Add("Object"_s, "bonus10"_s); m.NextSet(); m.Add("Object"_s, "bonus100"_s); } m.NextSet(); m.Add("Object"_s, "bonus20"_s); if (isFull) { m.NextSet(); // set 110 (1.24) / set 106 (1.23) m.Add("Object"_s, "bonus50"_s); } m.NextSet(2); m.Add("Witch"_s, "attack"_s); m.Add("Witch"_s, "die"_s); m.Add("Witch"_s, "idle"_s); m.Add("Witch"_s, "bullet_magic"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_throw_fireball"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_appear"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_vanish"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_bullet_fireball"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_idle"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_copter_attack"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_bomb"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_copter_idle"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_copter"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_walk"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_attack"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_idle_flavor"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_turn_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_turn_end"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_shell_reverse"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_shell"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_idle"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_walk"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Doggy"_s, "xmas_attack"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Doggy"_s, "xmas_walk"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Sparks"_s, "ghost_idle"_s); } return m; } AnimSetMapping AnimSetMapping::GetSampleMapping(JJ2Version version) { AnimSetMapping m(version); if (version == JJ2Version::PlusExtension) { // Nothing is here... } else if (version != JJ2Version::Unknown) { bool isFull = ((version & JJ2Version::SharewareDemo) != JJ2Version::SharewareDemo); // set 0 (all) m.Add("Weapon"_s, "shield_water_1"_s); m.Add("Weapon"_s, "shield_water_2"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded_1"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded_2"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded_3"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded_4"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded_5"_s); m.Add("Weapon"_s, "bullet_bouncer_upgraded_6"_s); m.Add("Weapon"_s, "tnt_explosion"_s); m.Add("Weapon"_s, "ricochet_contact"_s); m.Add("Weapon"_s, "ricochet_bullet_1"_s); m.Add("Weapon"_s, "ricochet_bullet_2"_s); m.Add("Weapon"_s, "ricochet_bullet_3"_s); m.Add("Weapon"_s, "shield_fire_noise_1"_s); m.Add("Weapon"_s, "shield_fire_noise_2"_s); m.Add("Weapon"_s, "bullet_bouncer_1"_s); m.Add("Weapon"_s, "bullet_blaster_jazz_1"_s); m.Add("Weapon"_s, "bullet_blaster_jazz_2"_s); m.Add("Weapon"_s, "bullet_blaster_jazz_3"_s); m.Add("Weapon"_s, "bullet_bouncer_2"_s); m.Add("Weapon"_s, "bullet_bouncer_3"_s); m.Add("Weapon"_s, "bullet_bouncer_4"_s); m.Add("Weapon"_s, "bullet_bouncer_5"_s); m.Add("Weapon"_s, "bullet_bouncer_6"_s); m.Add("Weapon"_s, "bullet_bouncer_7"_s); m.Add("Weapon"_s, "bullet_blaster_jazz_4"_s); m.Add("Weapon"_s, "bullet_pepper"_s); m.Add("Weapon"_s, "bullet_freezer_1"_s); m.Add("Weapon"_s, "bullet_freezer_2"_s); m.Add("Weapon"_s, "bullet_freezer_upgraded_1"_s); m.Add("Weapon"_s, "bullet_freezer_upgraded_2"_s); m.Add("Weapon"_s, "bullet_freezer_upgraded_3"_s); m.Add("Weapon"_s, "bullet_freezer_upgraded_4"_s); m.Add("Weapon"_s, "bullet_freezer_upgraded_5"_s); m.Add("Weapon"_s, "bullet_electro_1"_s); m.Add("Weapon"_s, "bullet_electro_2"_s); m.Add("Weapon"_s, "bullet_electro_3"_s); m.Add("Weapon"_s, "bullet_rf"_s); m.Add("Weapon"_s, "bullet_seeker"_s); m.Add("Weapon"_s, "bullet_blaster_spaz_1"_s); m.Add("Weapon"_s, "bullet_blaster_spaz_2"_s); m.Add("Weapon"_s, "bullet_blaster_spaz_3"_s); if (isFull) { m.NextSet(); m.Add("Bat"_s, "noise"_s); m.NextSet(6); m.Add("Bilsy"_s, "appear_2"_s); m.Add("Bilsy"_s, "snap"_s); m.Add("Bilsy"_s, "throw_fireball"_s); m.Add("Bilsy"_s, "fire_start"_s); m.Add("Bilsy"_s, "scary"_s); m.Add("Bilsy"_s, "thunder"_s); m.Add("Bilsy"_s, "appear_1"_s); m.NextSet(4); // set 11 (all) m.Add("Unknown"_s, "unknown_bonus1"_s); m.Add("Unknown"_s, "unknown_bonusblub"_s); } else { m.NextSet(); } m.NextSet(3); // set 14 m.Add("Bubba"_s, "hop_1"_s); m.Add("Bubba"_s, "hop_2"_s); m.Add("Bubba"_s, "unknown_bubbaexplo"_s); m.Add("Bubba"_s, "unknown_frog2"_s); m.Add("Bubba"_s, "unknown_frog3"_s); m.Add("Bubba"_s, "unknown_frog4"_s); m.Add("Bubba"_s, "unknown_frog5"_s); m.Add("Bubba"_s, "sneeze"_s); m.Add("Bubba"_s, "tornado"_s); m.NextSet(); // set 15 m.Add("Bee"_s, "noise"_s); if (isFull) { m.NextSet(3); } m.NextSet(2); // set 20 (all) m.Add("Caterpillar"_s, "dizzy"_s); if (isFull) { m.NextSet(); } m.NextSet(); m.Add("Common"_s, "char_airboard"_s); m.Add("Common"_s, "char_airboard_turn_1"_s); m.Add("Common"_s, "char_airboard_turn_2"_s); m.Add("Common"_s, "unknown_base"_s); m.Add("Common"_s, "powerup_shield_damage_1"_s); m.Add("Common"_s, "powerup_shield_damage_2"_s); m.Add("Common"_s, "bomb"_s); m.Add("Birdy"_s, "fly_1"_s); m.Add("Birdy"_s, "fly_2"_s); m.Add("Weapon"_s, "bouncer"_s); m.Add("Common"_s, "blub1"_s); m.Add("Weapon"_s, "shield_water_bullet"_s); m.Add("Weapon"_s, "shield_fire_bullet"_s); m.Add("Common"_s, "ambient_fire"_s); m.Add("Object"_s, "container_barrel_break"_s); m.Add("Common"_s, "powerup_shield_timer"_s); m.Add("Pickup"_s, "coin"_s); m.Add("Common"_s, "scenery_collapse"_s); m.Add("Common"_s, "cup"_s); m.Add("Common"_s, "scenery_destruct"_s); m.Add("Common"_s, "down"_s); m.Add("Common"_s, "downfl2"_s); m.Add("Pickup"_s, "food_drink_1"_s); m.Add("Pickup"_s, "food_drink_2"_s); m.Add("Pickup"_s, "food_drink_3"_s); m.Add("Pickup"_s, "food_drink_4"_s); m.Add("Pickup"_s, "food_edible_1"_s); m.Add("Pickup"_s, "food_edible_2"_s); m.Add("Pickup"_s, "food_edible_3"_s); m.Add("Pickup"_s, "food_edible_4"_s); m.Add("Pickup"_s, "shield_lightning_bullet_1"_s); m.Add("Pickup"_s, "shield_lightning_bullet_2"_s); m.Add("Pickup"_s, "shield_lightning_bullet_3"_s); m.Add("Weapon"_s, "tnt"_s); m.Add("Weapon"_s, "wall_poof"_s); m.Add("Weapon"_s, "toaster"_s); m.Add("Common"_s, "flap"_s); m.Add("Common"_s, "swish_9"_s); m.Add("Common"_s, "swish_10"_s); m.Add("Common"_s, "swish_11"_s); m.Add("Common"_s, "swish_12"_s); m.Add("Common"_s, "swish_13"_s); m.Add("Object"_s, "gem_giant_break"_s); m.Add("Object"_s, "powerup_break"_s); m.Add("Common"_s, "gunsm1"_s); m.Add("Pickup"_s, "1up"_s); m.Add("Unknown"_s, "common_head"_s); m.Add("Common"_s, "copter_noise"_s); m.Add("Common"_s, "hibell"_s); m.Add("Common"_s, "holyflut"_s); m.Add("UI"_s, "weapon_change"_s); m.Add("Common"_s, "ice_break"_s); m.Add("Object"_s, "shell_noise_1"_s); m.Add("Object"_s, "shell_noise_2"_s); m.Add("Object"_s, "shell_noise_3"_s); m.Add("Object"_s, "shell_noise_4"_s); m.Add("Object"_s, "shell_noise_5"_s); m.Add("Object"_s, "shell_noise_6"_s); m.Add("Object"_s, "shell_noise_7"_s); m.Add("Object"_s, "shell_noise_8"_s); m.Add("Object"_s, "shell_noise_9"_s); m.Add("Unknown"_s, "common_itemtre"_s); m.Add("Common"_s, "char_jump"_s); m.Add("Common"_s, "char_jump_alt"_s); m.Add("Common"_s, "land1"_s); m.Add("Common"_s, "land2"_s); m.Add("Common"_s, "land3"_s); m.Add("Common"_s, "land4"_s); m.Add("Common"_s, "land5"_s); m.Add("Common"_s, "char_land"_s); m.Add("Common"_s, "loadjazz"_s); m.Add("Common"_s, "loadspaz"_s); m.Add("Common"_s, "metalhit"_s); m.Add("Unimplemented"_s, "powerup_jazz1_style"_s); m.Add("Object"_s, "bonus_not_enough_coins"_s); m.Add("Pickup"_s, "gem"_s); m.Add("Pickup"_s, "ammo"_s); m.Add("Common"_s, "pistol1"_s); m.Add("Common"_s, "plop_5"_s); m.Add("Common"_s, "plop_1"_s); m.Add("Common"_s, "plop_2"_s); m.Add("Common"_s, "plop_3"_s); m.Add("Common"_s, "plop_4"_s); m.Add("Common"_s, "plop_6"_s); m.Add("Spaz"_s, "idle_flavor_4_spaceship"_s); m.Add("Common"_s, "copter_pre"_s); m.Add("Common"_s, "char_revup"_s); m.Add("Common"_s, "ringgun1"_s); m.Add("Common"_s, "ringgun2"_s); m.Add("Weapon"_s, "shield_lightning_noise_1"_s); m.Add("Weapon"_s, "shield_lightning_noise_2"_s); m.Add("Weapon"_s, "shield_lightning_noise_3"_s); m.Add("Common"_s, "shldof3"_s); m.Add("Common"_s, "slip"_s); m.Add("Common"_s, "splat_1"_s); m.Add("Common"_s, "splat_2"_s); m.Add("Common"_s, "splat_3"_s); m.Add("Common"_s, "splat_4"_s); m.Add("Common"_s, "splat_5"_s); m.Add("Common"_s, "splat_6"_s); m.Add("Spring"_s, "spring_2"_s); m.Add("Common"_s, "steam_low"_s); m.Add("Common"_s, "step"_s); m.Add("Common"_s, "stretch"_s); m.Add("Common"_s, "swish_1"_s); m.Add("Common"_s, "swish_2"_s); m.Add("Common"_s, "swish_3"_s); m.Add("Common"_s, "swish_4"_s); m.Add("Common"_s, "swish_5"_s); m.Add("Common"_s, "swish_6"_s); m.Add("Common"_s, "swish_7"_s); m.Add("Common"_s, "swish_8"_s); m.Add("Common"_s, "warp_in"_s); m.Add("Common"_s, "warp_out"_s); m.Add("Common"_s, "char_double_jump"_s); m.Add("Common"_s, "water_splash"_s); m.Add("Object"_s, "container_crate_break"_s); if (isFull) { m.NextSet(2); m.Add("Demon"_s, "attack"_s); m.NextSet(3); m.Add("Devan"_s, "spit_fireball"_s); m.Add("Devan"_s, "flap"_s); m.Add("Devan"_s, "unknown_frog4"_s); m.Add("Devan"_s, "jump_up"_s); m.Add("Devan"_s, "laugh"_s); m.Add("Devan"_s, "shoot"_s); m.Add("Devan"_s, "transform_demon_stretch_2"_s); m.Add("Devan"_s, "transform_demon_stretch_4"_s); m.Add("Devan"_s, "transform_demon_stretch_1"_s); m.Add("Devan"_s, "transform_demon_stretch_3"_s); m.Add("Devan"_s, "unknown_vanish"_s); m.Add("Devan"_s, "unknown_whistledescending2"_s); m.Add("Devan"_s, "transform_demon_wings"_s); m.NextSet(2); m.Add("Doggy"_s, "attack"_s); m.Add("Doggy"_s, "noise"_s); m.Add("Doggy"_s, "woof_1"_s); m.Add("Doggy"_s, "woof_2"_s); m.Add("Doggy"_s, "woof_3"_s); } else { m.NextSet(2); } m.NextSet(2); // set 31 (all) m.Add("Dragonfly"_s, "noise"_s); if (isFull) { m.NextSet(2); m.Add("Cinematics"_s, "ending_eva_thankyou"_s); } m.NextSet(); m.Add("Jazz"_s, "level_complete"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "level_complete"_s); m.NextSet(); m.Add("Spaz"_s, "level_complete"_s); m.NextSet(); //m.Add("Cinematics"_s, "logo_epic_1"_s); //m.Add("Cinematics"_s, "logo_epic_2"_s); m.DiscardItems(2); m.NextSet(); m.Add("Eva"_s, "Kiss1"_s); m.Add("Eva"_s, "Kiss2"_s); m.Add("Eva"_s, "Kiss3"_s); m.Add("Eva"_s, "Kiss4"_s); if (isFull) { m.NextSet(2); // set 40 (1.24) / set 39 (1.23) m.Add("Unknown"_s, "unknown_fan"_s); m.NextSet(); m.Add("FatChick"_s, "attack_1"_s); m.Add("FatChick"_s, "attack_2"_s); m.Add("FatChick"_s, "attack_3"_s); m.NextSet(); m.Add("Fencer"_s, "attack"_s); m.NextSet(); } m.NextSet(4); m.Add("Frog"_s, "noise_1"_s); m.Add("Frog"_s, "noise_2"_s); m.Add("Frog"_s, "noise_3"_s); m.Add("Frog"_s, "noise_4"_s); m.Add("Frog"_s, "noise_5"_s); m.Add("Frog"_s, "noise_6"_s); m.Add("Frog"_s, "transform"_s); m.Add("Frog"_s, "tongue"_s); if (isFull) { m.NextSet(3); // set 50 (1.24) / set 49 (1.23) m.Add("Unimplemented"_s, "boxing_glove_hit"_s); m.NextSet(); } m.NextSet(); m.Add("MadderHatter"_s, "cup"_s); m.Add("MadderHatter"_s, "hat"_s); m.Add("MadderHatter"_s, "spit"_s); m.Add("MadderHatter"_s, "splash_1"_s); m.Add("MadderHatter"_s, "splash_2"_s); if (isFull) { m.NextSet(); } m.NextSet(); m.Add("Cinematics"_s, "opening_blow"_s); m.Add("Cinematics"_s, "opening_boom_1"_s); m.Add("Cinematics"_s, "opening_boom_2"_s); m.Add("Cinematics"_s, "opening_brake"_s); m.Add("Cinematics"_s, "opening_end_shoot"_s); m.Add("Cinematics"_s, "opening_rope_grab"_s); m.Add("Cinematics"_s, "opening_sweep_1"_s); m.Add("Cinematics"_s, "opening_sweep_2"_s); m.Add("Cinematics"_s, "opening_sweep_3"_s); m.Add("Cinematics"_s, "opening_gun_noise_1"_s); m.Add("Cinematics"_s, "opening_gun_noise_2"_s); m.Add("Cinematics"_s, "opening_gun_noise_3"_s); m.Add("Cinematics"_s, "opening_helicopter"_s); m.Add("Cinematics"_s, "opening_hit_spaz"_s); m.Add("Cinematics"_s, "opening_hit_turtle"_s); m.Add("Cinematics"_s, "opening_vo_1"_s); m.Add("Cinematics"_s, "opening_gun_blow"_s); m.Add("Cinematics"_s, "opening_insect"_s); m.Add("Cinematics"_s, "opening_trolley_push"_s); m.Add("Cinematics"_s, "opening_land"_s); m.Add("Cinematics"_s, "opening_turtle_growl"_s); m.Add("Cinematics"_s, "opening_turtle_grunt"_s); m.Add("Cinematics"_s, "opening_rock"_s); m.Add("Cinematics"_s, "opening_rope_1"_s); m.Add("Cinematics"_s, "opening_rope_2"_s); m.Add("Cinematics"_s, "opening_run"_s); m.Add("Cinematics"_s, "opening_shot"_s); m.Add("Cinematics"_s, "opening_shot_grn"_s); m.Add("Cinematics"_s, "opening_slide"_s); m.Add("Cinematics"_s, "opening_end_sfx"_s); m.Add("Cinematics"_s, "opening_swish_1"_s); m.Add("Cinematics"_s, "opening_swish_2"_s); m.Add("Cinematics"_s, "opening_swish_3"_s); m.Add("Cinematics"_s, "opening_swish_4"_s); m.Add("Cinematics"_s, "opening_turtle_ugh"_s); m.Add("Cinematics"_s, "opening_up_1"_s); m.Add("Cinematics"_s, "opening_up_2"_s); m.Add("Cinematics"_s, "opening_wind"_s); if (isFull) { m.NextSet(); } m.NextSet(2); m.Add("Jazz"_s, "ledge"_s); m.Add("Jazz"_s, "hurt_1"_s); m.Add("Jazz"_s, "hurt_2"_s); m.Add("Jazz"_s, "hurt_3"_s); m.Add("Jazz"_s, "hurt_4"_s); m.Add("Jazz"_s, "idle_flavor_3"_s); m.Add("Jazz"_s, "hurt_5"_s); m.Add("Jazz"_s, "hurt_6"_s); m.Add("Jazz"_s, "hurt_7"_s); m.Add("Jazz"_s, "hurt_8"_s); m.Add("Jazz"_s, "carrot"_s); m.Add("Jazz"_s, "idle_flavor_4"_s); if (isFull) { m.NextSet(); } m.NextSet(); m.Add("LabRat"_s, "attack"_s); m.Add("LabRat"_s, "noise_1"_s); m.Add("LabRat"_s, "noise_2"_s); m.Add("LabRat"_s, "noise_3"_s); m.Add("LabRat"_s, "noise_4"_s); m.Add("LabRat"_s, "noise_5"_s); m.NextSet(); // set 60 (1.24) / set 59 (1.23) m.Add("Lizard"_s, "noise_1"_s); m.Add("Lizard"_s, "noise_2"_s); m.Add("Lizard"_s, "noise_3"_s); m.Add("Lizard"_s, "noise_4"_s); m.NextSet(3, JJ2Version::TSF | JJ2Version::CC); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "die"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_3"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_4"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_5"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_6"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_7"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "hurt_8"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "unknown_mic1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "unknown_mic2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "sidekick"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "fall"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_3"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "jump_4"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "unused_touch"_s); m.Add(JJ2Version::TSF | JJ2Version::CC, "Lori"_s, "yahoo"_s); m.NextSet(3); m.Add("UI"_s, "select_1"_s); m.Add("UI"_s, "select_2"_s); m.Add("UI"_s, "select_3"_s); m.Add("UI"_s, "select_4"_s); m.Add("UI"_s, "select_5"_s); m.Add("UI"_s, "select_6"_s); m.Add("UI"_s, "select_7"_s); m.Add("UI"_s, "type_char"_s); m.Add("UI"_s, "type_enter"_s); if (isFull) { m.NextSet(); m.Add("Monkey"_s, "banana_splat"_s); m.Add("Monkey"_s, "banana_throw"_s); m.NextSet(); m.Add("Moth"_s, "flap"_s); } m.NextSet(); //m.Add("Cinematics"_s, "orangegames_1_boom_l"_s); //m.Add("Cinematics"_s, "orangegames_1_boom_r"_s); //m.Add("Cinematics"_s, "orangegames_7_bubble_l"_s); //m.Add("Cinematics"_s, "orangegames_7_bubble_r"_s); //m.Add("Cinematics"_s, "orangegames_2_glass_1_l"_s); //m.Add("Cinematics"_s, "orangegames_2_glass_1_r"_s); //m.Add("Cinematics"_s, "orangegames_5_glass_2_l"_s); //m.Add("Cinematics"_s, "orangegames_5_glass_2_r"_s); //m.Add("Cinematics"_s, "orangegames_6_merge"_s); //m.Add("Cinematics"_s, "orangegames_3_sweep_1_l"_s); //m.Add("Cinematics"_s, "orangegames_3_sweep_1_r"_s); //m.Add("Cinematics"_s, "orangegames_4_sweep_2_l"_s); //m.Add("Cinematics"_s, "orangegames_4_sweep_2_r"_s); //m.Add("Cinematics"_s, "orangegames_5_sweep_3_l"_s); //m.Add("Cinematics"_s, "orangegames_5_sweep_3_r"_s); m.DiscardItems(15); m.NextSet(); // set 70 (1.24) / set 66 (1.23) //m.Add("Cinematics"_s, "project2_unused_crunch"_s); //m.Add("Cinematics"_s, "project2_10_fart"_s); //m.Add("Cinematics"_s, "project2_unused_foew1"_s); //m.Add("Cinematics"_s, "project2_unused_foew4"_s); //m.Add("Cinematics"_s, "project2_unused_foew5"_s); //m.Add("Cinematics"_s, "project2_unused_frog1"_s); //m.Add("Cinematics"_s, "project2_unused_frog2"_s); //m.Add("Cinematics"_s, "project2_unused_frog3"_s); //m.Add("Cinematics"_s, "project2_unused_frog4"_s); //m.Add("Cinematics"_s, "project2_unused_frog5"_s); //m.Add("Cinematics"_s, "project2_unused_kiss4"_s); //m.Add("Cinematics"_s, "project2_unused_open"_s); //m.Add("Cinematics"_s, "project2_unused_pinch1"_s); //m.Add("Cinematics"_s, "project2_unused_pinch2"_s); //m.Add("Cinematics"_s, "project2_3_plop_1"_s); //m.Add("Cinematics"_s, "project2_4_plop_2"_s); //m.Add("Cinematics"_s, "project2_5_plop_3"_s); //m.Add("Cinematics"_s, "project2_6_plop_4"_s); //m.Add("Cinematics"_s, "project2_7_plop_5"_s); //m.Add("Cinematics"_s, "project2_9_spit"_s); //m.Add("Cinematics"_s, "project2_unused_splout"_s); //m.Add("Cinematics"_s, "project2_2_splat"_s); //m.Add("Cinematics"_s, "project2_1_8_throw"_s); //m.Add("Cinematics"_s, "project2_unused_tong"_s); m.DiscardItems(24); m.NextSet(); m.Add("Object"_s, "checkpoint_open"_s); m.Add("Object"_s, "copter"_s); m.Add("Unknown"_s, "unknown_pickup_stretch1a"_s); if (isFull) { m.NextSet(); m.Add("Pinball"_s, "bumper_hit"_s); m.Add("Pinball"_s, "paddle_hit_1"_s); m.Add("Pinball"_s, "paddle_hit_2"_s); m.Add("Pinball"_s, "paddle_hit_3"_s); m.Add("Pinball"_s, "paddle_hit_4"_s); m.NextSet(3); m.Add("Queen"_s, "spring"_s); m.Add("Queen"_s, "scream"_s); m.NextSet(); m.Add("Rapier"_s, "die"_s); m.Add("Rapier"_s, "noise_1"_s); m.Add("Rapier"_s, "noise_2"_s); m.Add("Rapier"_s, "noise_3"_s); m.Add("Rapier"_s, "clunk"_s); m.NextSet(2); m.Add("Robot"_s, "unknown_big1"_s); m.Add("Robot"_s, "unknown_big2"_s); m.Add("Robot"_s, "unknown_can1"_s); m.Add("Robot"_s, "unknown_can2"_s); m.Add("Robot"_s, "attack_start"_s); m.Add("Robot"_s, "attack_end"_s); m.Add("Robot"_s, "attack"_s); m.Add("Robot"_s, "unknown_hydropuf"_s); m.Add("Robot"_s, "unknown_idle1"_s); m.Add("Robot"_s, "unknown_idle2"_s); m.Add("Robot"_s, "unknown_jmpcan1"_s); m.Add("Robot"_s, "unknown_jmpcan10"_s); m.Add("Robot"_s, "unknown_jmpcan2"_s); m.Add("Robot"_s, "unknown_jmpcan3"_s); m.Add("Robot"_s, "unknown_jmpcan4"_s); m.Add("Robot"_s, "unknown_jmpcan5"_s); m.Add("Robot"_s, "unknown_jmpcan6"_s); m.Add("Robot"_s, "unknown_jmpcan7"_s); m.Add("Robot"_s, "unknown_jmpcan8"_s); m.Add("Robot"_s, "unknown_jmpcan9"_s); m.Add("Robot"_s, "shrapnel_1"_s); m.Add("Robot"_s, "shrapnel_2"_s); m.Add("Robot"_s, "shrapnel_3"_s); m.Add("Robot"_s, "shrapnel_4"_s); m.Add("Robot"_s, "shrapnel_5"_s); m.Add("Robot"_s, "attack_start_shutter"_s); m.Add("Robot"_s, "unknown_out"_s); m.Add("Robot"_s, "unknown_poep"_s); m.Add("Robot"_s, "unknown_pole"_s); m.Add("Robot"_s, "unknown_shoot"_s); m.Add("Robot"_s, "walk_1"_s); m.Add("Robot"_s, "walk_2"_s); m.Add("Robot"_s, "walk_3"_s); m.NextSet(); m.Add("Object"_s, "rolling_rock"_s); m.NextSet(); } m.NextSet(); // set 81 (1.24) / set 77 (1.23) if (isFull) { //m.Add(JJ2Version::BaseGame | JJ2Version::HH, "Unknown"_s, "sugar_rush_heartbeat"_s); m.DiscardItems(1, JJ2Version::BaseGame | JJ2Version::HH); } m.Add("Common"_s, "sugar_rush"_s); if (isFull) { m.NextSet(); m.Add("Common"_s, "science_noise"_s); m.NextSet(); m.Add("Skeleton"_s, "bone_1"_s); m.Add("Skeleton"_s, "bone_2"_s); m.Add("Skeleton"_s, "bone_3"_s); m.Add("Skeleton"_s, "bone_4"_s); m.Add("Skeleton"_s, "bone_5"_s); m.Add("Skeleton"_s, "bone_6"_s); } m.NextSet(); m.Add("Pole"_s, "fall_start"_s); m.Add("Pole"_s, "fall_end"_s); //m.Add("Pole"_s, "fall_3"_s); m.DiscardItems(1); if (isFull) { m.NextSet(2); m.Add("Bolly"_s, "missile_1"_s); m.Add("Bolly"_s, "missile_2"_s); m.Add("Bolly"_s, "missile_3"_s); m.Add("Bolly"_s, "noise"_s); m.Add("Bolly"_s, "lock_on"_s); m.NextSet(3); } m.NextSet(3); // set 92 (1.24) / set 88 (1.23) m.Add("Spaz"_s, "hurt_1"_s); m.Add("Spaz"_s, "hurt_2"_s); m.Add("Spaz"_s, "idle_flavor_3_bird_land"_s); m.Add("Spaz"_s, "idle_flavor_4"_s); m.Add("Spaz"_s, "idle_flavor_3_bird"_s); m.Add("Spaz"_s, "idle_flavor_3_eat"_s); m.Add("Spaz"_s, "jump_1"_s); m.Add("Spaz"_s, "jump_2"_s); m.Add("Spaz"_s, "idle_flavor_2"_s); m.Add("Spaz"_s, "hihi"_s); m.Add("Spaz"_s, "spring_1"_s); m.Add("Spaz"_s, "double_jump"_s); m.Add("Spaz"_s, "sidekick_1"_s); m.Add("Spaz"_s, "sidekick_2"_s); m.Add("Spaz"_s, "spring_2"_s); m.Add("Spaz"_s, "oooh"_s); m.Add("Spaz"_s, "ledge"_s); m.Add("Spaz"_s, "jump_3"_s); m.Add("Spaz"_s, "jump_4"_s); if (isFull) { m.NextSet(3); } m.NextSet(); m.Add("Spring"_s, "spring_ver_down"_s); m.Add("Spring"_s, "spring"_s); m.NextSet(); m.Add("Common"_s, "steam_note"_s); if (isFull) { m.NextSet(); m.Add("Unimplemented"_s, "dizzy"_s); } m.NextSet(); m.Add("Sucker"_s, "deflate"_s); m.Add("Sucker"_s, "pinch_1"_s); m.Add("Sucker"_s, "pinch_2"_s); m.Add("Sucker"_s, "pinch_3"_s); m.Add("Sucker"_s, "plop_1"_s); m.Add("Sucker"_s, "plop_2"_s); m.Add("Sucker"_s, "plop_3"_s); m.Add("Sucker"_s, "plop_4"_s); m.Add("Sucker"_s, "up"_s); if (isFull) { m.NextSet(2); // set 101 (1.24) / set 97 (1.23) m.Add("TurtleBoss"_s, "attack_start"_s); m.Add("TurtleBoss"_s, "attack_end"_s); m.Add("TurtleBoss"_s, "mace"_s); m.NextSet(); } m.NextSet(); m.Add("Turtle"_s, "attack_bite"_s); m.Add("Turtle"_s, "turn_start"_s); m.Add("Turtle"_s, "shell_collide"_s); m.Add("Turtle"_s, "idle_1"_s); m.Add("Turtle"_s, "idle_2"_s); m.Add("Turtle"_s, "attack_neck"_s); m.Add("Turtle"_s, "noise_1"_s); m.Add("Turtle"_s, "noise_2"_s); m.Add("Turtle"_s, "noise_3"_s); m.Add("Turtle"_s, "noise_4"_s); m.Add("Turtle"_s, "turn_end"_s); if (isFull) { m.NextSet(2); m.Add("Uterus"_s, "closed_start"_s); m.Add("Uterus"_s, "closed_end"_s); m.Add("Crab"_s, "noise_1"_s); m.Add("Crab"_s, "noise_2"_s); m.Add("Crab"_s, "noise_3"_s); m.Add("Crab"_s, "noise_4"_s); m.Add("Crab"_s, "noise_5"_s); m.Add("Crab"_s, "noise_6"_s); m.Add("Crab"_s, "noise_7"_s); m.Add("Crab"_s, "noise_8"_s); m.Add("Uterus"_s, "scream"_s); m.Add("Crab"_s, "step_1"_s); m.Add("Crab"_s, "step_2"_s); m.NextSet(4); } m.NextSet(2); // set 111 (1.24) / set 107 (1.23) m.Add("Common"_s, "wind"_s); m.NextSet(); m.Add("Witch"_s, "laugh"_s); m.Add("Witch"_s, "magic"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_appear_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_snap"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_throw_fireball"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_fire_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_scary"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_thunder"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Bilsy"_s, "xmas_appear_1"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_noise_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_noise_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_noise_3"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Lizard"_s, "xmas_noise_4"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_attack_bite"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_turn_start"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_shell_collide"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_idle_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_idle_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_attack_neck"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_noise_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_noise_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_noise_3"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_noise_4"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Turtle"_s, "xmas_turn_end"_s); m.NextSet(1, JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Doggy"_s, "xmas_attack"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Doggy"_s, "xmas_noise"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Doggy"_s, "xmas_woof_1"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Doggy"_s, "xmas_woof_2"_s); m.Add(JJ2Version::TSF | JJ2Version::CC | JJ2Version::HH, "Doggy"_s, "xmas_woof_3"_s); } return m; } void AnimSetMapping::DiscardItems(std::uint32_t advanceBy, JJ2Version appliesTo) { if ((_version & appliesTo) != JJ2Version::Unknown) { for (std::uint32_t i = 0; i < advanceBy; i++) { Entry entry; entry.Category = Discard; std::int32_t key = (_currentSet << 16) | _currentItem; _entries.emplace(key, std::move(entry)); _currentItem++; _currentOrdinal++; } } } void AnimSetMapping::SkipItems(std::uint32_t advanceBy) { _currentItem += advanceBy; _currentOrdinal += advanceBy; } void AnimSetMapping::NextSet(std::uint32_t advanceBy, JJ2Version appliesTo) { if ((_version & appliesTo) != JJ2Version::Unknown) { _currentSet += advanceBy; _currentItem = 0; } } void AnimSetMapping::Add(JJ2Version appliesTo, StringView category, StringView name, JJ2DefaultPalette palette, bool skipNormalMap, bool allowRealtimePalette) { if ((_version & appliesTo) != JJ2Version::Unknown) { Entry entry; entry.Category = category; entry.Name = name; entry.Ordinal = _currentOrdinal; entry.Palette = palette; entry.SkipNormalMap = skipNormalMap; entry.AllowRealtimePalette = allowRealtimePalette; std::uint32_t key = (_currentSet << 16) | _currentItem; _entries.emplace(key, std::move(entry)); _currentItem++; _currentOrdinal++; } } void AnimSetMapping::Add(StringView category, StringView name, JJ2DefaultPalette palette, bool skipNormalMap, bool allowRealtimePalette) { Entry entry; entry.Category = category; entry.Name = name; entry.Ordinal = _currentOrdinal; entry.Palette = palette; entry.SkipNormalMap = skipNormalMap; entry.AllowRealtimePalette = allowRealtimePalette; std::uint32_t key = (_currentSet << 16) | _currentItem; _entries.emplace(key, std::move(entry)); _currentItem++; _currentOrdinal++; } AnimSetMapping::Entry* AnimSetMapping::Get(std::uint32_t set, std::uint32_t item) { if (set > UINT16_MAX || item > UINT16_MAX) { return nullptr; } std::uint32_t key = (set << 16) | item; auto it = _entries.find(key); if (it != _entries.end()) { return &it->second; } else { return nullptr; } } AnimSetMapping::Entry* AnimSetMapping::GetByOrdinal(std::uint32_t index) { for (auto& [key, entry] : _entries) { if (entry.Ordinal == index) { return &entry; } } return nullptr; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/AnimSetMapping.h000066400000000000000000000040041512772601700273670ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Version.h" #include "../../nCine/Base/HashMap.h" #include #include #include using namespace Death::Containers; using namespace Death::Containers::Literals; using namespace nCine; namespace Jazz2::Compatibility { /** @brief Default palette for original animation */ enum class JJ2DefaultPalette { Sprite, Menu }; /** @brief Maps indices from original data file to organized entries */ class AnimSetMapping { public: /** @{ @name Constants */ /** @brief Specifies that the entry should be discarded */ static constexpr char Discard[] = ":discard"; /** @} */ /** @brief Mapped entry */ struct Entry { String Category; String Name; std::uint32_t Ordinal; JJ2DefaultPalette Palette; bool SkipNormalMap; bool AllowRealtimePalette; }; Entry* Get(std::uint32_t set, std::uint32_t item); Entry* GetByOrdinal(std::uint32_t index); /** @brief Returns mapping of animations for the specified version */ static AnimSetMapping GetAnimMapping(JJ2Version version); /** @brief Returns mapping of sounds for the specified version */ static AnimSetMapping GetSampleMapping(JJ2Version version); private: JJ2Version _version; HashMap _entries; std::uint32_t _currentItem; std::uint32_t _currentSet; std::uint32_t _currentOrdinal; AnimSetMapping(JJ2Version version); void DiscardItems(std::uint32_t advanceBy, JJ2Version appliesTo = JJ2Version::All); void SkipItems(std::uint32_t advanceBy = 1); void NextSet(std::uint32_t advanceBy = 1, JJ2Version appliesTo = JJ2Version::All); void Add(JJ2Version appliesTo, StringView category, StringView name, JJ2DefaultPalette palette = JJ2DefaultPalette::Sprite, bool skipNormalMap = false, bool allowRealtimePalette = false); void Add(StringView category, StringView name, JJ2DefaultPalette palette = JJ2DefaultPalette::Sprite, bool skipNormalMap = false, bool allowRealtimePalette = false); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/EventConverter.cpp000066400000000000000000001303051512772601700300230ustar00rootroot00000000000000#include "EventConverter.h" #include "JJ2Level.h" #include "../Direction.h" #include "../Actors/Collectibles/FoodCollectible.h" using FoodType = Jazz2::Actors::Collectibles::FoodType; namespace Jazz2::Compatibility { EventConverter::EventConverter() { AddDefaultConverters(); } ConversionResult EventConverter::TryConvert(JJ2Level* level, JJ2Event old, std::uint32_t eventParams) { auto it = _converters.find(old); if (it != _converters.end()) { return it->second(level, eventParams); } else { return { EventType::Empty }; } } void EventConverter::Add(JJ2Event originalEvent, ConversionFunction&& converter) { if (_converters.contains(originalEvent)) { LOGW("Converter for event {} is already defined", originalEvent); } _converters[originalEvent] = std::move(converter); } void EventConverter::Override(JJ2Event originalEvent, ConversionFunction&& converter) { _converters[originalEvent] = std::move(converter); } EventConverter::ConversionFunction EventConverter::NoParamList(EventType ev) { return [ev](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { return { ev }; }; } EventConverter::ConversionFunction EventConverter::ConstantParamList(EventType ev, const std::array& eventParams) { return [ev, eventParams](JJ2Level* level, std::uint32_t jj2Params) mutable -> ConversionResult { ConversionResult result; result.Type = ev; std::int32_t i = 0; for (auto& param : eventParams) { result.Params[i++] = param; } return result; }; } EventConverter::ConversionFunction EventConverter::ParamIntToParamList(EventType ev, const std::array, 6>& paramDefs) { return [ev, paramDefs](JJ2Level* level, std::uint32_t jj2Params) mutable -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, arrayView(paramDefs.data(), paramDefs.size()), eventParams); ConversionResult result; result.Type = ev; std::memcpy(result.Params, eventParams, sizeof(result.Params)); return result; }; } void EventConverter::ConvertParamInt(uint32_t paramInt, const ArrayView>& paramTypes, std::uint8_t eventParams[16]) { std::int32_t i = 0; for (auto& param : paramTypes) { if (param.second() == 0) { continue; } switch (param.first()) { case JJ2ParamBool: { eventParams[i++] = (std::uint8_t)(paramInt & 1); paramInt = paramInt >> 1; break; } case JJ2ParamUInt: { std::uint32_t value = (paramInt & ((1 << param.second()) - 1)); if (param.second() > 8) { *(std::uint16_t*)&eventParams[i] = (std::uint16_t)value; i += 2; } else { eventParams[i++] = (std::uint8_t)value; } paramInt = paramInt >> param.second(); break; } case JJ2ParamInt: { std::uint32_t value = (paramInt & ((1 << param.second()) - 1)); // Complement of two, with variable bit length std::int32_t highestBitValue = (1 << (param.second() - 1)); if (value >= highestBitValue) { value = (uint32_t)(-highestBitValue + (value - highestBitValue)); } if (param.second() > 8) { *(std::uint16_t*)&eventParams[i] = (std::uint16_t)value; i += 2; } else { eventParams[i++] = (std::uint8_t)value; } paramInt = paramInt >> param.second(); break; } default: DEATH_ASSERT(false, "Unknown JJ2Param type specified", ); break; } } } void EventConverter::AddDefaultConverters() { Add(JJ2Event::EMPTY, NoParamList(EventType::Empty)); // Basic events Add(JJ2Event::JAZZ_LEVEL_START, ConstantParamList(EventType::LevelStart, { 0x01 /*Jazz*/ })); Add(JJ2Event::SPAZ_LEVEL_START, ConstantParamList(EventType::LevelStart, { 0x02 /*Spaz*/ })); Add(JJ2Event::LORI_LEVEL_START, ConstantParamList(EventType::LevelStart, { 0x04 /*Lori*/ })); Add(JJ2Event::MP_LEVEL_START, ParamIntToParamList(EventType::LevelStartMultiplayer, {{ { JJ2ParamUInt, 2 } // Team (JJ2+) }})); Add(JJ2Event::SAVE_POINT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { // Green xmas-themed checkpoints for some levels bool isXmas = (level->Tileset.find("xmas"_s) != nullptr); std::uint8_t theme = (isXmas ? 1 : 0); return { EventType::Checkpoint, { theme } }; }); // Scenery events Add(JJ2Event::SCENERY_DESTRUCT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 10 }, // Empty { JJ2ParamUInt, 5 }, // Speed { JJ2ParamUInt, 4 } // Weapon }, eventParams); if (eventParams[2] > 0) { return { EventType::SceneryDestructSpeed, { (std::uint8_t)(eventParams[2] + 4) } }; } else { std::uint16_t weaponMask; if (eventParams[3] == 0) { // Allow all weapons except Freezer weaponMask = UINT16_MAX & ~(1 << (std::uint16_t)WeaponType::Freezer); } else { weaponMask = (1 << ((std::uint16_t)eventParams[3] - 1)); // Fixed TNT blocks in `xmas3.j2l` (https://github.com/deathkiller/jazz2/issues/117) if (eventParams[3] == 9 && level->LevelName == "xmas3"_s) { weaponMask |= (1 << (std::uint16_t)WeaponType::TNT); } } return { EventType::SceneryDestruct, { (std::uint8_t)(weaponMask & 0xff), (std::uint8_t)((weaponMask >> 8) & 0xff) } }; } }); Add(JJ2Event::SCENERY_DESTR_BOMB, ConstantParamList(EventType::SceneryDestruct, { (std::uint8_t)(1 << (std::int32_t)WeaponType::TNT), 0 })); Add(JJ2Event::SCENERY_BUTTSTOMP, NoParamList(EventType::SceneryDestructButtstomp)); Add(JJ2Event::SCENERY_COLLAPSE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 10}, // Wait Time { JJ2ParamUInt, 5 } // FPS }, eventParams); std::uint16_t waitTime = *(std::uint16_t*)&eventParams[0] * 25; return { EventType::SceneryCollapse, { (std::uint8_t)(waitTime & 0xff), (std::uint8_t)((waitTime >> 8) & 0xff), eventParams[1] } }; }); // Modifier events Add(JJ2Event::MODIFIER_HOOK, NoParamList(EventType::ModifierHook)); Add(JJ2Event::MODIFIER_ONE_WAY, NoParamList(EventType::ModifierOneWay)); Add(JJ2Event::MODIFIER_VINE, NoParamList(EventType::ModifierVine)); Add(JJ2Event::MODIFIER_HURT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamBool, 1 }, // Up (JJ2+) { JJ2ParamBool, 1 }, // Down (JJ2+) { JJ2ParamBool, 1 }, // Left (JJ2+) { JJ2ParamBool, 1 } // Right (JJ2+) }, eventParams); Direction dir = Direction::None; if (eventParams[0]) dir |= Direction::Up; if (eventParams[1]) dir |= Direction::Down; if (eventParams[2]) dir |= Direction::Left; if (eventParams[3]) dir |= Direction::Right; if (dir == Direction::None) dir |= Direction::Up; return { EventType::ModifierHurt, { (std::uint8_t)dir } }; }); Add(JJ2Event::MODIFIER_RICOCHET, NoParamList(EventType::ModifierRicochet)); Add(JJ2Event::MODIFIER_H_POLE, NoParamList(EventType::ModifierHPole)); Add(JJ2Event::MODIFIER_V_POLE, NoParamList(EventType::ModifierVPole)); Add(JJ2Event::MODIFIER_TUBE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamInt, 7 }, // X Speed { JJ2ParamInt, 7 }, // Y Speed { JJ2ParamUInt, 1 }, // Trig Sample { JJ2ParamBool, 1 }, // BecomeNoclip (JJ2+) { JJ2ParamBool, 1 }, // Noclip Only (JJ2+) { JJ2ParamUInt, 3 } // Wait Time (JJ2+) }, eventParams); return { EventType::ModifierTube, { eventParams[0], eventParams[1], eventParams[5], eventParams[2], eventParams[3], eventParams[4] } }; }); Add(JJ2Event::MODIFIER_SLIDE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 2 } }, eventParams); // Strength return { EventType::ModifierSlide, { eventParams[0] } }; }); Add(JJ2Event::MODIFIER_BELT_LEFT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { // TODO: Use single variable std::uint8_t left, right; if (jj2Params == 0) { left = 3; right = 0; } else if (jj2Params > 127) { left = 0; right = (std::uint8_t)(256 - jj2Params); } else { left = (std::uint8_t)jj2Params; right = 0; } return { EventType::AreaHForce, { left, right } }; }); Add(JJ2Event::MODIFIER_BELT_RIGHT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { // TODO: Use single variable std::uint8_t left, right; if (jj2Params == 0) { left = 0; right = 3; } else if (jj2Params > 127) { left = (std::uint8_t)(256 - jj2Params); right = 0; } else { left = 0; right = (std::uint8_t)jj2Params; } return { EventType::AreaHForce, { left, right } }; }); Add(JJ2Event::MODIFIER_ACC_BELT_LEFT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { if (jj2Params == 0) { jj2Params = 3; } return { EventType::AreaHForce, { 0, 0, (std::uint8_t)jj2Params } }; }); Add(JJ2Event::MODIFIER_ACC_BELT_RIGHT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { if (jj2Params == 0) { jj2Params = 3; } return { EventType::AreaHForce, { 0, 0, 0, (std::uint8_t)jj2Params } }; }); Add(JJ2Event::MODIFIER_WIND_LEFT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { // TODO: Use single variable std::uint8_t left, right; if (jj2Params > 127) { left = (std::uint8_t)(256 - jj2Params); right = 0; } else { left = 0; right = (std::uint8_t)jj2Params; } return { EventType::AreaHForce, { 0, 0, 0, 0, left, right } }; }); Add(JJ2Event::MODIFIER_WIND_RIGHT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { return { EventType::AreaHForce, { 0, 0, 0, 0, 0, (uint8_t)jj2Params } }; }); Add(JJ2Event::MODIFIER_SET_WATER, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // Height (Tiles) { JJ2ParamBool, 1 }, // Instant [TODO] { JJ2ParamUInt, 2 } // Lighting [TODO] }, eventParams); uint16_t waterLevel = eventParams[0] * 32; return { EventType::ModifierSetWater, { (std::uint8_t)(waterLevel & 0xff), (std::uint8_t)((waterLevel >> 8) & 0xff), eventParams[1], eventParams[2] } }; }); Add(JJ2Event::AREA_LIMIT_X_SCROLL, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 10 }, // Left (Tiles) { JJ2ParamUInt, 10 } // Width (Tiles) }, eventParams); std::uint16_t left = *(std::uint16_t*)&eventParams[0]; std::uint16_t width = *(std::uint16_t*)&eventParams[2]; return { EventType::ModifierLimitCameraView, { (std::uint8_t)(left & 0xff), (std::uint8_t)((left >> 8) & 0xff), (std::uint8_t)(width & 0xff), (std::uint8_t)((width >> 8) & 0xff) } }; }); // Area events Add(JJ2Event::AREA_STOP_ENEMY, NoParamList(EventType::AreaStopEnemy)); Add(JJ2Event::AREA_FLOAT_UP, NoParamList(EventType::AreaFloatUp)); Add(JJ2Event::AREA_ACTIVATE_BOSS, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 1 } // Music }, eventParams); return { EventType::AreaActivateBoss, { 'b', 'o', 's', 's', (std::uint8_t)('1' + eventParams[0]), '.', 'j', '2', 'b', '\0' } }; }); Add(JJ2Event::AREA_EOL, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamBool, 1 }, // Secret { JJ2ParamBool, 1 }, // Fast (JJ2+) { JJ2ParamUInt, 4 }, // TextID (JJ2+) { JJ2ParamUInt, 4 } // Offset (JJ2+) }, eventParams); if (eventParams[2] != 0) { level->AddLevelTokenTextID(eventParams[2]); } return { EventType::AreaEndOfLevel, { (std::uint8_t)(eventParams[0] == 1 ? 4 : 1), eventParams[1], eventParams[2], eventParams[3] } }; }); Add(JJ2Event::AREA_EOL_WARP, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamBool, 1 }, // Empty (JJ2+) { JJ2ParamBool, 1 }, // Fast (JJ2+) { JJ2ParamUInt, 4 }, // TextID (JJ2+) { JJ2ParamUInt, 4 } // Offset (JJ2+) }, eventParams); if (eventParams[2] != 0) { level->AddLevelTokenTextID(eventParams[2]); } return { EventType::AreaEndOfLevel, { 2, eventParams[1], eventParams[2], eventParams[3] } }; }); Add(JJ2Event::AREA_SECRET_WARP, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 10 }, // Coins { JJ2ParamUInt, 4 }, // TextID (JJ2+) { JJ2ParamUInt, 4 } // Offset (JJ2+) }, eventParams); if (eventParams[1] != 0) { level->AddLevelTokenTextID(eventParams[2]); } std::uint16_t coins = *(std::uint16_t*)&eventParams[0]; return { EventType::AreaEndOfLevel, { 3, 0, eventParams[2], eventParams[3], (std::uint8_t)(coins & 0xff), (std::uint8_t)((coins >> 8) & 0xff) } }; }); Add(JJ2Event::EOL_SIGN, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamBool, 1 } // Secret }, eventParams); return { EventType::SignEOL, { (std::uint8_t)(eventParams[0] == 1 ? 4 : 1) } }; }); Add(JJ2Event::BONUS_SIGN, ConstantParamList(EventType::AreaEndOfLevel, { 3, 0, 0, 0, 0 })); Add(JJ2Event::AREA_TEXT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // Text { JJ2ParamBool, 1 }, // Vanish { JJ2ParamBool, 1 }, // AngelScript (JJ2+) { JJ2ParamUInt, 8 } // Offset (JJ2+) }, eventParams); if (eventParams[2] != 0) { return { EventType::AreaCallback, { eventParams[0], eventParams[3], eventParams[1] } }; } else { return { EventType::AreaText, { eventParams[0], eventParams[3], eventParams[1] } }; } }); Add(JJ2Event::AREA_FLY_OFF, NoParamList(EventType::AreaFlyOff)); Add(JJ2Event::AREA_REVERT_MORPH, NoParamList(EventType::AreaRevertMorph)); Add(JJ2Event::AREA_MORPH_FROG, NoParamList(EventType::AreaMorphToFrog)); Add(JJ2Event::AREA_NO_FIRE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 2 }, // Set To (JJ2+) { JJ2ParamUInt, 2 } // Var (JJ2+) }, eventParams); // TODO: Gravity (1) and Invisibility (2) is not supported yet if (eventParams[1] != 0) { return { EventType::Empty }; } // TODO: Toggle is not supported yet if (eventParams[0] == 3) { return { EventType::Empty }; } return { EventType::AreaNoFire, { eventParams[0] } }; }); Add(JJ2Event::WATER_BLOCK, ParamIntToParamList(EventType::AreaWaterBlock, {{ { JJ2ParamInt, 8 } // Adjust Y }})); Add(JJ2Event::SNOW, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 2 }, // Intensity { JJ2ParamBool, 1 }, // Outdoors { JJ2ParamBool, 1 }, // Off { JJ2ParamUInt, 2 } // Type }, eventParams); WeatherType weatherType = (eventParams[2] == 0 ? (WeatherType)(eventParams[3] + 1) : WeatherType::None); if (weatherType != WeatherType::None && eventParams[1] != 0) { weatherType |= WeatherType::OutdoorsOnly; } std::uint8_t weatherIntensity = (weatherType != WeatherType::None ? (std::uint8_t)((eventParams[0] + 1) * 5 / 3) : 0); return { EventType::AreaWeather, { (std::uint8_t)weatherType, weatherIntensity } }; }); Add(JJ2Event::AMBIENT_SOUND, ParamIntToParamList(EventType::AreaAmbientSound, {{ { JJ2ParamUInt, 8 }, // Sample { JJ2ParamUInt, 8 }, // Amplify { JJ2ParamBool, 1 }, // Fade [TODO] { JJ2ParamBool, 1 } // Sine [TODO] }})); Add(JJ2Event::SCENERY_BUBBLER, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 4 } // Speed }, eventParams); return { EventType::AreaAmbientBubbles, { (std::uint8_t)((eventParams[0] + 1) * 5 / 3) } }; }); // Triggers events Add(JJ2Event::TRIGGER_CRATE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 5 }, // Trigger ID { JJ2ParamBool, 1 }, // Set to (0: on, 1: off) { JJ2ParamBool, 1 } // Switch }, eventParams); return { EventType::TriggerCrate, { eventParams[0], (std::uint8_t)(eventParams[1] == 0 ? 1 : 0), eventParams[2] } }; }); Add(JJ2Event::TRIGGER_AREA, ParamIntToParamList(EventType::TriggerArea, {{ { JJ2ParamUInt, 5 } // Trigger ID }})); Add(JJ2Event::TRIGGER_ZONE, ParamIntToParamList(EventType::TriggerZone, {{ { JJ2ParamUInt, 5 }, // Trigger ID { JJ2ParamBool, 1 }, // Set to (0: off, 1: on) { JJ2ParamBool, 1 } // Switch }})); // Warp events Add(JJ2Event::WARP_ORIGIN, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // Warp ID { JJ2ParamUInt, 8 }, // Coins { JJ2ParamBool, 1 }, // Set Lap { JJ2ParamBool, 1 }, // Show Anim { JJ2ParamBool, 1 } // Fast (JJ2+) }, eventParams); if (eventParams[1] > 0 || eventParams[3] != 0) { return { EventType::WarpCoinBonus, { eventParams[0], eventParams[4], eventParams[2], eventParams[1], eventParams[3] } }; } else { return { EventType::WarpOrigin, { eventParams[0], eventParams[4], eventParams[2] } }; } }); Add(JJ2Event::WARP_TARGET, ParamIntToParamList(EventType::WarpTarget, {{ { JJ2ParamUInt, 8 } // Warp ID }})); // Lights events Add(JJ2Event::LIGHT_SET, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 7 }, // Intensity { JJ2ParamUInt, 4 }, // Red { JJ2ParamUInt, 4 }, // Green { JJ2ParamUInt, 4 }, // Blue { JJ2ParamBool, 1 } // Flicker }, eventParams); return { EventType::LightAmbient, { (std::uint8_t)std::min(eventParams[0] * 255 / 100, 255), eventParams[1], eventParams[2], eventParams[3], eventParams[4] } }; }); Add(JJ2Event::LIGHT_RESET, [](JJ2Level* level, uint32_t jj2Params) -> ConversionResult { return { EventType::LightAmbient, { (std::uint8_t)std::min(level->LightingStart * 255 / 64, 255), 255, 255, 255, 0 } }; }); Add(JJ2Event::LIGHT_DIM, ConstantParamList(EventType::LightSteady, { 127, 60, 100, 0, 0, 0, 0, 0 })); Add(JJ2Event::LIGHT_STEADY, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 3 }, // Type { JJ2ParamUInt, 7 } // Size }, eventParams); switch (eventParams[0]) { default: case 0: { // Normal std::uint16_t radiusNear = (std::uint16_t)(eventParams[1] == 0 ? 60 : eventParams[1] * 6); std::uint16_t radiusFar = (std::uint16_t)(radiusNear * 1.666f); return { EventType::LightSteady, { 255, 10, (std::uint8_t)(radiusNear & 0xff), (std::uint8_t)((radiusNear >> 8) & 0xff), (std::uint8_t)(radiusFar & 0xff), (std::uint8_t)((radiusFar >> 8) & 0xff) } }; } case 1: // Single point (ignores the "Size" parameter) return { EventType::LightSteady, { 127, 10, 0, 0, 16, 0 } }; case 2: // Single point (brighter) (ignores the "Size" parameter) return { EventType::LightSteady, { 255, 200, 0, 0, 16, 0 } }; case 3: { // Flicker light std::uint16_t radiusNear = (std::uint16_t)(eventParams[1] == 0 ? 60 : eventParams[1] * 6); std::uint16_t radiusFar = (std::uint16_t)(radiusNear * 1.666f); return { EventType::LightFlicker, { std::min((std::uint8_t)(110 + eventParams[1] * 2), (std::uint8_t)255), 40, (std::uint8_t)(radiusNear & 0xff), (std::uint8_t)((radiusNear >> 8) & 0xff), (std::uint8_t)(radiusFar & 0xff), (std::uint8_t)((radiusFar >> 8) & 0xff) } }; } case 4: { // Bright normal light std::uint16_t radiusNear = (std::uint16_t)(eventParams[1] == 0 ? 80 : eventParams[1] * 7); std::uint16_t radiusFar = (std::uint16_t)(radiusNear * 1.25f); return { EventType::LightSteady, { 255, 200, (std::uint8_t)(radiusNear & 0xff), (std::uint8_t)((radiusNear >> 8) & 0xff), (std::uint8_t)(radiusFar & 0xff), (std::uint8_t)((radiusFar >> 8) & 0xff) } }; } case 5: { // Laser shield/Illuminate Surroundings return { EventType::LightIlluminate, { (std::uint8_t)(eventParams[1] < 1 ? 1 : eventParams[1]) } }; } case 6: // Ring of light // TODO: JJ2+ Extension return { EventType::Empty }; case 7: // Ring of light 2 // TODO: JJ2+ Extension return { EventType::Empty }; } }); Add(JJ2Event::LIGHT_PULSE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // Speed { JJ2ParamUInt, 4 }, // Sync { JJ2ParamUInt, 3 }, // Type { JJ2ParamUInt, 5 } // Size }, eventParams); std::uint16_t radiusNear1 = (std::uint16_t)(eventParams[3] == 0 ? 20 : eventParams[3] * 4.8f); std::uint16_t radiusNear2 = (std::uint16_t)(radiusNear1 * 2); std::uint16_t radiusFar = (std::uint16_t)(radiusNear1 * 2.4f); std::uint8_t speed = (std::uint8_t)(eventParams[0] == 0 ? 6 : eventParams[0]); // Quickfix for Tube2.j2l to look better std::uint8_t sync = eventParams[1]; switch (eventParams[2]) { default: case 0: { // Normal return { EventType::LightPulse, { 255, 10, (std::uint8_t)(radiusNear1 & 0xff), (std::uint8_t)((radiusNear1 >> 8) & 0xff), (std::uint8_t)(radiusNear2 & 0xff), (std::uint8_t)((radiusNear2 >> 8) & 0xff), (std::uint8_t)(radiusFar & 0xff), (std::uint8_t)((radiusFar >> 8) & 0xff), speed, sync } }; } case 4: { // Bright normal light return { EventType::LightPulse, { 255, 200, (std::uint8_t)(radiusNear1 & 0xff), (std::uint8_t)((radiusNear1 >> 8) & 0xff), (std::uint8_t)(radiusNear2 & 0xff), (std::uint8_t)((radiusNear2 >> 8) & 0xff), (std::uint8_t)(radiusFar & 0xff), (std::uint8_t)((radiusFar >> 8) & 0xff), speed, sync } }; } case 5: { // Laser shield/Illuminate Surroundings // TODO: Not pulsating yet return { EventType::LightIlluminate, { (std::uint8_t)(eventParams[1] < 1 ? 1 : eventParams[1]) } }; } } }); Add(JJ2Event::LIGHT_FLICKER, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 } // Sample (not used) }, eventParams); return { EventType::LightFlicker, { 110, 40, 60, 0, 110, 0 } }; }); // Environment events Add(JJ2Event::PUSHABLE_ROCK, ConstantParamList(EventType::PushableBox, { 0 })); Add(JJ2Event::PUSHABLE_BOX, ConstantParamList(EventType::PushableBox, { 1 })); Add(JJ2Event::PLATFORM_FRUIT, GetPlatformConverter(1)); Add(JJ2Event::PLATFORM_BOLL, GetPlatformConverter(2)); Add(JJ2Event::PLATFORM_GRASS, GetPlatformConverter(3)); Add(JJ2Event::PLATFORM_PINK, GetPlatformConverter(4)); Add(JJ2Event::PLATFORM_SONIC, GetPlatformConverter(5)); Add(JJ2Event::PLATFORM_SPIKE, GetPlatformConverter(6)); Add(JJ2Event::BOLL_SPIKE, GetPlatformConverter(7)); Add(JJ2Event::BOLL_SPIKE_3D, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 2 }, // Sync { JJ2ParamInt, 6 }, // Speed { JJ2ParamUInt, 4 }, // Length { JJ2ParamBool, 1 }, // Swing { JJ2ParamBool, 1 } // Shade }, eventParams); return { EventType::SpikeBall, { eventParams[0], eventParams[1], eventParams[2], eventParams[3], eventParams[4] } }; }); Add(JJ2Event::SPRING_RED, GetSpringConverter(0 /*Red*/, false, false)); Add(JJ2Event::SPRING_GREEN, GetSpringConverter(1 /*Green*/, false, false)); Add(JJ2Event::SPRING_BLUE, GetSpringConverter(2 /*Blue*/, false, false)); Add(JJ2Event::SPRING_RED_HOR, GetSpringConverter(0 /*Red*/, true, false)); Add(JJ2Event::SPRING_GREEN_HOR, GetSpringConverter(1 /*Green*/, true, false)); Add(JJ2Event::SPRING_BLUE_HOR, GetSpringConverter(2 /*Blue*/, true, false)); Add(JJ2Event::SPRING_GREEN_FROZEN, GetSpringConverter(1 /*Green*/, false, true)); Add(JJ2Event::BRIDGE, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 4 }, // Width { JJ2ParamUInt, 3 }, // Type { JJ2ParamUInt, 4 } // Toughness }, eventParams); std::uint16_t width = (eventParams[0] * 2); return { EventType::Bridge, { (std::uint8_t)(width & 0xff), (std::uint8_t)((width >> 8) & 0xff), eventParams[1], eventParams[2] } }; }); Add(JJ2Event::POLE_CARROTUS, GetPoleConverter(0)); Add(JJ2Event::POLE_DIAMONDUS, GetPoleConverter(1)); Add(JJ2Event::SMALL_TREE, GetPoleConverter(2)); Add(JJ2Event::POLE_JUNGLE, GetPoleConverter(3)); Add(JJ2Event::POLE_PSYCH, GetPoleConverter(4)); Add(JJ2Event::ROTATING_ROCK, ParamIntToParamList(EventType::RollingRock, {{ { JJ2ParamUInt, 8 }, // ID { JJ2ParamInt, 4 }, // X-Speed { JJ2ParamInt, 4 } // Y-Speed }})); Add(JJ2Event::TRIGGER_ROCK, ParamIntToParamList(EventType::RollingRockTrigger, {{ { JJ2ParamUInt, 8 } // ID }})); Add(JJ2Event::SWINGING_VINE, NoParamList(EventType::SwingingVine)); // Enemies Add(JJ2Event::ENEMY_TURTLE_NORMAL, ConstantParamList(EventType::EnemyTurtle, { 0 })); Add(JJ2Event::ENEMY_NORMAL_TURTLE_XMAS, ConstantParamList(EventType::EnemyTurtle, { 1 })); Add(JJ2Event::ENEMY_LIZARD, ConstantParamList(EventType::EnemyLizard, { 0 })); Add(JJ2Event::ENEMY_LIZARD_XMAS, ConstantParamList(EventType::EnemyLizard, { 1 })); Add(JJ2Event::ENEMY_LIZARD_FLOAT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // Duration { JJ2ParamBool, 1 } // Fly Away }, eventParams); return { EventType::EnemyLizardFloat, { 0, eventParams[0], eventParams[1] } }; }); Add(JJ2Event::ENEMY_LIZARD_FLOAT_XMAS, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // Duration { JJ2ParamBool, 1 } // Fly Away }, eventParams); return { EventType::EnemyLizardFloat, { 1, eventParams[0], eventParams[1] } }; }); Add(JJ2Event::ENEMY_DRAGON, NoParamList(EventType::EnemyDragon)); Add(JJ2Event::ENEMY_LAB_RAT, NoParamList(EventType::EnemyLabRat)); Add(JJ2Event::ENEMY_SUCKER_FLOAT, NoParamList(EventType::EnemySuckerFloat)); Add(JJ2Event::ENEMY_SUCKER, NoParamList(EventType::EnemySucker)); Add(JJ2Event::ENEMY_HELMUT, NoParamList(EventType::EnemyHelmut)); Add(JJ2Event::ENEMY_BAT, NoParamList(EventType::EnemyBat)); Add(JJ2Event::ENEMY_FAT_CHICK, NoParamList(EventType::EnemyFatChick)); Add(JJ2Event::ENEMY_FENCER, NoParamList(EventType::EnemyFencer)); Add(JJ2Event::ENEMY_RAPIER, NoParamList(EventType::EnemyRapier)); Add(JJ2Event::ENEMY_SPARKS, NoParamList(EventType::EnemySparks)); Add(JJ2Event::ENEMY_MONKEY, ConstantParamList(EventType::EnemyMonkey, { 1 })); Add(JJ2Event::ENEMY_MONKEY_STAND, ConstantParamList(EventType::EnemyMonkey, { 0 })); Add(JJ2Event::ENEMY_DEMON, NoParamList(EventType::EnemyDemon)); Add(JJ2Event::ENEMY_BEE, NoParamList(EventType::EnemyBee)); Add(JJ2Event::ENEMY_BEE_SWARM, NoParamList(EventType::EnemyBeeSwarm)); Add(JJ2Event::ENEMY_CATERPILLAR, NoParamList(EventType::EnemyCaterpillar)); Add(JJ2Event::ENEMY_CRAB, NoParamList(EventType::EnemyCrab)); Add(JJ2Event::ENEMY_DOGGY_DOGG, ConstantParamList(EventType::EnemyDoggy, { 0 })); Add(JJ2Event::EMPTY_TSF_DOG, ConstantParamList(EventType::EnemyDoggy, { 1 })); Add(JJ2Event::ENEMY_DRAGONFLY, NoParamList(EventType::EnemyDragonfly)); Add(JJ2Event::ENEMY_FISH, NoParamList(EventType::EnemyFish)); Add(JJ2Event::ENEMY_MADDER_HATTER, NoParamList(EventType::EnemyMadderHatter)); Add(JJ2Event::ENEMY_RAVEN, NoParamList(EventType::EnemyRaven)); Add(JJ2Event::ENEMY_SKELETON, NoParamList(EventType::EnemySkeleton)); Add(JJ2Event::ENEMY_TUF_TURT, NoParamList(EventType::EnemyTurtleTough)); Add(JJ2Event::ENEMY_TURTLE_TUBE, NoParamList(EventType::EnemyTurtleTube)); Add(JJ2Event::ENEMY_WITCH, NoParamList(EventType::EnemyWitch)); Add(JJ2Event::TURTLE_SHELL, NoParamList(EventType::TurtleShell)); // Bosses Add(JJ2Event::BOSS_TWEEDLE, GetBossConverter(EventType::BossTweedle)); Add(JJ2Event::BOSS_BILSY, GetBossConverter(EventType::BossBilsy, { 0 })); Add(JJ2Event::EMPTY_BOSS_BILSY_XMAS, GetBossConverter(EventType::BossBilsy, { 1 })); Add(JJ2Event::BOSS_DEVAN_DEVIL, GetBossConverter(EventType::BossDevan)); Add(JJ2Event::BOSS_ROBOT, NoParamList(EventType::BossRobot)); Add(JJ2Event::BOSS_QUEEN, GetBossConverter(EventType::BossQueen)); Add(JJ2Event::BOSS_UTERUS, GetBossConverter(EventType::BossUterus)); Add(JJ2Event::BOSS_BUBBA, GetBossConverter(EventType::BossBubba)); Add(JJ2Event::BOSS_TUF_TURT, GetBossConverter(EventType::BossTurtleTough)); Add(JJ2Event::BOSS_DEVAN_ROBOT, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 4 }, // IntroText { JJ2ParamUInt, 4 } // EndText }, eventParams); return { EventType::BossDevanRemote, { 0, eventParams[0], eventParams[1] } }; }); Add(JJ2Event::BOSS_BOLLY, GetBossConverter(EventType::BossBolly)); // Collectibles Add(JJ2Event::COIN_SILVER, ConstantParamList(EventType::Coin, { 0 })); Add(JJ2Event::COIN_GOLD, ConstantParamList(EventType::Coin, { 1 })); Add(JJ2Event::GEM_RED, ConstantParamList(EventType::Gem, { 0 })); Add(JJ2Event::GEM_GREEN, ConstantParamList(EventType::Gem, { 1 })); Add(JJ2Event::GEM_BLUE, ConstantParamList(EventType::Gem, { 2 })); Add(JJ2Event::GEM_PURPLE, ConstantParamList(EventType::Gem, { 3 })); Add(JJ2Event::GEM_RED_RECT, ConstantParamList(EventType::Gem, { 0, 2 })); Add(JJ2Event::GEM_GREEN_RECT, ConstantParamList(EventType::Gem, { 1, 2 })); Add(JJ2Event::GEM_BLUE_RECT, ConstantParamList(EventType::Gem, { 2, 2 })); Add(JJ2Event::GEM_SUPER, NoParamList(EventType::GemGiant)); Add(JJ2Event::GEM_RING, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 5 }, // Length { JJ2ParamUInt, 5 }, // Speed { JJ2ParamBool, 8 } // Event }, eventParams); return { EventType::GemRing, { eventParams[0], eventParams[1] } }; }); Add(JJ2Event::SCENERY_GEMSTOMP, NoParamList(EventType::GemStomp)); Add(JJ2Event::CARROT, ConstantParamList(EventType::Carrot, { 0 })); Add(JJ2Event::CARROT_FULL, ConstantParamList(EventType::Carrot, { 1 })); Add(JJ2Event::CARROT_FLY, NoParamList(EventType::CarrotFly)); Add(JJ2Event::CARROT_INVINCIBLE, NoParamList(EventType::CarrotInvincible)); Add(JJ2Event::ONEUP, NoParamList(EventType::OneUp)); Add(JJ2Event::AMMO_BOUNCER, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::Bouncer })); Add(JJ2Event::AMMO_FREEZER, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::Freezer })); Add(JJ2Event::AMMO_SEEKER, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::Seeker })); Add(JJ2Event::AMMO_RF, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::RF })); Add(JJ2Event::AMMO_TOASTER, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::Toaster })); Add(JJ2Event::AMMO_TNT, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::TNT })); Add(JJ2Event::AMMO_PEPPER, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::Pepper })); Add(JJ2Event::AMMO_ELECTRO, ConstantParamList(EventType::Ammo, { (std::uint8_t)WeaponType::Electro })); Add(JJ2Event::FAST_FIRE, NoParamList(EventType::FastFire)); Add(JJ2Event::POWERUP_BLASTER, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Blaster })); Add(JJ2Event::POWERUP_BOUNCER, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Bouncer })); Add(JJ2Event::POWERUP_FREEZER, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Freezer })); Add(JJ2Event::POWERUP_SEEKER, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Seeker })); Add(JJ2Event::POWERUP_RF, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::RF })); Add(JJ2Event::POWERUP_TOASTER, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Toaster })); Add(JJ2Event::POWERUP_TNT, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::TNT })); Add(JJ2Event::POWERUP_PEPPER, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Pepper })); Add(JJ2Event::POWERUP_ELECTRO, ConstantParamList(EventType::PowerUpWeapon, { (std::uint8_t)WeaponType::Electro })); Add(JJ2Event::FOOD_APPLE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Apple })); Add(JJ2Event::FOOD_BANANA, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Banana })); Add(JJ2Event::FOOD_CHERRY, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Cherry })); Add(JJ2Event::FOOD_ORANGE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Orange })); Add(JJ2Event::FOOD_PEAR, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Pear })); Add(JJ2Event::FOOD_PRETZEL, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Pretzel })); Add(JJ2Event::FOOD_STRAWBERRY, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Strawberry })); Add(JJ2Event::FOOD_LEMON, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Lemon })); Add(JJ2Event::FOOD_LIME, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Lime })); Add(JJ2Event::FOOD_THING, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Thing })); Add(JJ2Event::FOOD_WATERMELON, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::WaterMelon })); Add(JJ2Event::FOOD_PEACH, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Peach })); Add(JJ2Event::FOOD_GRAPES, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Grapes })); Add(JJ2Event::FOOD_LETTUCE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Lettuce })); Add(JJ2Event::FOOD_EGGPLANT, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Eggplant })); Add(JJ2Event::FOOD_CUCUMBER, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Cucumber })); Add(JJ2Event::FOOD_PEPSI, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Pepsi })); Add(JJ2Event::FOOD_COKE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Coke })); Add(JJ2Event::FOOD_MILK, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Milk })); Add(JJ2Event::FOOD_PIE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Pie })); Add(JJ2Event::FOOD_CAKE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Cake })); Add(JJ2Event::FOOD_DONUT, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Donut })); Add(JJ2Event::FOOD_CUPCAKE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Cupcake })); Add(JJ2Event::FOOD_CHIPS, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Chips })); Add(JJ2Event::FOOD_CANDY, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Candy })); Add(JJ2Event::FOOD_CHOCOLATE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Chocolate })); Add(JJ2Event::FOOD_ICE_CREAM, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::IceCream })); Add(JJ2Event::FOOD_BURGER, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Burger })); Add(JJ2Event::FOOD_PIZZA, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Pizza })); Add(JJ2Event::FOOD_FRIES, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Fries })); Add(JJ2Event::FOOD_CHICKEN_LEG, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::ChickenLeg })); Add(JJ2Event::FOOD_SANDWICH, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Sandwich })); Add(JJ2Event::FOOD_TACO, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Taco })); Add(JJ2Event::FOOD_HOT_DOG, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::HotDog })); Add(JJ2Event::FOOD_HAM, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Ham })); Add(JJ2Event::FOOD_CHEESE, ConstantParamList(EventType::Food, { (std::uint8_t)FoodType::Cheese })); Add(JJ2Event::CRATE_AMMO, GetAmmoCrateConverter(0)); Add(JJ2Event::CRATE_AMMO_BOUNCER, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 3 } // Weapon }, eventParams); std::uint8_t type = eventParams[0] + 1; if (type < 1 || type > 8) { type = 1; // Fallback to Bouncer if out of range } return { EventType::CrateAmmo, { type } }; }); Add(JJ2Event::CRATE_AMMO_FREEZER, GetAmmoCrateConverter(2)); Add(JJ2Event::CRATE_AMMO_SEEKER, GetAmmoCrateConverter(3)); Add(JJ2Event::CRATE_AMMO_RF, GetAmmoCrateConverter(4)); Add(JJ2Event::CRATE_AMMO_TOASTER, GetAmmoCrateConverter(5)); Add(JJ2Event::CRATE_CARROT, ConstantParamList(EventType::Crate, { (std::uint8_t)EventType::Carrot, (std::uint8_t)((std::int32_t)EventType::Carrot >> 8), 1/*Count*/ })); Add(JJ2Event::CRATE_SPRING, ConstantParamList(EventType::Crate, { (std::uint8_t)EventType::Spring, (std::uint8_t)((std::int32_t)EventType::Spring >> 8), 1/*Count*/, 1/*Green*/, 0/*Up*/, 0x01 })); Add(JJ2Event::CRATE_ONEUP, ConstantParamList(EventType::Crate, { (std::uint8_t)EventType::OneUp, (std::uint8_t)((std::int32_t)EventType::OneUp >> 8), 1/*Count*/ })); Add(JJ2Event::CRATE_BOMB, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 8 }, // ExtraEvent { JJ2ParamUInt, 4 }, // NumEvent { JJ2ParamBool, 1 }, // RandomFly { JJ2ParamBool, 1 } // NoBomb }, eventParams); // TODO: Implement RandomFly parameter if (eventParams[0] > 0 && eventParams[1] > 0) { // TODO: Convert ExtraEvent //return { EventType::Crate, { (uint8_t)(eventParams[0] & 0xff), (uint8_t)((eventParams[0] >> 8) & 0xff), eventParams[1] } }; return { EventType::Crate }; } else if (eventParams[3] == 0) { return { EventType::Crate, { (std::uint8_t)EventType::Bomb, (std::uint8_t)((std::int32_t)EventType::Bomb >> 8), 1 } }; } else { return { EventType::Crate }; } }); Add(JJ2Event::BARREL_AMMO, NoParamList(EventType::BarrelAmmo)); Add(JJ2Event::BARREL_CARROT, ConstantParamList(EventType::Barrel, { (std::uint8_t)EventType::Carrot, (std::uint8_t)((std::int32_t)EventType::Carrot >> 8), 1 })); Add(JJ2Event::BARREL_ONEUP, ConstantParamList(EventType::Barrel, { (std::uint8_t)EventType::OneUp, (std::uint8_t)((std::int32_t)EventType::OneUp >> 8), 1 })); Add(JJ2Event::CRATE_GEM, ParamIntToParamList(EventType::CrateGem, {{ { JJ2ParamUInt, 4 }, // Red { JJ2ParamUInt, 4 }, // Green { JJ2ParamUInt, 4 }, // Blue { JJ2ParamUInt, 4 } // Purple }})); Add(JJ2Event::BARREL_GEM, ParamIntToParamList(EventType::BarrelGem, {{ { JJ2ParamUInt, 4 }, // Red { JJ2ParamUInt, 4 }, // Green { JJ2ParamUInt, 4 }, // Blue { JJ2ParamUInt, 4 } // Purple }})); Add(JJ2Event::POWERUP_SWAP, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { if (level->GetVersion() == JJ2Version::TSF || level->GetVersion() == JJ2Version::CC) { return { EventType::PowerUpMorph, { 1 } }; } else { return { EventType::PowerUpMorph, { 0 } }; } }); Add(JJ2Event::POWERUP_BIRD, ConstantParamList(EventType::PowerUpMorph, { 2 })); Add(JJ2Event::BIRDY, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamBool, 1 } // Chuck (Yellow) }, eventParams); return { EventType::BirdCage, { eventParams[0] } }; }); // Misc events Add(JJ2Event::EVA, NoParamList(EventType::Eva)); Add(JJ2Event::MOTH, ParamIntToParamList(EventType::Moth, {{ { JJ2ParamUInt, 3 } }})); Add(JJ2Event::STEAM, NoParamList(EventType::SteamNote)); Add(JJ2Event::SCENERY_BOMB, NoParamList(EventType::Bomb)); Add(JJ2Event::PINBALL_BUMP_500, ConstantParamList(EventType::PinballBumper, { 0 })); Add(JJ2Event::PINBALL_BUMP_CARROT, ConstantParamList(EventType::PinballBumper, { 1 })); Add(JJ2Event::PINBALL_PADDLE_L, ConstantParamList(EventType::PinballPaddle, { 0 })); Add(JJ2Event::PINBALL_PADDLE_R, ConstantParamList(EventType::PinballPaddle, { 1 })); Add(JJ2Event::AIRBOARD, [](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 5 } // Delay (Secs.) - Default: 30 }, eventParams); return { EventType::AirboardGenerator, { (std::uint8_t)(eventParams[0] == 0 ? 30 : eventParams[0]) } }; }); Add(JJ2Event::COPTER, NoParamList(EventType::Copter)); Add(JJ2Event::CTF_BASE, ParamIntToParamList(EventType::CtfBase, {{ { JJ2ParamUInt, 1 }, // Team { JJ2ParamUInt, 1 } // Direction }})); Add(JJ2Event::SHIELD_FIRE, ConstantParamList(EventType::PowerUpShield, { 1 })); Add(JJ2Event::SHIELD_WATER, ConstantParamList(EventType::PowerUpShield, { 2 })); Add(JJ2Event::SHIELD_LIGHTNING, ConstantParamList(EventType::PowerUpShield, { 3 })); Add(JJ2Event::SHIELD_LASER, ConstantParamList(EventType::PowerUpShield, { 4 })); Add(JJ2Event::STOPWATCH, NoParamList(EventType::Stopwatch)); } EventConverter::ConversionFunction EventConverter::GetSpringConverter(std::uint8_t type, bool horizontal, bool frozen) { return [type, horizontal, frozen](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamBool, 1 }, // Orientation (vertical only) { JJ2ParamBool, 1 }, // Keep X Speed (vertical only) { JJ2ParamBool, 1 }, // Keep Y Speed { JJ2ParamUInt, 4 }, // Delay { JJ2ParamBool, 1 } // Reverse (horzontal only, JJ2+) }, eventParams); std::uint8_t orientation = (std::uint8_t)(horizontal ? (eventParams[4] != 0 ? 5 : 4) : (eventParams[0] != 0 ? 2 : 0)); bool applyGravitation = (orientation == 0); std::uint8_t flags = 0; if (applyGravitation) flags |= 0x01; if (frozen) flags |= 0x02; if (eventParams[1] != 0) flags |= 0x04; if (eventParams[2] != 0) flags |= 0x08; return { EventType::Spring, { type, orientation, flags, eventParams[3] } }; }; } EventConverter::ConversionFunction EventConverter::GetPlatformConverter(std::uint8_t type) { return [type](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 2 }, // Sync { JJ2ParamInt, 6 }, // Speed { JJ2ParamUInt, 4 }, // Length { JJ2ParamBool, 1 } // Swing }, eventParams); return { EventType::MovingPlatform, { type, eventParams[0], eventParams[1], eventParams[2], eventParams[3] } }; }; } EventConverter::ConversionFunction EventConverter::GetPoleConverter(std::uint8_t theme) { return [theme](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 5 }, // Adjust Y { JJ2ParamInt, 6 } // Adjust X }, eventParams); constexpr std::int32_t AdjustX = 2; constexpr std::int32_t AdjustY = 2; std::uint16_t x = (std::uint16_t)((std::int8_t)eventParams[1] + 16 - AdjustX); std::uint16_t y = (std::uint16_t)((eventParams[0] == 0 ? 24 : eventParams[0]) - AdjustY); return { EventType::Pole, { theme, (std::uint8_t)(x & 0xff), (std::uint8_t)((x >> 8) & 0xff), (std::uint8_t)(y & 0xff), (std::uint8_t)((y >> 8) & 0xff) } }; }; } EventConverter::ConversionFunction EventConverter::GetAmmoCrateConverter(std::uint8_t type) { return [type](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { return { EventType::CrateAmmo, { type } }; }; } EventConverter::ConversionFunction EventConverter::GetBossConverter(EventType ev, std::uint8_t customParam) { return [ev, customParam](JJ2Level* level, std::uint32_t jj2Params) -> ConversionResult { std::uint8_t eventParams[16]; ConvertParamInt(jj2Params, { { JJ2ParamUInt, 4 } // EndText }, eventParams); return { ev, { customParam, eventParams[0] } }; }; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/EventConverter.h000066400000000000000000000042541512772601700274730ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Event.h" #include "../EventType.h" #include #include "../../nCine/Base/HashMap.h" using namespace nCine; #include #include #include namespace Jazz2::Compatibility { class JJ2Level; /** @brief Converted extended event description */ struct ConversionResult { EventType Type; std::uint8_t Params[16]; }; constexpr std::int32_t JJ2ParamNone = 0; constexpr std::int32_t JJ2ParamBool = 1; constexpr std::int32_t JJ2ParamUInt = 2; constexpr std::int32_t JJ2ParamInt = 3; /** @brief Maps original events to extended event descriptions */ class EventConverter { public: using ConversionFunction = Function; EventConverter(); ConversionResult TryConvert(JJ2Level* level, JJ2Event old, std::uint32_t eventParams); void Add(JJ2Event originalEvent, ConversionFunction&& converter); void Override(JJ2Event originalEvent, ConversionFunction&& converter); ConversionFunction NoParamList(EventType ev); ConversionFunction ConstantParamList(EventType ev, const std::array& eventParams); ConversionFunction ParamIntToParamList(EventType ev, const std::array, 6>& paramDefs); static void ConvertParamInt(std::uint32_t paramInt, const ArrayView>& paramTypes, std::uint8_t eventParams[16]); static void ConvertParamInt(std::uint32_t paramInt, const std::initializer_list> paramTypes, std::uint8_t eventParams[16]) { ConvertParamInt(paramInt, arrayView(paramTypes), eventParams); } private: HashMap _converters; void AddDefaultConverters(); static ConversionFunction GetSpringConverter(std::uint8_t type, bool horizontal, bool frozen); static ConversionFunction GetPlatformConverter(std::uint8_t type); static ConversionFunction GetPoleConverter(std::uint8_t theme); static ConversionFunction GetAmmoCrateConverter(std::uint8_t type); static ConversionFunction GetBossConverter(EventType ev, std::uint8_t customParam = 0); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Anims.Palettes.h000066400000000000000000000427471512772601700276700ustar00rootroot00000000000000#pragma once #include "../../nCine/Primitives/Color.h" using namespace nCine; static constexpr uint8_t ToasterPowerUpFix[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, /*<*/88, 89, 90, 91, 92, 93, 94/*>*/, 31, /*<*/40, 41, 42, 43, 44, 45/*>*/, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, /*<*/41, 42, 43, 44/*>*/, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 }; static constexpr Color MenuPalette[] = { Color(0, 0, 0, 0), // 0 Color(0, 0, 1, 255), // 1 Color(0, 1, 0, 255), // 2 Color(1, 0, 0, 255), // 3 Color(0, 1, 1, 255), // 4 Color(1, 0, 1, 255), // 5 Color(1, 1, 0, 255), // 6 Color(1, 1, 1, 255), // 7 Color(0, 0, 2, 255), // 8 Color(0, 1, 2, 255), // 9 Color(127, 115, 95, 255), // 10 Color(159, 143, 119, 255), // 11 Color(191, 175, 151, 255), // 12 Color(223, 207, 179, 255), // 13 Color(255, 243, 211, 255), // 14 Color(255, 255, 254, 255), // 15 Color(247, 255, 223, 255), // 16 Color(235, 255, 183, 255), // 17 Color(227, 255, 147, 255), // 18 Color(219, 255, 111, 255), // 19 Color(199, 255, 0, 255), // 20 Color(163, 231, 0, 255), // 21 Color(127, 207, 0, 255), // 22 Color(99, 183, 0, 255), // 23 Color(75, 159, 0, 255), // 24 Color(39, 139, 0, 255), // 25 Color(11, 119, 0, 255), // 26 Color(0, 99, 7, 255), // 27 Color(0, 79, 19, 255), // 28 Color(0, 59, 23, 255), // 29 Color(0, 39, 23, 255), // 30 Color(0, 19, 16, 255), // 31 Color(251, 255, 235, 255), // 32 Color(251, 255, 215, 255), // 33 Color(251, 255, 199, 255), // 34 Color(255, 247, 183, 255), // 35 Color(255, 239, 167, 255), // 36 Color(255, 227, 151, 255), // 37 Color(255, 211, 135, 255), // 38 Color(255, 195, 119, 255), // 39 Color(223, 179, 115, 255), // 40 Color(195, 167, 107, 255), // 41 Color(163, 147, 95, 255), // 42 Color(135, 123, 87, 255), // 43 Color(107, 99, 71, 255), // 44 Color(75, 75, 55, 255), // 45 Color(47, 47, 35, 255), // 46 Color(19, 19, 15, 255), // 47 Color(235, 239, 255, 255), // 48 Color(191, 207, 251, 255), // 49 Color(155, 179, 247, 255), // 50 Color(115, 155, 243, 255), // 51 Color(79, 131, 239, 255), // 52 Color(43, 115, 235, 255), // 53 Color(19, 103, 219, 255), // 54 Color(0, 91, 203, 255), // 55 Color(0, 83, 179, 255), // 56 Color(0, 75, 159, 255), // 57 Color(0, 71, 135, 255), // 58 Color(0, 63, 115, 255), // 59 Color(0, 51, 95, 255), // 60 Color(0, 43, 71, 255), // 61 Color(0, 31, 51, 255), // 62 Color(0, 20, 31, 255), // 63 Color(255, 223, 223, 255), // 64 Color(255, 183, 183, 255), // 65 Color(255, 147, 147, 255), // 66 Color(255, 111, 111, 255), // 67 Color(255, 71, 71, 255), // 68 Color(255, 35, 35, 255), // 69 Color(255, 0, 0, 255), // 70 Color(227, 0, 11, 255), // 71 Color(199, 0, 35, 255), // 72 Color(175, 0, 55, 255), // 73 Color(147, 0, 71, 255), // 74 Color(123, 0, 75, 255), // 75 Color(95, 0, 71, 255), // 76 Color(71, 0, 63, 255), // 77 Color(39, 0, 43, 255), // 78 Color(15, 0, 19, 255), // 79 Color(255, 251, 223, 255), // 80 Color(255, 243, 147, 255), // 81 Color(255, 239, 71, 255), // 82 Color(255, 215, 59, 255), // 83 Color(255, 195, 47, 255), // 84 Color(255, 167, 35, 255), // 85 Color(255, 143, 23, 255), // 86 Color(235, 115, 47, 255), // 87 Color(219, 103, 67, 255), // 88 Color(203, 95, 87, 255), // 89 Color(171, 83, 59, 255), // 90 Color(139, 71, 39, 255), // 91 Color(111, 59, 23, 255), // 92 Color(79, 47, 11, 255), // 93 Color(47, 35, 0, 255), // 94 Color(19, 15, 0, 255), // 95 Color(0, 255, 67, 255), // 96 Color(0, 235, 75, 255), // 97 Color(0, 215, 83, 255), // 98 Color(0, 199, 87, 255), // 99 Color(0, 179, 91, 255), // 100 Color(0, 163, 91, 255), // 101 Color(0, 147, 87, 255), // 102 Color(0, 131, 79, 255), // 103 Color(0, 119, 75, 255), // 104 Color(0, 103, 67, 255), // 105 Color(0, 91, 59, 255), // 106 Color(0, 75, 51, 255), // 107 Color(0, 59, 43, 255), // 108 Color(0, 47, 35, 255), // 109 Color(0, 31, 23, 255), // 110 Color(0, 19, 15, 255), // 111 Color(251, 223, 255, 255), // 112 Color(243, 175, 255, 255), // 113 Color(235, 131, 255, 255), // 114 Color(231, 87, 255, 255), // 115 Color(223, 43, 255, 255), // 116 Color(215, 0, 255, 255), // 117 Color(191, 0, 231, 255), // 118 Color(171, 0, 207, 255), // 119 Color(151, 0, 183, 255), // 120 Color(131, 0, 159, 255), // 121 Color(111, 0, 135, 255), // 122 Color(91, 0, 111, 255), // 123 Color(71, 0, 87, 255), // 124 Color(51, 0, 63, 255), // 125 Color(31, 0, 43, 255), // 126 Color(16, 0, 19, 255), // 127 Color(247, 251, 255, 255), // 128 Color(215, 227, 235, 255), // 129 Color(183, 203, 219, 255), // 130 Color(159, 179, 203, 255), // 131 Color(135, 159, 183, 255), // 132 Color(111, 139, 167, 255), // 133 Color(91, 123, 151, 255), // 134 Color(75, 107, 139, 255), // 135 Color(59, 95, 127, 255), // 136 Color(47, 83, 115, 255), // 137 Color(35, 71, 103, 255), // 138 Color(27, 63, 91, 255), // 139 Color(15, 51, 75, 255), // 140 Color(7, 39, 59, 255), // 141 Color(0, 27, 43, 255), // 142 Color(0, 19, 31, 255), // 143 Color(223, 0, 255, 255), // 144 Color(215, 0, 247, 255), // 145 Color(207, 0, 243, 255), // 146 Color(199, 0, 235, 255), // 147 Color(191, 0, 227, 255), // 148 Color(183, 0, 223, 255), // 149 Color(175, 0, 215, 255), // 150 Color(167, 0, 211, 255), // 151 Color(159, 1, 203, 255), // 152 Color(155, 0, 199, 255), // 153 Color(147, 0, 191, 255), // 154 Color(143, 0, 187, 255), // 155 Color(135, 0, 179, 255), // 156 Color(127, 0, 171, 255), // 157 Color(123, 0, 167, 255), // 158 Color(115, 0, 159, 255), // 159 Color(111, 1, 155, 255), // 160 Color(103, 0, 147, 255), // 161 Color(99, 0, 143, 255), // 162 Color(91, 0, 135, 255), // 163 Color(87, 0, 131, 255), // 164 Color(83, 0, 123, 255), // 165 Color(75, 0, 119, 255), // 166 Color(71, 0, 111, 255), // 167 Color(67, 0, 103, 255), // 168 Color(59, 0, 99, 255), // 169 Color(55, 0, 91, 255), // 170 Color(51, 0, 87, 255), // 171 Color(47, 0, 79, 255), // 172 Color(43, 0, 75, 255), // 173 Color(39, 0, 67, 255), // 174 Color(35, 0, 63, 255), // 175 Color(215, 39, 203, 255), // 176 Color(199, 35, 187, 255), // 177 Color(183, 31, 175, 255), // 178 Color(171, 31, 163, 255), // 179 Color(155, 27, 147, 255), // 180 Color(143, 23, 135, 255), // 181 Color(127, 23, 119, 255), // 182 Color(115, 19, 107, 255), // 183 Color(99, 15, 95, 255), // 184 Color(83, 15, 79, 255), // 185 Color(71, 11, 67, 255), // 186 Color(55, 7, 51, 255), // 187 Color(43, 7, 39, 255), // 188 Color(31, 0, 27, 255), // 189 Color(19, 0, 15, 255), // 190 Color(7, 0, 7, 255), // 191 Color(255, 254, 255, 255), // 192 Color(255, 255, 215, 255), // 193 Color(255, 255, 179, 255), // 194 Color(255, 255, 143, 255), // 195 Color(255, 255, 107, 255), // 196 Color(255, 255, 71, 255), // 197 Color(255, 255, 35, 255), // 198 Color(255, 255, 0, 255), // 199 Color(247, 247, 0, 255), // 200 Color(239, 239, 0, 255), // 201 Color(231, 231, 0, 255), // 202 Color(223, 223, 0, 255), // 203 Color(215, 215, 0, 255), // 204 Color(207, 207, 0, 255), // 205 Color(199, 199, 0, 255), // 206 Color(191, 191, 0, 255), // 207 Color(35, 0, 87, 255), // 208 Color(39, 0, 95, 255), // 209 Color(43, 0, 99, 255), // 210 Color(47, 0, 107, 255), // 211 Color(51, 0, 115, 255), // 212 Color(59, 0, 123, 255), // 213 Color(63, 0, 131, 255), // 214 Color(67, 0, 139, 255), // 215 Color(75, 0, 147, 255), // 216 Color(79, 0, 155, 255), // 217 Color(87, 0, 163, 255), // 218 Color(91, 0, 171, 255), // 219 Color(99, 0, 179, 255), // 220 Color(103, 0, 187, 255), // 221 Color(111, 0, 195, 255), // 222 Color(119, 0, 203, 255), // 223 Color(51, 1, 87, 255), // 224 Color(55, 0, 95, 255), // 225 Color(63, 0, 99, 255), // 226 Color(67, 0, 107, 255), // 227 Color(75, 0, 115, 255), // 228 Color(79, 0, 123, 255), // 229 Color(88, 0, 131, 255), // 230 Color(95, 0, 139, 255), // 231 Color(104, 0, 147, 255), // 232 Color(111, 0, 155, 255), // 233 Color(115, 0, 163, 255), // 234 Color(123, 0, 171, 255), // 235 Color(131, 0, 179, 255), // 236 Color(139, 0, 187, 255), // 237 Color(151, 0, 195, 255), // 238 Color(159, 0, 203, 255), // 239 Color(0, 255, 195, 255), // 240 Color(0, 227, 167, 255), // 241 Color(0, 199, 143, 255), // 242 Color(0, 171, 123, 255), // 243 Color(0, 143, 99, 255), // 244 Color(0, 115, 79, 255), // 245 Color(247, 211, 255, 255), // 246 Color(247, 212, 255, 255), // 247 Color(248, 211, 255, 255), // 248 Color(248, 212, 255, 255), // 249 Color(246, 211, 255, 255), // 250 Color(246, 212, 255, 255), // 251 Color(246, 210, 255, 255), // 252 Color(247, 210, 255, 255), // 253 Color(248, 210, 255, 255), // 254 Color(254, 255, 255, 255) // 255 }; static constexpr Color SpritePalette[] = { Color(0, 0, 0, 0), // 0 Color(0, 0, 1, 255), // 1 Color(0, 1, 0, 255), // 2 Color(1, 0, 0, 255), // 3 Color(0, 1, 1, 255), // 4 Color(1, 0, 1, 255), // 5 Color(1, 1, 0, 255), // 6 Color(1, 1, 1, 255), // 7 Color(0, 0, 2, 255), // 8 Color(0, 1, 2, 255), // 9 Color(255, 67, 195, 255), // 10 Color(174, 176, 188, 255), // 11 Color(63, 237, 224, 255), // 12 Color(160, 153, 218, 255), // 13 Color(36, 38, 50, 255), // 14 Color(255, 255, 254, 255), // 15 Color(199, 255, 0, 255), // 16 Color(147, 223, 0, 255), // 17 Color(107, 191, 0, 255), // 18 Color(71, 163, 0, 255), // 19 Color(43, 131, 0, 255), // 20 Color(19, 103, 0, 255), // 21 Color(7, 55, 0, 255), // 22 Color(0, 12, 0, 255), // 23 Color(255, 0, 0, 255), // 24 Color(227, 0, 0, 255), // 25 Color(199, 0, 0, 255), // 26 Color(171, 0, 0, 255), // 27 Color(143, 0, 0, 255), // 28 Color(115, 0, 0, 255), // 29 Color(63, 0, 0, 255), // 30 Color(12, 0, 0, 255), // 31 Color(187, 227, 255, 255), // 32 Color(123, 199, 255, 255), // 33 Color(59, 171, 255, 255), // 34 Color(0, 139, 255, 255), // 35 Color(0, 107, 203, 255), // 36 Color(0, 79, 151, 255), // 37 Color(0, 47, 79, 255), // 38 Color(0, 7, 11, 255), // 39 Color(255, 255, 0, 255), // 40 Color(255, 199, 0, 255), // 41 Color(255, 147, 0, 255), // 42 Color(255, 95, 0, 255), // 43 Color(203, 55, 0, 255), // 44 Color(155, 27, 0, 255), // 45 Color(83, 7, 0, 255), // 46 Color(11, 0, 0, 255), // 47 Color(251, 139, 183, 255), // 48 Color(247, 91, 151, 255), // 49 Color(243, 43, 123, 255), // 50 Color(239, 0, 99, 255), // 51 Color(191, 0, 75, 255), // 52 Color(147, 0, 55, 255), // 53 Color(99, 0, 35, 255), // 54 Color(55, 0, 19, 255), // 55 Color(56, 176, 181, 255), // 56 Color(78, 219, 201, 255), // 57 Color(202, 198, 245, 255), // 58 Color(219, 195, 0, 255), // 59 Color(187, 147, 0, 255), // 60 Color(155, 107, 0, 255), // 61 Color(83, 55, 0, 255), // 62 Color(11, 7, 0, 255), // 63 Color(255, 243, 211, 255), // 64 Color(219, 207, 175, 255), // 65 Color(187, 175, 147, 255), // 66 Color(155, 139, 115, 255), // 67 Color(119, 107, 87, 255), // 68 Color(87, 75, 63, 255), // 69 Color(47, 35, 31, 255), // 70 Color(11, 7, 7, 255), // 71 Color(211, 231, 255, 255), // 72 Color(171, 195, 219, 255), // 73 Color(139, 159, 187, 255), // 74 Color(107, 127, 155, 255), // 75 Color(75, 95, 119, 255), // 76 Color(51, 63, 87, 255), // 77 Color(27, 31, 47, 255), // 78 Color(7, 7, 11, 255), // 79 Color(0, 255, 163, 255), // 80 Color(0, 227, 127, 255), // 81 Color(7, 199, 95, 255), // 82 Color(7, 171, 67, 255), // 83 Color(11, 143, 47, 255), // 84 Color(11, 119, 31, 255), // 85 Color(0, 63, 7, 255), // 86 Color(0, 11, 0, 255), // 87 Color(219, 99, 255, 255), // 88 Color(207, 47, 255, 255), // 89 Color(199, 0, 255, 255), // 90 Color(159, 0, 207, 255), // 91 Color(119, 0, 159, 255), // 92 Color(83, 0, 111, 255), // 93 Color(47, 0, 63, 255), // 94 Color(11, 0, 15, 255), // 95 Color(207, 0, 143, 255), // 96 Color(191, 0, 131, 255), // 97 Color(175, 0, 123, 255), // 98 Color(163, 7, 111, 255), // 99 Color(147, 7, 103, 255), // 100 Color(135, 7, 91, 255), // 101 Color(119, 7, 83, 255), // 102 Color(103, 7, 71, 255), // 103 Color(91, 7, 63, 255), // 104 Color(79, 7, 55, 255), // 105 Color(67, 0, 51, 255), // 106 Color(55, 0, 43, 255), // 107 Color(43, 0, 35, 255), // 108 Color(31, 0, 27, 255), // 109 Color(19, 0, 15, 255), // 110 Color(7, 0, 7, 255), // 111 Color(211, 59, 255, 255), // 112 Color(183, 43, 239, 255), // 113 Color(159, 31, 223, 255), // 114 Color(135, 23, 207, 255), // 115 Color(111, 11, 191, 255), // 116 Color(91, 7, 175, 255), // 117 Color(71, 0, 159, 255), // 118 Color(55, 0, 143, 255), // 119 Color(39, 0, 127, 255), // 120 Color(31, 0, 111, 255), // 121 Color(19, 0, 95, 255), // 122 Color(11, 0, 79, 255), // 123 Color(7, 0, 63, 255), // 124 Color(0, 0, 47, 255), // 125 Color(0, 0, 31, 255), // 126 Color(0, 0, 15, 255), // 127 Color(0, 255, 247, 255), // 128 Color(0, 231, 223, 255), // 129 Color(0, 207, 203, 255), // 130 Color(0, 183, 179, 255), // 131 Color(0, 159, 155, 255), // 132 Color(0, 135, 131, 255), // 133 Color(0, 111, 111, 255), // 134 Color(0, 99, 99, 255), // 135 Color(0, 87, 87, 255), // 136 Color(0, 79, 79, 255), // 137 Color(0, 67, 67, 255), // 138 Color(0, 55, 56, 255), // 139 Color(0, 47, 47, 255), // 140 Color(0, 35, 35, 255), // 141 Color(0, 23, 23, 255), // 142 Color(0, 15, 15, 255), // 143 Color(119, 27, 199, 255), // 144 Color(103, 23, 187, 255), // 145 Color(87, 19, 175, 255), // 146 Color(71, 15, 167, 255), // 147 Color(59, 15, 155, 255), // 148 Color(47, 11, 147, 255), // 149 Color(35, 11, 135, 255), // 150 Color(27, 7, 123, 255), // 151 Color(15, 7, 115, 255), // 152 Color(7, 7, 103, 255), // 153 Color(0, 0, 95, 255), // 154 Color(0, 7, 83, 255), // 155 Color(0, 7, 71, 255), // 156 Color(0, 11, 63, 255), // 157 Color(0, 11, 51, 255), // 158 Color(0, 12, 43, 255), // 159 Color(0, 139, 175, 255), // 160 Color(0, 131, 163, 255), // 161 Color(0, 127, 151, 255), // 162 Color(0, 119, 143, 255), // 163 Color(0, 111, 131, 255), // 164 Color(0, 103, 119, 255), // 165 Color(0, 95, 111, 255), // 166 Color(0, 87, 99, 255), // 167 Color(0, 79, 87, 255), // 168 Color(0, 71, 79, 255), // 169 Color(0, 63, 67, 255), // 170 Color(0, 55, 55, 255), // 171 Color(95, 183, 183, 255), // 172 Color(141, 131, 214, 255), // 173 Color(207, 177, 28, 255), // 174 Color(65, 168, 179, 255), // 175 Color(107, 71, 251, 255), // 176 Color(99, 67, 243, 255), // 177 Color(95, 67, 239, 255), // 178 Color(91, 63, 231, 255), // 179 Color(83, 59, 227, 255), // 180 Color(79, 55, 219, 255), // 181 Color(71, 55, 211, 255), // 182 Color(67, 51, 207, 255), // 183 Color(63, 51, 199, 255), // 184 Color(59, 47, 195, 255), // 185 Color(55, 43, 187, 255), // 186 Color(51, 43, 183, 255), // 187 Color(43, 39, 175, 255), // 188 Color(43, 39, 171, 255), // 189 Color(39, 35, 163, 255), // 190 Color(35, 35, 159, 255), // 191 Color(31, 31, 151, 255), // 192 Color(31, 31, 147, 255), // 193 Color(27, 31, 139, 255), // 194 Color(27, 31, 135, 255), // 195 Color(23, 31, 127, 255), // 196 Color(23, 31, 123, 255), // 197 Color(19, 31, 111, 255), // 198 Color(15, 27, 103, 255), // 199 Color(11, 23, 91, 255), // 200 Color(7, 19, 79, 255), // 201 Color(7, 19, 71, 255), // 202 Color(0, 15, 63, 255), // 203 Color(0, 11, 55, 255), // 204 Color(0, 11, 43, 255), // 205 Color(0, 7, 35, 255), // 206 Color(0, 7, 27, 255), // 207 Color(120, 226, 220, 255), // 208 Color(82, 205, 193, 255), // 209 Color(98, 100, 112, 255), // 210 Color(60, 251, 232, 255), // 211 Color(96, 187, 169, 255), // 212 Color(191, 255, 255, 255), // 213 Color(209, 255, 255, 255), // 214 Color(64, 152, 152, 255), // 215 Color(203, 255, 255, 255), // 216 Color(121, 147, 218, 255), // 217 Color(209, 158, 33, 255), // 218 Color(113, 106, 171, 255), // 219 Color(152, 154, 166, 255), // 220 Color(115, 144, 197, 255), // 221 Color(214, 255, 255, 255), // 222 Color(182, 190, 11, 255), // 223 Color(197, 211, 252, 255), // 224 Color(26, 146, 151, 255), // 225 Color(207, 255, 255, 255), // 226 Color(75, 71, 118, 255), // 227 Color(158, 125, 250, 255), // 228 Color(123, 155, 190, 255), // 229 Color(126, 214, 214, 255), // 230 Color(197, 199, 211, 255), // 231 Color(33, 168, 185, 255), // 232 Color(85, 75, 158, 255), // 233 Color(47, 138, 120, 255), // 234 Color(101, 174, 162, 255), // 235 Color(67, 93, 164, 255), // 236 Color(27, 201, 188, 255), // 237 Color(124, 129, 224, 255), // 238 Color(86, 56, 163, 255), // 239 Color(30, 133, 144, 255), // 240 Color(182, 193, 252, 255), // 241 Color(89, 195, 189, 255), // 242 Color(173, 255, 255, 255), // 243 Color(86, 100, 141, 255), // 244 Color(222, 116, 25, 255), // 245 Color(0, 2, 1, 255), // 246 Color(0, 2, 2, 255), // 247 Color(1, 0, 2, 255), // 248 Color(2, 0, 1, 255), // 249 Color(2, 0, 2, 255), // 250 Color(1, 2, 0, 255), // 251 Color(2, 1, 0, 255), // 252 Color(2, 2, 0, 255), // 253 Color(1, 1, 2, 255), // 254 Color(254, 255, 255, 255) // 255 };deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Anims.cpp000066400000000000000000000634761512772601700264450ustar00rootroot00000000000000#include "JJ2Anims.h" #include "JJ2Anims.Palettes.h" #include "JJ2Block.h" #include "AnimSetMapping.h" #include #include #include #include #include using namespace Death::IO; namespace Jazz2::Compatibility { JJ2Version JJ2Anims::Convert(StringView path, PakWriter& pakWriter, bool isPlus) { JJ2Version version; SmallVector anims; SmallVector samples; auto s = fs::Open(path, FileAccess::Read); if (!s->IsValid()) { LOGE("Cannot open file \"{}\" for reading", path); return JJ2Version::Unknown; } bool seemsLikeCC = false; std::uint32_t magic = s->ReadValueAsLE(); DEATH_ASSERT(magic == 0x42494C41, "Invalid magic number", JJ2Version::Unknown); std::uint32_t signature = s->ReadValueAsLE(); DEATH_ASSERT(signature == 0x00BEBA00, "Invalid signature", JJ2Version::Unknown); std::uint32_t headerLen = s->ReadValueAsLE(); std::uint32_t magicUnknown = s->ReadValueAsLE(); // Probably `uint16_t version` and `uint16_t unknown` DEATH_ASSERT(magicUnknown == 0x18080200, "Invalid version", JJ2Version::Unknown); /*std::uint32_t fileLen =*/ s->ReadValueAsLE(); /*std::uint32_t crc =*/ s->ReadValueAsLE(); std::int32_t setCount = s->ReadValueAsLE(); SmallVector setAddresses(setCount); for (std::int32_t i = 0; i < setCount; i++) { setAddresses[i] = s->ReadValueAsLE(); } DEATH_ASSERT(headerLen == s->GetPosition(), "Invalid header size", JJ2Version::Unknown); // Read content bool isStreamComplete = true; for (std::int32_t i = 0; i < setCount; i++) { if (s->GetPosition() >= s->GetSize()) { isStreamComplete = false; LOGW("Stream should contain {} sets, but found {} sets instead!", setCount, i); break; } std::uint32_t magicANIM = s->ReadValueAsLE(); std::uint8_t animCount = s->ReadValue(); std::uint8_t sndCount = s->ReadValue(); /*std::uint16_t frameCount =*/ s->ReadValueAsLE(); /*std::uint32_t cumulativeSndIndex =*/ s->ReadValueAsLE(); std::int32_t infoBlockLenC = s->ReadValueAsLE(); std::int32_t infoBlockLenU = s->ReadValueAsLE(); std::int32_t frameDataBlockLenC = s->ReadValueAsLE(); std::int32_t frameDataBlockLenU = s->ReadValueAsLE(); std::int32_t imageDataBlockLenC = s->ReadValueAsLE(); std::int32_t imageDataBlockLenU = s->ReadValueAsLE(); std::int32_t sampleDataBlockLenC = s->ReadValueAsLE(); std::int32_t sampleDataBlockLenU = s->ReadValueAsLE(); JJ2Block infoBlock(s, infoBlockLenC, infoBlockLenU); JJ2Block frameDataBlock(s, frameDataBlockLenC, frameDataBlockLenU); JJ2Block imageDataBlock(s, imageDataBlockLenC, imageDataBlockLenU); JJ2Block sampleDataBlock(s, sampleDataBlockLenC, sampleDataBlockLenU); if (magicANIM != 0x4D494E41) { LOGD("Header for set {} is incorrect (bad magic value), skipping", i); continue; } for (std::uint16_t j = 0; j < animCount; j++) { AnimSection& anim = anims.emplace_back(); anim.Set = i; anim.Anim = j; anim.FrameCount = infoBlock.ReadUInt16(); anim.FrameRate = infoBlock.ReadUInt16(); anim.Frames.resize(anim.FrameCount); // Skip the rest, seems to be 0x00000000 for all headers infoBlock.DiscardBytes(4); if (anim.FrameCount > 0) { for (std::uint16_t k = 0; k < anim.FrameCount; k++) { AnimFrameSection& frame = anim.Frames[k]; frame.SizeX = frameDataBlock.ReadInt16(); frame.SizeY = frameDataBlock.ReadInt16(); frame.ColdspotX = frameDataBlock.ReadInt16(); frame.ColdspotY = frameDataBlock.ReadInt16(); frame.HotspotX = frameDataBlock.ReadInt16(); frame.HotspotY = frameDataBlock.ReadInt16(); frame.GunspotX = frameDataBlock.ReadInt16(); frame.GunspotY = frameDataBlock.ReadInt16(); frame.ImageAddr = frameDataBlock.ReadInt32(); frame.MaskAddr = frameDataBlock.ReadInt32(); // Adjust normalized position // In the output images, we want to make the hotspot and image size constant. anim.NormalizedHotspotX = std::max((std::int16_t)-frame.HotspotX, anim.NormalizedHotspotX); anim.NormalizedHotspotY = std::max((std::int16_t)-frame.HotspotY, anim.NormalizedHotspotY); anim.LargestOffsetX = std::max((std::int16_t)(frame.SizeX + frame.HotspotX), anim.LargestOffsetX); anim.LargestOffsetY = std::max((std::int16_t)(frame.SizeY + frame.HotspotY), anim.LargestOffsetY); anim.AdjustedSizeX = std::max( (std::int16_t)(anim.NormalizedHotspotX + anim.LargestOffsetX), anim.AdjustedSizeX ); anim.AdjustedSizeY = std::max( (std::int16_t)(anim.NormalizedHotspotY + anim.LargestOffsetY), anim.AdjustedSizeY ); std::int32_t dpos = (frame.ImageAddr + 4); imageDataBlock.SeekTo(dpos - 4); std::uint16_t width2 = imageDataBlock.ReadUInt16(); imageDataBlock.SeekTo(dpos - 2); /*std::uint16_t height2 =*/ imageDataBlock.ReadUInt16(); frame.DrawTransparent = (width2 & 0x8000) > 0; std::int32_t pxRead = 0; std::int32_t pxTotal = (frame.SizeX * frame.SizeY); bool lastOpEmpty = true; frame.ImageData = std::make_unique(pxTotal); imageDataBlock.SeekTo(dpos); while (pxRead < pxTotal) { std::uint8_t op = imageDataBlock.ReadByte(); if (op < 0x80) { // Skip the given number of pixels, writing them with the transparent color 0, array should be already zeroed pxRead += op; } else if (op == 0x80) { // Skip until the end of the line, array should be already zeroed std::uint16_t linePxLeft = (std::uint16_t)(frame.SizeX - pxRead % frame.SizeX); if (pxRead % frame.SizeX == 0 && !lastOpEmpty) { linePxLeft = 0; } pxRead += linePxLeft; } else { // Copy specified amount of pixels (ignoring the high bit) std::uint16_t bytesToRead = (std::uint16_t)(op & 0x7F); imageDataBlock.ReadRawBytes(frame.ImageData.get() + pxRead, bytesToRead); pxRead += bytesToRead; } lastOpEmpty = (op == 0x80); } // TODO: Sprite mask /*frame.MaskData = std::make_unique(pxTotal); if (frame.MaskAddr != 0xFFFFFFFF) { imageDataBlock.SeekTo(frame.MaskAddr); pxRead = 0; while (pxRead < pxTotal) { std::uint8_t b = imageDataBlock.ReadByte(); for (std::uint8_t bit = 0; bit < 8 && (pxRead + bit) < pxTotal; ++bit) { frame.MaskData[pxRead + bit] = ((b & (1 << (7 - bit))) != 0); } pxRead += 8; } }*/ } } } if (i == 65 && animCount > 5) { seemsLikeCC = true; } for (std::uint16_t j = 0; j < sndCount; j++) { SampleSection& sample = samples.emplace_back(); sample.IdInSet = j; sample.Set = i; std::int32_t totalSize = sampleDataBlock.ReadInt32(); std::uint32_t magicRIFF = sampleDataBlock.ReadUInt32(); std::int32_t chunkSize = sampleDataBlock.ReadInt32(); // "ASFF" for 1.20, "AS " for 1.24 std::uint32_t format = sampleDataBlock.ReadUInt32(); DEATH_ASSERT(format == 0x46465341 || format == 0x20205341, "Invalid sound format", JJ2Version::Unknown); bool isASFF = (format == 0x46465341); std::uint32_t magicSAMP = sampleDataBlock.ReadUInt32(); /*std::uint32_t sampSize =*/ sampleDataBlock.ReadUInt32(); DEATH_ASSERT(magicRIFF == 0x46464952 && magicSAMP == 0x504D4153, "Invalid sound format", JJ2Version::Unknown); // Padding/unknown data #1 // For set 0 sample 0: // 1.20 1.24 // +00 00 00 00 00 00 00 00 00 +00 40 00 00 00 00 00 00 00 // +08 00 00 00 00 00 00 00 00 +08 00 00 00 00 00 00 00 00 // +10 00 00 00 00 00 00 00 00 +10 00 00 00 00 00 00 00 00 // +18 00 00 00 00 +18 00 00 00 00 00 00 00 00 // +20 00 00 00 00 00 40 FF 7F sampleDataBlock.DiscardBytes(40 - (isASFF ? 12 : 0)); if (isASFF) { // All 1.20 samples seem to be 8-bit. Some of them are among those // for which 1.24 reads as 24-bit but that might just be a mistake. sampleDataBlock.DiscardBytes(2); sample.Multiplier = 0; } else { // for 1.24. 1.20 has "20 40" instead in s0s0 which makes no sense sample.Multiplier = sampleDataBlock.ReadUInt16(); } // Unknown. s0s0 1.20: 00 80, 1.24: 80 00 sampleDataBlock.DiscardBytes(2); /*uint32_t payloadSize =*/ sampleDataBlock.ReadUInt32(); // Padding #2, all zeroes in both sampleDataBlock.DiscardBytes(8); sample.SampleRate = sampleDataBlock.ReadUInt32(); sample.DataSize = chunkSize - 76 + (isASFF ? 12 : 0); sample.Data = std::make_unique(sample.DataSize); sampleDataBlock.ReadRawBytes(sample.Data.get(), sample.DataSize); // Padding #3 sampleDataBlock.DiscardBytes(4); /*if (sample.Data.Length < actualDataSize) { Log.Write(LogType.Warning, "Sample " + j + " in set " + i + " was shorter than expected! Expected " + actualDataSize + " bytes, but read " + sample.Data.Length + " instead."); }*/ if (totalSize > chunkSize + 12) { // Sample data is probably aligned to X bytes since the next sample doesn't always appear right after the first ends. LOGW("Adjusting read offset of sample {} in set {} by {} bytes.", j, i, (totalSize - chunkSize - 12)); sampleDataBlock.DiscardBytes(totalSize - chunkSize - 12); } } } // Detect version to import if (headerLen == 464) { if (isStreamComplete) { version = JJ2Version::BaseGame; LOGI("Detected Jazz Jackrabbit 2 (v1.20/1.23)"); } else { version = JJ2Version::BaseGame | JJ2Version::SharewareDemo; LOGI("Detected Jazz Jackrabbit 2 (v1.20/1.23): Shareware Demo"); } } else if (headerLen == 500) { if (!isStreamComplete) { version = JJ2Version::TSF | JJ2Version::SharewareDemo; // TODO: This version is not supported (yet) LOGE("Detected Jazz Jackrabbit 2: The Secret Files Demo - This version is not supported!"); return JJ2Version::Unknown; } else if (seemsLikeCC) { version = JJ2Version::CC; LOGI("Detected Jazz Jackrabbit 2: Christmas Chronicles"); } else { version = JJ2Version::TSF; LOGI("Detected Jazz Jackrabbit 2: The Secret Files"); } } else if (headerLen == 476) { version = JJ2Version::HH; LOGI("Detected Jazz Jackrabbit 2: Holiday Hare '98"); } else if (headerLen == 64) { version = JJ2Version::PlusExtension; if (!isPlus) { LOGE("Detected Jazz Jackrabbit 2 Plus extension - This version is not supported!"); return JJ2Version::Unknown; } } else { version = JJ2Version::Unknown; LOGE("Could not determine the version, header size: {} bytes", headerLen); } ImportAnimations(pakWriter, version, anims); ImportAudioSamples(pakWriter, version, samples); return version; } void JJ2Anims::ImportAnimations(PakWriter& pakWriter, JJ2Version version, SmallVectorImpl& anims) { if (anims.empty()) { return; } LOGI("Importing animations..."); AnimSetMapping animMapping = AnimSetMapping::GetAnimMapping(version); for (auto& anim : anims) { if (anim.FrameCount == 0) { continue; } AnimSetMapping::Entry* entry = animMapping.Get(anim.Set, anim.Anim); if (entry == nullptr || entry->Category == AnimSetMapping::Discard) { continue; } std::int32_t sizeX = (anim.AdjustedSizeX + AddBorder * 2); std::int32_t sizeY = (anim.AdjustedSizeY + AddBorder * 2); // Determine the frame configuration to use. // Each asset must fit into a 4096 by 4096 texture, // as that is the smallest texture size we have decided to support. if (anim.FrameCount > 1) { std::int32_t rows = std::max(1, (std::int32_t)std::ceil(sqrt(anim.FrameCount * sizeX / sizeY))); std::int32_t columns = std::max(1, (std::int32_t)std::ceil(anim.FrameCount * 1.0 / rows)); // Do a bit of optimization, as the above algorithm ends occasionally with some extra space // (it is careful with not underestimating the required space) while (columns * (rows - 1) >= anim.FrameCount) { rows--; } anim.FrameConfigurationX = (std::uint8_t)columns; anim.FrameConfigurationY = (std::uint8_t)rows; } else { anim.FrameConfigurationX = (std::uint8_t)anim.FrameCount; anim.FrameConfigurationY = 1; } // TODO: Hardcoded name bool applyToasterPowerUpFix = (entry->Category == "Object"_s && entry->Name == "powerup_upgrade_toaster"_s); if (applyToasterPowerUpFix) { LOGI("Applying \"Toaster PowerUp\" palette fix to {}:{}", anim.Set, anim.Anim); } bool applyVineFix = (entry->Category == "Object"_s && entry->Name == "vine"_s); if (applyVineFix) { LOGI("Applying \"Vine\" palette fix to {}:{}", anim.Set, anim.Anim); } bool applyFlyCarrotFix = (entry->Category == "Pickup"_s && entry->Name == "carrot_fly"_s); if (applyFlyCarrotFix) { // This image has 4 wrong pixels that should be transparent LOGI("Applying \"Fly Carrot\" image fix to {}:{}", anim.Set, anim.Anim); } bool playerFlareFix = ((entry->Category == "Jazz"_s || entry->Category == "Spaz"_s) && (entry->Name == "shoot_ver"_s || entry->Name == "vine_shoot_up"_s)); if (playerFlareFix) { // This image has already applied weapon flare, remove it LOGI("Applying \"Player Flare\" image fix to {}:{}", anim.Set, anim.Anim); } String filename; if (entry->Name.empty()) { LOGE("Entry name is empty"); continue; } filename = fs::CombinePath({ "Animations"_s, entry->Category, String(entry->Name + ".aura"_s) }); std::int32_t stride = sizeX * anim.FrameConfigurationX; std::unique_ptr pixels = std::make_unique(stride * sizeY * anim.FrameConfigurationY * 4); for (std::int32_t j = 0; j < anim.Frames.size(); j++) { auto& frame = anim.Frames[j]; std::int32_t offsetX = anim.NormalizedHotspotX + frame.HotspotX; std::int32_t offsetY = anim.NormalizedHotspotY + frame.HotspotY; for (std::int32_t y = 0; y < frame.SizeY; y++) { for (std::int32_t x = 0; x < frame.SizeX; x++) { std::int32_t targetX = (j % anim.FrameConfigurationX) * sizeX + offsetX + x + AddBorder; std::int32_t targetY = (j / anim.FrameConfigurationX) * sizeY + offsetY + y + AddBorder; std::uint8_t colorIdx = frame.ImageData[frame.SizeX * y + x]; // Apply palette fixes if (applyToasterPowerUpFix) { if ((x >= 3 && y >= 4 && x <= 15 && y <= 20) || (x >= 2 && y >= 7 && x <= 15 && y <= 19)) { colorIdx = ToasterPowerUpFix[colorIdx]; } } else if (applyVineFix) { if (colorIdx == 128) { colorIdx = 0; } } else if (applyFlyCarrotFix) { if (colorIdx >= 68 && colorIdx <= 70) { colorIdx = 0; } } else if (playerFlareFix) { if (j == 0 && y < 14 && (colorIdx == 15 || (colorIdx >= 40 && colorIdx <= 42))) { colorIdx = 0; } } if (entry->Palette == JJ2DefaultPalette::Menu) { const Color& src = MenuPalette[colorIdx]; std::uint8_t a; if (colorIdx == 0) { a = 0; } else if (frame.DrawTransparent) { a = 140 * src.A / 255; } else { a = src.A; } pixels[(stride * targetY + targetX) * 4] = src.R; pixels[(stride * targetY + targetX) * 4 + 1] = src.G; pixels[(stride * targetY + targetX) * 4 + 2] = src.B; pixels[(stride * targetY + targetX) * 4 + 3] = a; } else { std::uint8_t a; if (colorIdx == 0) { a = 0; } else if (frame.DrawTransparent) { a = 140; } else { a = 255; } pixels[(stride * targetY + targetX) * 4] = colorIdx; pixels[(stride * targetY + targetX) * 4 + 1] = colorIdx; pixels[(stride * targetY + targetX) * 4 + 2] = colorIdx; pixels[(stride * targetY + targetX) * 4 + 3] = a; } } } } bool applyLoriLiftFix = (entry->Category == "Lori"_s && (entry->Name == "lift"_s || entry->Name == "lift_start"_s || entry->Name == "lift_end"_s)); if (applyLoriLiftFix) { LOGI("Applying \"Lori\" hotspot fix to {}:{}", anim.Set, anim.Anim); anim.NormalizedHotspotX = 20; anim.NormalizedHotspotY = 4; } // TODO: Use single channel instead MemoryStream so(16384); WriteImageToStream(so, pixels.get(), sizeX, sizeY, 4, anim, entry); so.Seek(0, SeekOrigin::Begin); bool success = pakWriter.AddFile(so, filename, PakPreferredCompression::Deflate); DEATH_ASSERT(success, "Failed to add file to .pak container", ); /*if (!string.IsNullOrEmpty(data.Name) && !data.SkipNormalMap) { PngWriter normalMap = NormalMapGenerator.FromSprite(img, new Point(currentAnim.FrameConfigurationX, currentAnim.FrameConfigurationY), !data.AllowRealtimePalette && data.Palette == JJ2DefaultPalette.ByIndex ? JJ2DefaultPalette.Sprite : null); normalMap.Save(filename.Replace(".png", ".n.png")); }*/ } } void JJ2Anims::ImportAudioSamples(PakWriter& pakWriter, JJ2Version version, SmallVectorImpl& samples) { if (samples.empty()) { return; } LOGI("Importing audio samples..."); AnimSetMapping mapping = AnimSetMapping::GetSampleMapping(version); for (auto& sample : samples) { AnimSetMapping::Entry* entry = mapping.Get(sample.Set, sample.IdInSet); if (entry == nullptr || entry->Category == AnimSetMapping::Discard) { continue; } String filename; if (entry->Name.empty()) { LOGE("Entry name is empty"); continue; } filename = fs::CombinePath({ "Animations"_s, entry->Category, String(entry->Name + ".wav"_s) }); MemoryStream so(16384); // TODO: The modulo here essentially clips the sample to 8- or 16-bit. // There are some samples (at least the Rapier random noise) that at least get reported as 24-bit // by the read header data. It is not clear if they actually are or if the header data is just // read incorrectly, though - one would think the data would need to be reshaped between 24 and 8 // but it works just fine as is. std::int32_t bytesPerSample = (sample.Multiplier / 4) % 2 + 1; std::int32_t dataOffset = 0; if (sample.Data[0] == 0x00 && sample.Data[1] == 0x00 && sample.Data[2] == 0x00 && sample.Data[3] == 0x00 && (sample.Data[4] != 0x00 || sample.Data[5] != 0x00 || sample.Data[6] != 0x00 || sample.Data[7] != 0x00) && (sample.Data[7] == 0x00 || sample.Data[8] == 0x00)) { // Trim first 8 samples (bytes) to prevent popping dataOffset = 8; } // Create PCM wave file // Main header so.Write("RIFF", 4); so.WriteValueAsLE(36 + sample.DataSize - dataOffset); // File size so.Write("WAVE", 4); // Format header so.Write("fmt ", 4); so.WriteValueAsLE(16); // Header remainder length so.WriteValueAsLE(1); // Format = PCM so.WriteValueAsLE(1); // Channels so.WriteValueAsLE(sample.SampleRate); // Sample rate so.WriteValueAsLE(sample.SampleRate * bytesPerSample); // Bytes per second so.WriteValueAsLE(bytesPerSample * 0x00080001); // Payload so.Write("data", 4); so.WriteValueAsLE(sample.DataSize - dataOffset); // Payload size for (std::uint32_t k = dataOffset; k < sample.DataSize; k++) { so.WriteValue((bytesPerSample << 7) ^ sample.Data[k]); } so.Seek(0, SeekOrigin::Begin); bool success = pakWriter.AddFile(so, filename, PakPreferredCompression::Deflate); DEATH_ASSERT(success, "Failed to add file to .pak container", ); } } void JJ2Anims::WriteImageToFile(StringView targetPath, const std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry) { FileStream so(targetPath, FileAccess::Write); DEATH_ASSERT(so.IsValid(), "Cannot open file for writing", ); WriteImageToStream(so, data, width, height, channelCount, anim, entry); } void JJ2Anims::WriteImageToStream(Stream& targetStream, const std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry) { std::uint8_t flags = 0x00; if (entry != nullptr) { flags |= 0x80; /*if (!entry->AllowRealtimePalette && entry->Palette == JJ2DefaultPalette::Sprite) { flags |= 0x01; } if (!entry->AllowRealtimePalette) { // Use Linear Sampling, only if the palette is applied in pre-processing stage flags |= 0x02; }*/ if (entry->Palette != JJ2DefaultPalette::Sprite) { flags |= 0x01; } if (entry->SkipNormalMap) { flags |= 0x02; } } targetStream.WriteValueAsLE(0xB8EF8498E2BFBBEF); targetStream.WriteValueAsLE(0x208F); targetStream.WriteValue(0x02); // Version 2 is reserved for sprites (or bigger images) targetStream.WriteValue(flags); targetStream.WriteValue(channelCount); targetStream.WriteValueAsLE(width); targetStream.WriteValueAsLE(height); // Include Sprite extension if (entry != nullptr) { targetStream.WriteValue(anim.FrameConfigurationX); targetStream.WriteValue(anim.FrameConfigurationY); targetStream.WriteValueAsLE(anim.FrameCount); targetStream.WriteValueAsLE(anim.FrameRate == 0 ? 0 : 256 * 5 / anim.FrameRate); if (anim.NormalizedHotspotX != 0 || anim.NormalizedHotspotY != 0) { targetStream.WriteValueAsLE(anim.NormalizedHotspotX + AddBorder); targetStream.WriteValueAsLE(anim.NormalizedHotspotY + AddBorder); } else { targetStream.WriteValueAsLE(UINT16_MAX); targetStream.WriteValueAsLE(UINT16_MAX); } if (anim.Frames[0].ColdspotX != 0 || anim.Frames[0].ColdspotY != 0) { targetStream.WriteValueAsLE((anim.NormalizedHotspotX + anim.Frames[0].HotspotX) - anim.Frames[0].ColdspotX + AddBorder); targetStream.WriteValueAsLE((anim.NormalizedHotspotY + anim.Frames[0].HotspotY) - anim.Frames[0].ColdspotY + AddBorder); } else { targetStream.WriteValueAsLE(UINT16_MAX); targetStream.WriteValueAsLE(UINT16_MAX); } if (anim.Frames[0].GunspotX != 0 || anim.Frames[0].GunspotY != 0) { targetStream.WriteValueAsLE((anim.NormalizedHotspotX + anim.Frames[0].HotspotX) - anim.Frames[0].GunspotX + AddBorder); targetStream.WriteValueAsLE((anim.NormalizedHotspotY + anim.Frames[0].HotspotY) - anim.Frames[0].GunspotY + AddBorder); } else { targetStream.WriteValueAsLE(UINT16_MAX); targetStream.WriteValueAsLE(UINT16_MAX); } width *= anim.FrameConfigurationX; height *= anim.FrameConfigurationY; } WriteImageContent(targetStream, data, width, height, channelCount); } void JJ2Anims::WriteImageContent(Stream& so, const std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount) { typedef union { struct { std::uint8_t r, g, b, a; } rgba; std::uint32_t v; } rgba_t; #define QOI_OP_INDEX 0x00 /* 00xxxxxx */ #define QOI_OP_DIFF 0x40 /* 01xxxxxx */ #define QOI_OP_LUMA 0x80 /* 10xxxxxx */ #define QOI_OP_RUN 0xc0 /* 11xxxxxx */ #define QOI_OP_RGB 0xfe /* 11111110 */ #define QOI_OP_RGBA 0xff /* 11111111 */ #define QOI_MASK_2 0xc0 /* 11000000 */ #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) auto pixels = (const std::uint8_t*)data; rgba_t index[64] {}; rgba_t px, px_prev; std::int32_t run = 0; px_prev.rgba.r = 0; px_prev.rgba.g = 0; px_prev.rgba.b = 0; px_prev.rgba.a = 255; px = px_prev; std::int32_t px_len = width * height * channelCount; std::int32_t px_end = px_len - channelCount; for (std::int32_t px_pos = 0; px_pos < px_len; px_pos += channelCount) { if (channelCount == 4) { px = *(rgba_t*)(pixels + px_pos); } else { px.rgba.r = pixels[px_pos + 0]; px.rgba.g = pixels[px_pos + 1]; px.rgba.b = pixels[px_pos + 2]; } if (px.v == px_prev.v) { run++; if (run == 62 || px_pos == px_end) { so.WriteValue(QOI_OP_RUN | (run - 1)); run = 0; } } else { std::int32_t index_pos; if (run > 0) { so.WriteValue(QOI_OP_RUN | (run - 1)); run = 0; } index_pos = QOI_COLOR_HASH(px) & (64 - 1); if (index[index_pos].v == px.v) { so.WriteValue(QOI_OP_INDEX | index_pos); } else { index[index_pos] = px; if (px.rgba.a == px_prev.rgba.a) { std::int8_t vr = px.rgba.r - px_prev.rgba.r; std::int8_t vg = px.rgba.g - px_prev.rgba.g; std::int8_t vb = px.rgba.b - px_prev.rgba.b; std::int8_t vg_r = vr - vg; std::int8_t vg_b = vb - vg; if ( vr > -3 && vr < 2 && vg > -3 && vg < 2 && vb > -3 && vb < 2 ) { so.WriteValue(QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2)); } else if ( vg_r > -9 && vg_r < 8 && vg > -33 && vg < 32 && vg_b > -9 && vg_b < 8 ) { so.WriteValue(QOI_OP_LUMA | (vg + 32)); so.WriteValue((vg_r + 8) << 4 | (vg_b + 8)); } else { so.WriteValue(QOI_OP_RGB); so.WriteValue(px.rgba.r); so.WriteValue(px.rgba.g); so.WriteValue(px.rgba.b); } } else { so.WriteValue(QOI_OP_RGBA); so.WriteValue(px.rgba.r); so.WriteValue(px.rgba.g); so.WriteValue(px.rgba.b); so.WriteValue(px.rgba.a); } } } px_prev = px; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Anims.h000066400000000000000000000047561512772601700261060ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Version.h" #include "AnimSetMapping.h" #include #include #include #include #include using namespace Death::Containers; using namespace Death::IO; using namespace nCine; namespace Jazz2::Compatibility { /** @brief Parses original `.j2a` animation files */ class JJ2Anims { public: #ifndef DOXYGEN_GENERATING_OUTPUT static constexpr std::uint16_t CacheVersion = 35; #endif static JJ2Version Convert(StringView path, PakWriter& pakWriter, bool isPlus = false); static void WriteImageContent(Stream& so, const std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount); private: static constexpr int32_t AddBorder = 2; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct AnimFrameSection { std::int16_t SizeX, SizeY; std::int16_t ColdspotX, ColdspotY; std::int16_t HotspotX, HotspotY; std::int16_t GunspotX, GunspotY; std::unique_ptr ImageData; // TODO: Sprite mask //std::unique_ptr MaskData; std::int32_t ImageAddr; std::int32_t MaskAddr; bool DrawTransparent; }; struct AnimSection { std::uint16_t FrameCount; std::uint16_t FrameRate; SmallVector Frames; std::int32_t Set; std::uint16_t Anim; std::int16_t AdjustedSizeX, AdjustedSizeY; std::int16_t LargestOffsetX, LargestOffsetY; std::int16_t NormalizedHotspotX, NormalizedHotspotY; std::int8_t FrameConfigurationX, FrameConfigurationY; }; struct SampleSection { std::int32_t Set; std::uint16_t IdInSet; std::uint32_t SampleRate; std::uint32_t DataSize; std::unique_ptr Data; std::uint16_t Multiplier; }; #endif JJ2Anims(); static void ImportAnimations(PakWriter& pakWriter, JJ2Version version, SmallVectorImpl& anims); static void ImportAudioSamples(PakWriter& pakWriter, JJ2Version version, SmallVectorImpl& samples); static void WriteImageToFile(StringView targetPath, const std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry); static void WriteImageToStream(Stream& targetStream, const std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount, const AnimSection& anim, AnimSetMapping::Entry* entry); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Block.cpp000066400000000000000000000076451512772601700264240ustar00rootroot00000000000000#include "JJ2Block.h" #include #include #include using namespace Death::Memory; using namespace Death::IO::Compression; namespace Jazz2::Compatibility { JJ2Block::JJ2Block(std::unique_ptr& s, std::int32_t length, std::int32_t uncompressedLength) : _length(0), _offset(0) { if (uncompressedLength > 0) { s->Seek(2, SeekOrigin::Current); _buffer = std::make_unique(uncompressedLength); DeflateStream uc(*s, length - 2); uc.Read(_buffer.get(), uncompressedLength); _length = (uc.IsValid() ? uncompressedLength : 0); } else { _buffer = std::make_unique(length); s->Read(_buffer.get(), length); _length = length; } } void JJ2Block::SeekTo(std::int32_t offset) { _offset = offset; } void JJ2Block::DiscardBytes(std::int32_t length) { _offset += length; } bool JJ2Block::ReadBool() { if (_offset >= _length) { _offset = INT32_MAX; return false; } return _buffer[_offset++] != 0x00; } std::uint8_t JJ2Block::ReadByte() { if (_offset >= _length) { _offset = INT32_MAX; return 0; } return _buffer[_offset++]; } std::int16_t JJ2Block::ReadInt16() { if (_offset > _length - 2) { _offset = INT32_MAX; return false; } std::int16_t result; std::memcpy(&result, &_buffer[_offset], sizeof(result)); _offset += sizeof(result); return AsLE(result); } std::uint16_t JJ2Block::ReadUInt16() { if (_offset > _length - 2) { _offset = INT32_MAX; return false; } std::uint16_t result; std::memcpy(&result, &_buffer[_offset], sizeof(result)); _offset += sizeof(result); return AsLE(result); } std::int32_t JJ2Block::ReadInt32() { if (_offset > _length - 4) { _offset = INT32_MAX; return false; } std::int32_t result; std::memcpy(&result, &_buffer[_offset], sizeof(result)); _offset += sizeof(result); return AsLE(result); } std::uint32_t JJ2Block::ReadUInt32() { if (_offset > _length - 4) { _offset = INT32_MAX; return false; } std::uint32_t result; std::memcpy(&result, &_buffer[_offset], sizeof(result)); _offset += sizeof(result); return AsLE(result); } std::int32_t JJ2Block::ReadUint7bitEncoded() { std::int32_t result = 0; while (true) { if (_offset >= _length) { _offset = INT32_MAX; break; } std::uint8_t current = _buffer[_offset++]; result |= (current & 0x7F); if (current >= 0x80) { result <<= 7; } else { break; } } return result; } float JJ2Block::ReadFloat() { if (_offset > _length - 4) { _offset = INT32_MAX; return false; } float result; std::memcpy(&result, &_buffer[_offset], sizeof(result)); _offset += sizeof(result); return AsLE(result); } float JJ2Block::ReadFloatEncoded() { return ((float)ReadInt32() / 65536.0f); } void JJ2Block::ReadRawBytes(std::uint8_t* dst, std::int32_t length) { std::int32_t bytesLeft = _length - _offset; bool endOfStream = false; if (length > bytesLeft) { length = bytesLeft; endOfStream = true; } if (length > 0) { std::memcpy(dst, _buffer.get() + _offset, length); } if (endOfStream) { _offset = INT32_MAX; } else { _offset += length; } } StringView JJ2Block::ReadString(std::int32_t length, bool trimToNull) { std::int32_t bytesLeft = _length - _offset; bool endOfStream = false; if (length > bytesLeft) { length = bytesLeft; endOfStream = true; } std::int32_t realLength = length; if (trimToNull) { for (std::int32_t i = 0; i < realLength; i++) { if (_buffer[_offset + i] == '\0') { realLength = i; break; } } } else { while (realLength > 0 && (_buffer[_offset + realLength - 1] == '\0' || _buffer[_offset + realLength - 1] == ' ')) { realLength--; } } StringView result((const char*)&_buffer[_offset], realLength); if (endOfStream) { _offset = INT32_MAX; } else { _offset += length; } return result; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Block.h000066400000000000000000000021421512772601700260540ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include #include #include using namespace Death::Containers; using namespace Death::IO; namespace Jazz2::Compatibility { /** @brief Processes compressed or uncompressed blocks from original files (in little endian) */ class JJ2Block { public: JJ2Block(std::unique_ptr& s, std::int32_t length, std::int32_t uncompressedLength = 0); void SeekTo(std::int32_t offset); void DiscardBytes(std::int32_t length); bool ReadBool(); std::uint8_t ReadByte(); std::int16_t ReadInt16(); std::uint16_t ReadUInt16(); std::int32_t ReadInt32(); std::uint32_t ReadUInt32(); std::int32_t ReadUint7bitEncoded(); float ReadFloat(); float ReadFloatEncoded(); void ReadRawBytes(std::uint8_t* dst, std::int32_t length); StringView ReadString(std::int32_t length, bool trimToNull); bool ReachedEndOfStream() { return (_offset == INT32_MAX); } std::int32_t GetLength() { return _length; } private: std::unique_ptr _buffer; std::int32_t _length; std::int32_t _offset; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Data.cpp000066400000000000000000000200551512772601700262310ustar00rootroot00000000000000#include "JJ2Data.h" #include "JJ2Anims.h" #include "JJ2Anims.Palettes.h" #include "JJ2Block.h" #include "../ContentResolver.h" #include "../../nCine/Base/Algorithms.h" #include #include #include #include #include using namespace Death; using namespace Death::Containers::Literals; using namespace Death::IO::Compression; using namespace nCine; namespace Jazz2::Compatibility { bool JJ2Data::Open(StringView path, bool strictParser) { auto s = fs::Open(path, FileAccess::Read); if (!s->IsValid()) { LOGE("Cannot open file \"{}\" for reading", path); return false; } std::uint32_t magic = s->ReadValueAsLE(); std::uint32_t signature = s->ReadValueAsLE(); DEATH_ASSERT(magic == 0x42494C50 /*PLIB*/ && signature == 0xBEBAADDE, "Invalid signature", false); /*std::uint32_t version =*/ s->ReadValueAsLE(); std::uint32_t recordedSize = s->ReadValueAsLE(); DEATH_ASSERT(!strictParser || s->GetSize() == recordedSize, "Unexpected file size", false); /*uint32_t recordedCRC =*/ s->ReadValueAsLE(); std::int32_t headerBlockPackedSize = s->ReadValueAsLE(); std::int32_t headerBlockUnpackedSize = s->ReadValueAsLE(); JJ2Block headerBlock(s, headerBlockPackedSize, headerBlockUnpackedSize); std::int32_t baseOffset = (std::int32_t)s->GetPosition(); while (s->GetPosition() < s->GetSize()) { StringView name = headerBlock.ReadString(32, true); std::uint32_t type = headerBlock.ReadUInt32(); std::uint32_t offset = headerBlock.ReadUInt32(); /*std::uint32_t fileCRC =*/ headerBlock.ReadUInt32(); std::int32_t filePackedSize = headerBlock.ReadInt32(); std::int32_t fileUnpackedSize = headerBlock.ReadInt32(); s->Seek(baseOffset + offset, SeekOrigin::Begin); JJ2Block fileBlock(s, filePackedSize, fileUnpackedSize); DEATH_ASSERT(fileBlock.GetLength() == fileUnpackedSize, "Unexpected item size", false); Item& item = Items.emplace_back(); item.Filename = name; item.Type = type; item.Blob = std::make_unique(fileUnpackedSize); item.Size = fileUnpackedSize; fileBlock.ReadRawBytes(item.Blob.get(), fileUnpackedSize); } return true; } void JJ2Data::Extract(StringView targetPath) { fs::CreateDirectories(targetPath); for (auto& item : Items) { auto so = fs::Open(fs::CombinePath(targetPath, item.Filename), FileAccess::Write); if (!so->IsValid()) { LOGE("Cannot open file \"{}\" for writing", item.Filename); continue; } so->Write(item.Blob.get(), item.Size); } } void JJ2Data::Convert(PakWriter& pakWriter, JJ2Version version) { AnimSetMapping animMapping = AnimSetMapping::GetSampleMapping(version); auto animationsUiPath = fs::CombinePath("Animations"_s, "UI"_s); for (auto& item : Items) { if (item.Filename == "SoundFXList.Intro"_s) { ConvertSfxList(item, pakWriter, fs::CombinePath("Cinematics"_s, "intro.j2sfx"), animMapping); } else if (item.Filename == "SoundFXList.Ending"_s) { ConvertSfxList(item, pakWriter, fs::CombinePath("Cinematics"_s, "ending.j2sfx"), animMapping); } else if (item.Filename == "Menu.Texture.16x16"_s) { ConvertMenuImage(item, pakWriter, fs::CombinePath(animationsUiPath, "menu16.aura"), 16, 16); } else if (item.Filename == "Menu.Texture.32x32"_s) { ConvertMenuImage(item, pakWriter, fs::CombinePath(animationsUiPath, "menu32.aura"), 32, 32); } else if (item.Filename == "Menu.Texture.128x128"_s) { ConvertMenuImage(item, pakWriter, fs::CombinePath(animationsUiPath, "menu128.aura"), 128, 128); } } } void JJ2Data::ConvertSfxList(const Item& item, PakWriter& pakWriter, StringView targetPath, AnimSetMapping& animMapping) { #pragma pack(push, 1) struct SoundFXList { std::uint32_t Frame; std::uint32_t Sample; std::uint32_t Volume; std::uint32_t Panning; }; #pragma pack(pop) MemoryStream so(16384); so.WriteValueAsLE(0x2095A59FF0BFBBEF); // Signature so.WriteValue(ContentResolver::SfxListFile); so.WriteValueAsLE(1); HashMap sampleToIndex; SmallVector indexToSample; std::int32_t itemCount = item.Size / sizeof(SoundFXList); std::int32_t itemRealCount = 0; std::int32_t sampleCount = 0; for (std::int32_t i = 0; i < itemCount; i++) { SoundFXList sfx; std::memcpy(&sfx, &item.Blob[i * sizeof(SoundFXList)], sizeof(SoundFXList)); if (sfx.Frame == UINT32_MAX) { break; } auto it = sampleToIndex.find(sfx.Sample); if (it == sampleToIndex.end()) { sampleToIndex.emplace(sfx.Sample, sampleCount); indexToSample.emplace_back(sfx.Sample); sampleCount++; } itemRealCount++; } so.WriteValueAsLE(sampleCount); for (std::int32_t i = 0; i < sampleCount; i++) { auto sample = animMapping.GetByOrdinal(indexToSample[i]); if (sample == nullptr) { so.WriteValue(0); } else { String samplePath = sample->Category + '/' + sample->Name + ".wav"_s; so.WriteValue((std::uint8_t)samplePath.size()); so.Write(samplePath.data(), (std::uint32_t)samplePath.size()); } } so.WriteValueAsLE(itemRealCount); for (std::int32_t i = 0; i < itemRealCount; i++) { SoundFXList sfx; std::memcpy(&sfx, &item.Blob[i * sizeof(SoundFXList)], sizeof(SoundFXList)); so.WriteVariableUint32(sfx.Frame); auto it = sampleToIndex.find(sfx.Sample); if (it != sampleToIndex.end()) { so.WriteValueAsLE(it->second); } else { so.WriteValueAsLE(0); } so.WriteValue((std::uint8_t)std::min(sfx.Volume * 255 / 0x40, (std::uint32_t)UINT8_MAX)); so.WriteValue((std::int8_t)std::clamp(((std::int32_t)sfx.Panning - 0x20) * INT8_MAX / /*0x20*/0x40, -(std::int32_t)INT8_MAX, (std::int32_t)INT8_MAX)); } so.Seek(0, SeekOrigin::Begin); bool success = pakWriter.AddFile(so, targetPath, PakPreferredCompression::Deflate); DEATH_ASSERT(success, "Cannot add file to .pak container", ); } void JJ2Data::ConvertMenuImage(const Item& item, PakWriter& pakWriter, StringView targetPath, std::int32_t width, std::int32_t height) { std::int32_t pixelCount = width * height; DEATH_ASSERT(item.Size == pixelCount, "Image has unexpected size", ); std::unique_ptr pixels = std::make_unique(pixelCount * 4); for (std::int32_t i = 0; i < pixelCount; i++) { std::uint8_t colorIdx = item.Blob[i]; const Color& src = MenuPalette[colorIdx]; std::uint8_t a = (colorIdx == 0 ? 0 : src.A); pixels[(i * 4)] = src.R; pixels[(i * 4) + 1] = src.G; pixels[(i * 4) + 2] = src.B; pixels[(i * 4) + 3] = a; } MemoryStream so(16384); constexpr std::uint8_t flags = 0x80 | 0x01 | 0x02; so.WriteValueAsLE(0xB8EF8498E2BFBBEF); so.WriteValueAsLE(0x208F); so.WriteValue(0x02); // Version 2 is reserved for sprites (or bigger images) so.WriteValue(flags); so.WriteValue(4); so.WriteValueAsLE(width); so.WriteValueAsLE(height); // Include Sprite extension so.WriteValue(1); // FrameConfigurationX so.WriteValue(1); // FrameConfigurationY so.WriteValueAsLE(1); // FrameCount so.WriteValueAsLE(0); // FrameRate so.WriteValueAsLE(UINT16_MAX); // NormalizedHotspotX so.WriteValueAsLE(UINT16_MAX); // NormalizedHotspotY so.WriteValueAsLE(UINT16_MAX); // ColdspotX so.WriteValueAsLE(UINT16_MAX); // ColdspotY so.WriteValueAsLE(UINT16_MAX); // GunspotX so.WriteValueAsLE(UINT16_MAX); // GunspotY JJ2Anims::WriteImageContent(so, pixels.get(), width, height, 4); so.Seek(0, SeekOrigin::Begin); bool success = pakWriter.AddFile(so, targetPath, PakPreferredCompression::Deflate); DEATH_ASSERT(success, "Cannot add file to .pak container", ); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Data.h000066400000000000000000000017671512772601700257070ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "AnimSetMapping.h" #include "JJ2Version.h" #include #include #include #include using namespace Death::Containers; using namespace Death::IO; namespace Jazz2::Compatibility { /** @brief Parses original `.j2d` data files */ class JJ2Data { public: /** @brief Item from a `.j2d` data file */ struct Item { String Filename; std::unique_ptr Blob; std::uint32_t Type; std::int32_t Size; }; SmallVector Items; JJ2Data() {} bool Open(StringView path, bool strictParser); void Extract(StringView targetPath); void Convert(PakWriter& pakWriter, JJ2Version version); private: void ConvertSfxList(const Item& item, PakWriter& pakWriter, StringView targetPath, AnimSetMapping& animMapping); void ConvertMenuImage(const Item& item, PakWriter& pakWriter, StringView targetPath, std::int32_t width, std::int32_t height); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Episode.cpp000066400000000000000000000202161512772601700267470ustar00rootroot00000000000000#include "JJ2Episode.h" #include "JJ2Anims.h" #include "JJ2Anims.Palettes.h" #include "../ContentResolver.h" #include "../../nCine/Base/Algorithms.h" #include #include using namespace Death::IO; namespace Jazz2::Compatibility { JJ2Episode::JJ2Episode() : Position(0), ImageWidth(0), ImageHeight(0), TitleWidth(0), TitleHeight(0) { } JJ2Episode::JJ2Episode(StringView name, StringView displayName, StringView firstLevel, std::int32_t position) : Name(name), DisplayName(displayName), FirstLevel(firstLevel), Position(position), ImageWidth(0), ImageHeight(0), TitleWidth(0), TitleHeight(0) { } bool JJ2Episode::Open(StringView path) { auto s = fs::Open(path, FileAccess::Read); if (!s->IsValid()) { LOGE("Cannot open file \"{}\" for reading", path); return false; } Name = fs::GetFileNameWithoutExtension(path); StringUtils::lowercaseInPlace(Name); // TODO: Implement JJ2+ extended data, but I haven't seen it anywhere yet // the condition of unlocking (currently only defined for 0 meaning "always unlocked" // and 1 meaning "requires the previous episode to be finished", stored as a 4-byte-long // integer starting at byte 0x4), binary flags of various purpose (currently supported // flags are 1 and 2 used to reset respectively player ammo and lives when the episode // begins; stored as a 4-byte-long integer starting at byte 0x8) // Header (208 bytes) /*std::int32_t headerSize =*/ s->ReadValueAsLE(); Position = s->ReadValueAsLE(); /*std::uint32_t flags =*/ s->ReadValueAsLE(); // 0x01 = Not Shareware /*std::uint32_t unknown1 =*/ s->ReadValueAsLE(); char tmpBuffer[64]; // Episode name s->Read(tmpBuffer, 64); std::int32_t length = 0; while (tmpBuffer[length] != '\0' && length < 64) { length++; } DisplayName = String(tmpBuffer, length); // Previous Episode s->Read(tmpBuffer, 32); length = 0; while (tmpBuffer[length] != '\0' && length < 32) { length++; } PreviousEpisode = String(tmpBuffer, length); StringUtils::lowercaseInPlace(PreviousEpisode); // Next Episode s->Read(tmpBuffer, 32); length = 0; while (tmpBuffer[length] != '\0' && length < 32) { length++; } NextEpisode = String(tmpBuffer, length); StringUtils::lowercaseInPlace(NextEpisode); // First level s->Read(tmpBuffer, 32); length = 0; while (tmpBuffer[length] != '\0' && length < 32) { length++; } FirstLevel = String(tmpBuffer, length); StringUtils::lowercaseInPlace(FirstLevel); ImageWidth = s->ReadValueAsLE(); ImageHeight = s->ReadValueAsLE(); /*std::int32_t unknown2 =*/ s->ReadValueAsLE(); /*std::int32_t unknown3 =*/ s->ReadValueAsLE(); TitleWidth = s->ReadValueAsLE(); TitleHeight = s->ReadValueAsLE(); /*std::int32_t unknown4 =*/ s->ReadValueAsLE(); /*std::int32_t unknown5 =*/ s->ReadValueAsLE(); // Background image { std::int32_t imagePackedSize = s->ReadValueAsLE(); std::int32_t imageUnpackedSize = ImageWidth * ImageHeight; JJ2Block imageBlock(s, imagePackedSize, imageUnpackedSize); ImageData = std::make_unique(imageUnpackedSize); imageBlock.ReadRawBytes(ImageData.get(), imageUnpackedSize); } // Title image { std::int32_t titleLightPackedSize = s->ReadValueAsLE(); std::int32_t titleLightUnpackedSize = TitleWidth * TitleHeight; JJ2Block titleLightBlock(s, titleLightPackedSize, titleLightUnpackedSize); TitleData = std::make_unique(titleLightUnpackedSize); titleLightBlock.ReadRawBytes(TitleData.get(), titleLightUnpackedSize); } //{ // std::int32_t titleDarkPackedSize = Stream::FromLE(->ReadValue()); // std::int32_t titleDarkUnpackedSize = titleWidth * titleHeight; // JJ2Block titleDarkBlock(s, titleDarkPackedSize, titleDarkUnpackedSize); // episode.titleDark = ConvertIndicesToRgbaBitmap(titleWidth, titleHeight, titleDarkBlock, true); //} return true; } void JJ2Episode::Convert(StringView targetPath, Function&& levelTokenConversion, Function&& episodeNameConversion, Function(JJ2Episode*)>&& episodePrevNext) { auto so = fs::Open(targetPath, FileAccess::Write); DEATH_ASSERT(so->IsValid(), "Cannot open file for writing", ); so->WriteValueAsLE(0x2095A59FF0BFBBEF); so->WriteValue(ContentResolver::EpisodeFile); std::uint16_t flags = 0x00; so->WriteValueAsLE(flags); String displayName = (episodeNameConversion ? episodeNameConversion(this) : DisplayName); so->WriteValue((std::uint8_t)displayName.size()); so->Write(displayName.data(), displayName.size()); so->WriteValueAsLE(Position); MutableStringView firstLevel = FirstLevel; if (JJ2Level::StringHasSuffixIgnoreCase(firstLevel, ".j2l"_s) || JJ2Level::StringHasSuffixIgnoreCase(firstLevel, ".lev"_s)) { firstLevel = firstLevel.exceptSuffix(4); } if (levelTokenConversion) { auto token = levelTokenConversion(firstLevel); so->WriteValue(std::uint8_t(token.Level.size())); so->Write(token.Level.data(), token.Level.size()); } else { so->WriteValue(std::uint8_t(firstLevel.size())); so->Write(firstLevel.data(), firstLevel.size()); } if (!PreviousEpisode.empty() || !NextEpisode.empty()) { MutableStringView previousEpisode = PreviousEpisode; if (JJ2Level::StringHasSuffixIgnoreCase(previousEpisode, ".j2e"_s)) { previousEpisode = previousEpisode.exceptSuffix(4); } else if (JJ2Level::StringHasSuffixIgnoreCase(previousEpisode, ".j2pe"_s)) { previousEpisode = previousEpisode.exceptSuffix(5); } MutableStringView nextEpisode = NextEpisode; if (JJ2Level::StringHasSuffixIgnoreCase(nextEpisode, ".j2e"_s)) { nextEpisode = nextEpisode.exceptSuffix(4); } else if(JJ2Level::StringHasSuffixIgnoreCase(nextEpisode, ".j2pe"_s)) { nextEpisode = nextEpisode.exceptSuffix(5); } so->WriteValue((std::uint8_t)previousEpisode.size()); so->Write(previousEpisode.data(), previousEpisode.size()); so->WriteValue((std::uint8_t)nextEpisode.size()); so->Write(nextEpisode.data(), nextEpisode.size()); } else if (episodePrevNext) { auto prevNext = episodePrevNext(this); so->WriteValue((std::uint8_t)prevNext.first().size()); so->Write(prevNext.first().data(), prevNext.first().size()); so->WriteValue((std::uint8_t)prevNext.second().size()); so->Write(prevNext.second().data(), prevNext.second().size()); } else { so->WriteValue(0); so->WriteValue(0); } // Write episode title image so->WriteValueAsLE(TitleWidth); so->WriteValueAsLE(TitleHeight); std::uint32_t titlePixelsCount = TitleWidth * TitleHeight; std::unique_ptr titlePixels = std::make_unique(titlePixelsCount * 4); for (std::uint32_t i = 0; i < titlePixelsCount; i++) { std::uint8_t colorIdx = TitleData[i]; // Remove shadow if (colorIdx == 63 || colorIdx == 143) { colorIdx = 0; } const Color& src = MenuPalette[colorIdx]; titlePixels[i * 4] = src.R; titlePixels[i * 4 + 1] = src.G; titlePixels[i * 4 + 2] = src.B; titlePixels[i * 4 + 3] = src.A; } JJ2Anims::WriteImageContent(*so, titlePixels.get(), TitleWidth, TitleHeight, 4); // Write episode background image so->WriteValueAsLE(ImageWidth); so->WriteValueAsLE(ImageHeight); std::uint32_t imagePixelsCount = ImageWidth * ImageHeight; std::unique_ptr imagePixels = std::make_unique(imagePixelsCount * 4); for (std::uint32_t i = 0; i < imagePixelsCount; i++) { std::uint8_t colorIdx = ImageData[i]; const Color& src = MenuPalette[colorIdx]; imagePixels[i * 4] = src.R; imagePixels[i * 4 + 1] = src.G; imagePixels[i * 4 + 2] = src.B; imagePixels[i * 4 + 3] = src.A; } JJ2Anims::WriteImageContent(*so, imagePixels.get(), ImageWidth, ImageHeight, 4); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Episode.h000066400000000000000000000020371512772601700264150ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Level.h" #include #include #include #include using namespace Death::Containers; namespace Jazz2::Compatibility { /** @brief Parses original `.j2e`/`.j2pe` episode files */ class JJ2Episode { public: std::int32_t Position; String Name; String DisplayName; String FirstLevel; String PreviousEpisode; String NextEpisode; std::int32_t ImageWidth; std::int32_t ImageHeight; std::unique_ptr ImageData; std::int32_t TitleWidth; std::int32_t TitleHeight; std::unique_ptr TitleData; JJ2Episode(); JJ2Episode(StringView name, StringView displayName, StringView firstLevel, std::int32_t position); bool Open(StringView path); void Convert(StringView targetPath, Function&& levelTokenConversion = {}, Function&& episodeNameConversion = {}, Function(JJ2Episode*)>&& episodePrevNext = {}); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Event.h000066400000000000000000000140401512772601700261030ustar00rootroot00000000000000#pragma once #include "../../Main.h" namespace Jazz2::Compatibility { /** @brief Original event type */ enum class JJ2Event : uint8_t { EMPTY = 0x00, // Base events MODIFIER_ONE_WAY = 0x01, MODIFIER_HURT = 0x02, MODIFIER_VINE = 0x03, MODIFIER_HOOK = 0x04, MODIFIER_SLIDE = 0x05, MODIFIER_H_POLE = 0x06, MODIFIER_V_POLE = 0x07, AREA_FLY_OFF = 0x08, MODIFIER_RICOCHET = 0x09, MODIFIER_BELT_RIGHT = 0x0A, MODIFIER_BELT_LEFT = 0x0B, MODIFIER_ACC_BELT_RIGHT = 0x0C, MODIFIER_ACC_BELT_LEFT = 0x0D, AREA_STOP_ENEMY = 0x0E, MODIFIER_WIND_LEFT = 0x0F, MODIFIER_WIND_RIGHT = 0x10, AREA_EOL = 0x11, AREA_EOL_WARP = 0x12, AREA_REVERT_MORPH = 0x13, AREA_FLOAT_UP = 0x14, TRIGGER_ROCK = 0x15, LIGHT_DIM = 0x16, LIGHT_SET = 0x17, AREA_LIMIT_X_SCROLL = 0x18, LIGHT_RESET = 0x19, AREA_SECRET_WARP = 0x1A, MODIFIER_ECHO = 0x1B, AREA_ACTIVATE_BOSS = 0x1C, JAZZ_LEVEL_START = 0x1D, SPAZ_LEVEL_START = 0x1E, MP_LEVEL_START = 0x1F, AMMO_FREEZER = 0x21, AMMO_BOUNCER = 0x22, AMMO_SEEKER = 0x23, AMMO_RF = 0x24, AMMO_TOASTER = 0x25, AMMO_TNT = 0x26, AMMO_PEPPER = 0x27, AMMO_ELECTRO = 0x28, TURTLE_SHELL = 0x29, SWINGING_VINE = 0x2A, SCENERY_BOMB = 0x2B, COIN_SILVER = 0x2C, COIN_GOLD = 0x2D, CRATE_AMMO = 0x2E, CRATE_CARROT = 0x2F, CRATE_ONEUP = 0x30, BARREL_GEM = 0x31, BARREL_CARROT = 0x32, BARREL_ONEUP = 0x33, CRATE_BOMB = 0x34, CRATE_AMMO_FREEZER = 0x35, CRATE_AMMO_BOUNCER = 0x36, CRATE_AMMO_SEEKER = 0x37, CRATE_AMMO_RF = 0x38, CRATE_AMMO_TOASTER = 0x39, SCENERY_TNT = 0x3A, AIRBOARD = 0x3B, SPRING_GREEN_FROZEN = 0x3C, FAST_FIRE = 0x3D, CRATE_SPRING = 0x3E, GEM_RED = 0x3F, GEM_GREEN = 0x40, GEM_BLUE = 0x41, GEM_PURPLE = 0x42, GEM_SUPER = 0x43, BIRDY = 0x44, BARREL_AMMO = 0x45, CRATE_GEM = 0x46, POWERUP_SWAP = 0x47, CARROT = 0x48, CARROT_FULL = 0x49, SHIELD_FIRE = 0x4A, SHIELD_WATER = 0x4B, SHIELD_LIGHTNING = 0x4C, MAX_WEAPON = 0x4D, AREA_AUTO_FIRE = 0x4E, FAST_FEET = 0x4F, ONEUP = 0x50, EOL_SIGN = 0x51, SAVE_POINT = 0x53, BONUS_SIGN = 0x54, SPRING_RED = 0x55, SPRING_GREEN = 0x56, SPRING_BLUE = 0x57, CARROT_INVINCIBLE = 0x58, SHIELD_TIME = 0x59, FREEZE = 0x5A, SPRING_RED_HOR = 0x5B, SPRING_GREEN_HOR = 0x5C, SPRING_BLUE_HOR = 0x5D, POWERUP_BIRD = 0x5E, TRIGGER_CRATE = 0x5F, CARROT_FLY = 0x60, GEM_RED_RECT = 0x61, GEM_GREEN_RECT = 0x62, GEM_BLUE_RECT = 0x63, ENEMY_TUF_TURT = 0x64, BOSS_TUF_TURT = 0x65, ENEMY_LAB_RAT = 0x66, ENEMY_DRAGON = 0x67, ENEMY_LIZARD = 0x68, ENEMY_BEE = 0x69, ENEMY_RAPIER = 0x6A, ENEMY_SPARKS = 0x6B, ENEMY_BAT = 0x6C, ENEMY_SUCKER = 0x6D, ENEMY_CATERPILLAR = 0x6E, CHESHIRE_HOOK = 0x6F, CHESHIRE_2 = 0x70, ENEMY_MADDER_HATTER = 0x71, BOSS_BILSY = 0x72, ENEMY_SKELETON = 0x73, ENEMY_DOGGY_DOGG = 0x74, ENEMY_TURTLE_NORMAL = 0x75, ENEMY_HELMUT = 0x76, LEAF = 0x77, ENEMY_DEMON = 0x78, FIRE = 0x79, LAVA = 0x7A, ENEMY_DRAGONFLY = 0x7B, ENEMY_MONKEY = 0x7C, ENEMY_FAT_CHICK = 0x7D, ENEMY_FENCER = 0x7E, ENEMY_FISH = 0x7F, MOTH = 0x80, STEAM = 0x81, ROTATING_ROCK = 0x82, POWERUP_BLASTER = 0x83, POWERUP_BOUNCER = 0x84, POWERUP_FREEZER = 0x85, POWERUP_SEEKER = 0x86, POWERUP_RF = 0x87, POWERUP_TOASTER = 0x88, PINBALL_PADDLE_L = 0x89, PINBALL_PADDLE_R = 0x8A, PINBALL_BUMP_500 = 0x8B, PINBALL_BUMP_CARROT = 0x8C, FOOD_APPLE = 0x8D, FOOD_BANANA = 0x8E, FOOD_CHERRY = 0x8F, FOOD_ORANGE = 0x90, FOOD_PEAR = 0x91, FOOD_PRETZEL = 0x92, FOOD_STRAWBERRY = 0x93, LIGHT_STEADY = 0x94, LIGHT_PULSE = 0x95, LIGHT_FLICKER = 0x96, BOSS_QUEEN = 0x97, ENEMY_SUCKER_FLOAT = 0x98, BRIDGE = 0x99, FOOD_LEMON = 0x9A, FOOD_LIME = 0x9B, FOOD_THING = 0x9C, FOOD_WATERMELON = 0x9D, FOOD_PEACH = 0x9E, FOOD_GRAPES = 0x9F, FOOD_LETTUCE = 0xA0, FOOD_EGGPLANT = 0xA1, FOOD_CUCUMBER = 0xA2, FOOD_PEPSI = 0xA3, FOOD_COKE = 0xA4, FOOD_MILK = 0xA5, FOOD_PIE = 0xA6, FOOD_CAKE = 0xA7, FOOD_DONUT = 0xA8, FOOD_CUPCAKE = 0xA9, FOOD_CHIPS = 0xAA, FOOD_CANDY = 0xAB, FOOD_CHOCOLATE = 0xAC, FOOD_ICE_CREAM = 0xAD, FOOD_BURGER = 0xAE, FOOD_PIZZA = 0xAF, FOOD_FRIES = 0xB0, FOOD_CHICKEN_LEG = 0xB1, FOOD_SANDWICH = 0xB2, FOOD_TACO = 0xB3, FOOD_HOT_DOG = 0xB4, FOOD_HAM = 0xB5, FOOD_CHEESE = 0xB6, ENEMY_LIZARD_FLOAT = 0xB7, ENEMY_MONKEY_STAND = 0xB8, SCENERY_DESTRUCT = 0xB9, SCENERY_DESTR_BOMB = 0xBA, SCENERY_COLLAPSE = 0xBB, SCENERY_BUTTSTOMP = 0xBC, SCENERY_GEMSTOMP = 0xBD, ENEMY_RAVEN = 0xBE, ENEMY_TURTLE_TUBE = 0xBF, GEM_RING = 0xC0, SMALL_TREE = 0xC1, AMBIENT_SOUND = 0xC2, BOSS_UTERUS = 0xC3, ENEMY_CRAB = 0xC4, ENEMY_WITCH = 0xC5, BOSS_TURTLE_ROCKET = 0xC6, BOSS_BUBBA = 0xC7, BOSS_DEVAN_DEVIL = 0xC8, BOSS_DEVAN_ROBOT = 0xC9, BOSS_ROBOT = 0xCA, POLE_CARROTUS = 0xCB, POLE_PSYCH = 0xCC, POLE_DIAMONDUS = 0xCD, MODIFIER_TUBE = 0xCE, AREA_TEXT = 0xCF, MODIFIER_SET_WATER = 0xD0, PLATFORM_FRUIT = 0xD1, PLATFORM_BOLL = 0xD2, PLATFORM_GRASS = 0xD3, PLATFORM_PINK = 0xD4, PLATFORM_SONIC = 0xD5, PLATFORM_SPIKE = 0xD6, BOLL_SPIKE = 0xD7, MODIFIER_GENERATOR = 0xD8, EVA = 0xD9, SCENERY_BUBBLER = 0xDA, POWERUP_TNT = 0xDB, POWERUP_PEPPER = 0xDC, POWERUP_ELECTRO = 0xDD, AREA_MORPH_FROG = 0xDE, BOLL_SPIKE_3D = 0xDF, SPRINGCORD = 0xE0, ENEMY_BEE_SWARM = 0xE1, COPTER = 0xE2, SHIELD_LASER = 0xE3, STOPWATCH = 0xE4, POLE_JUNGLE = 0xE5, WARP_ORIGIN = 0xE6, PUSHABLE_ROCK = 0xE7, PUSHABLE_BOX = 0xE8, WATER_BLOCK = 0xE9, TRIGGER_AREA = 0xEA, BOSS_BOLLY = 0xEB, ENEMY_BUTTERFLY = 0xEC, ENEMY_BEEBOY = 0xED, SNOW = 0xEE, WARP_TARGET = 0xF0, BOSS_TWEEDLE = 0xF1, AREA_ID = 0xF2, CTF_BASE = 0xF4, AREA_NO_FIRE = 0xF5, TRIGGER_ZONE = 0xF6, // TSF+ events LORI_LEVEL_START = 0x20, BILSY_DUMMY = 0xF7, ENEMY_NORMAL_TURTLE_XMAS = 0xF8, ENEMY_LIZARD_XMAS = 0xF9, ENEMY_LIZARD_FLOAT_XMAS = 0xFA, EMPTY_BOSS_BILSY_XMAS = 0xFB, EMPTY_TSF_DOG = 0xFC, EMPTY_TSF_GHOST = 0xFD, // Unassigned events EMPTY_82 = 0x52, EMPTY_239 = 0xEF, EMPTY_243 = 0xF3, EMPTY_254 = 0xFE, MCE = 0xFF }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Level.cpp000066400000000000000000001354561512772601700264430ustar00rootroot00000000000000#include "JJ2Level.h" #include "JJ2Strings.h" #include "EventConverter.h" #include "../ContentResolver.h" #include "../LevelFlags.h" #include "../Tiles/TileMap.h" #include "../../nCine/Base/Algorithms.h" #include #include #include #include #include using namespace Death::IO; using namespace Death::IO::Compression; namespace Jazz2::Compatibility { bool JJ2Level::Open(StringView path, bool strictParser) { auto s = fs::Open(path, FileAccess::Read); if (!s->IsValid()) { LOGE("Cannot open file \"{}\" for reading", path); return false; } // Skip copyright notice s->Seek(180, SeekOrigin::Current); LevelName = fs::GetFileNameWithoutExtension(path); StringUtils::lowercaseInPlace(LevelName); JJ2Block headerBlock(s, 262 - 180); std::uint32_t magic = headerBlock.ReadUInt32(); DEATH_ASSERT(magic == 0x4C56454C /*LEVL*/, "Invalid magic string", false); // passwordHash is 3 bytes headerBlock.DiscardBytes(3); _isHidden = headerBlock.ReadBool(); DisplayName = headerBlock.ReadString(32, true); std::uint16_t version = headerBlock.ReadUInt16(); _version = (version <= 0x202 ? JJ2Version::BaseGame : JJ2Version::TSF); _darknessColor = 0; _weatherType = WeatherType::None; _weatherIntensity = 0; _waterLevel = 32767; std::int32_t recordedSize = headerBlock.ReadInt32(); DEATH_ASSERT(!strictParser || s->GetSize() == recordedSize, "Unexpected file size", false); // Get the CRC; would check here if it matches if we knew what variant it is AND what it applies to // Test file across all CRC32 variants + Adler had no matches to the value obtained from the file // so either the variant is something else or the CRC is not applied to the whole file but on a part /*int recordedCRC =*/ headerBlock.ReadInt32(); // Read the lengths, uncompress the blocks and bail if any block could not be uncompressed // This could look better without all the copy-paste, but meh. std::int32_t infoBlockPackedSize = headerBlock.ReadInt32(); std::int32_t infoBlockUnpackedSize = headerBlock.ReadInt32(); std::int32_t eventBlockPackedSize = headerBlock.ReadInt32(); std::int32_t eventBlockUnpackedSize = headerBlock.ReadInt32(); std::int32_t dictBlockPackedSize = headerBlock.ReadInt32(); std::int32_t dictBlockUnpackedSize = headerBlock.ReadInt32(); std::int32_t layoutBlockPackedSize = headerBlock.ReadInt32(); std::int32_t layoutBlockUnpackedSize = headerBlock.ReadInt32(); JJ2Block infoBlock(s, infoBlockPackedSize, infoBlockUnpackedSize); JJ2Block eventBlock(s, eventBlockPackedSize, eventBlockUnpackedSize); JJ2Block dictBlock(s, dictBlockPackedSize, dictBlockUnpackedSize); JJ2Block layoutBlock(s, layoutBlockPackedSize, layoutBlockUnpackedSize); LoadMetadata(infoBlock, strictParser); LoadEvents(eventBlock, strictParser); LoadLayers(dictBlock, dictBlockUnpackedSize / 8, layoutBlock, strictParser); // Try to read MLLE data stream std::uint32_t mlleMagic = s->ReadValueAsLE(); if (mlleMagic == 0x454C4C4D /*MLLE*/) { std::uint32_t mlleVersion = s->ReadValueAsLE(); std::int32_t mlleBlockPackedSize = s->ReadValueAsLE(); std::int32_t mlleBlockUnpackedSize = s->ReadValueAsLE(); JJ2Block mlleBlock(s, mlleBlockPackedSize, mlleBlockUnpackedSize); LoadMlleData(mlleBlock, mlleVersion, path, strictParser); } return true; } void JJ2Level::LoadMetadata(JJ2Block& block, bool strictParser) { // First 9 bytes are JCS coordinates on last save. block.DiscardBytes(9); LightingMin = block.ReadByte(); LightingStart = block.ReadByte(); _animCount = block.ReadUInt16(); _verticalMPSplitscreen = block.ReadBool(); _isMpLevel = block.ReadBool(); // This should be the same as size of block in the start? /*std::int32_t headerSize =*/ block.ReadInt32(); String secondLevelName = block.ReadString(32, true); //DEATH_ASSERT(!strictParser || DisplayName == secondLevelName, "Level name mismatch", ); Tileset = block.ReadString(32, true); BonusLevel = block.ReadString(32, true); NextLevel = block.ReadString(32, true); SecretLevel = block.ReadString(32, true); Music = block.ReadString(32, true); for (std::int32_t i = 0; i < TextEventStringsCount; ++i) { _textEventStrings[i] = block.ReadString(512, true); } LoadLayerMetadata(block, strictParser); /*uint16_t staticTilesCount =*/ block.ReadUInt16(); //DEATH_ASSERT(!strictParser || GetMaxSupportedTiles() - _animCount == staticTilesCount, "Tile count mismatch", ); LoadStaticTileData(block, strictParser); // The unused XMask field block.DiscardBytes(GetMaxSupportedTiles()); LoadAnimatedTiles(block, strictParser); } void JJ2Level::LoadStaticTileData(JJ2Block& block, bool strictParser) { std::int32_t tileCount = GetMaxSupportedTiles(); _staticTiles = std::make_unique(tileCount); for (std::int32_t i = 0; i < tileCount; ++i) { std::uint32_t tileEvent = block.ReadUInt32(); auto& tile = _staticTiles[i]; tile.Event.EventType = (JJ2Event)(std::uint8_t)(tileEvent & 0x000000FF); tile.Event.Difficulty = (std::uint8_t)((tileEvent & 0x0000C000) >> 14); tile.Event.Illuminate = ((tileEvent & 0x00002000) >> 13 == 1); tile.Event.TileParams = (std::uint32_t)(((tileEvent >> 12) & 0x000FFFF0) | ((tileEvent >> 8) & 0x0000000F)); } for (std::int32_t i = 0; i < tileCount; ++i) { _staticTiles[i].Flipped = block.ReadBool(); } for (std::int32_t i = 0; i < tileCount; ++i) { _staticTiles[i].Type = block.ReadByte(); } } void JJ2Level::LoadAnimatedTiles(JJ2Block& block, bool strictParser) { _animatedTiles = std::make_unique(_animCount); for (std::int32_t i = 0; i < _animCount; i++) { auto& tile = _animatedTiles[i]; tile.Delay = block.ReadUInt16(); tile.DelayJitter = block.ReadUInt16(); tile.ReverseDelay = block.ReadUInt16(); tile.IsPingPong = block.ReadBool(); tile.Speed = block.ReadByte(); // 0-70 tile.FrameCount = block.ReadByte(); for (std::int32_t j = 0; j < 64; j++) { tile.Frames[j] = block.ReadUInt16(); } } } void JJ2Level::LoadLayerMetadata(JJ2Block& block, bool strictParser) { _layers.resize(JJ2LayerCount); for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].Flags = block.ReadUInt32(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].Type = block.ReadByte(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].Used = block.ReadBool(); _layers[i].Visible = true; } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].Width = block.ReadInt32(); } // This is related to how data is presented in the file, the above is a WYSIWYG version, solely shown on the UI for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].InternalWidth = block.ReadInt32(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].Height = block.ReadInt32(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].Depth = block.ReadInt32(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].DetailLevel = block.ReadByte(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].OffsetX = block.ReadFloatEncoded(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].OffsetY = block.ReadFloatEncoded(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].SpeedX = block.ReadFloatEncoded(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].SpeedY = block.ReadFloatEncoded(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].AutoSpeedX = block.ReadFloatEncoded(); _layers[i].SpeedModelX = LayerSectionSpeedModel::Normal; } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].AutoSpeedY = block.ReadFloatEncoded(); _layers[i].SpeedModelY = LayerSectionSpeedModel::Normal; } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].TexturedBackgroundType = block.ReadByte(); } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { _layers[i].TexturedParams1 = block.ReadByte(); _layers[i].TexturedParams2 = block.ReadByte(); _layers[i].TexturedParams3 = block.ReadByte(); _layers[i].SpriteMode = 0; _layers[i].SpriteParam = 0; } } void JJ2Level::LoadEvents(JJ2Block& block, bool strictParser) { std::int32_t width = _layers[3].Width; std::int32_t height = _layers[3].Height; if (width <= 0 && height <= 0) { return; } _events = std::make_unique(width * height); for (std::int32_t y = 0; y < _layers[3].Height; y++) { for (std::int32_t x = 0; x < width; x++) { std::uint32_t eventData = block.ReadUInt32(); auto& tileEvent = _events[x + y * width]; tileEvent.EventType = (JJ2Event)(std::uint8_t)(eventData & 0x000000FF); tileEvent.Difficulty = (std::uint8_t)((eventData & 0x00000300) >> 8); tileEvent.Illuminate = ((eventData & 0x00000400) >> 10 == 1); tileEvent.TileParams = ((eventData & 0xFFFFF000) >> 12); } } auto& lastTileEvent = _events[(width * height) - 1]; if (lastTileEvent.EventType == JJ2Event::MODIFIER_ONE_WAY) { _hasPit = false; _hasPitInstantDeath = false; } else if (lastTileEvent.EventType == JJ2Event::MCE) { _hasPit = true; _hasPitInstantDeath = true; } else { _hasPit = true; _hasPitInstantDeath = false; } for (std::int32_t i = 0; i < width * height; i++) { if (_events[i].EventType == JJ2Event::CTF_BASE) { _hasCTF = true; } else if (_events[i].EventType == JJ2Event::WARP_ORIGIN) { if (((_events[i].TileParams >> 16) & 1) == 1) { _hasLaps = true; } } } } void JJ2Level::LoadLayers(JJ2Block& dictBlock, std::int32_t dictLength, JJ2Block& layoutBlock, bool strictParser) { struct DictionaryEntry { std::uint16_t Tiles[4]; }; std::unique_ptr dictionary = std::make_unique(dictLength); for (std::int32_t i = 0; i < dictLength; i++) { auto& entry = dictionary[i]; for (std::int32_t j = 0; j < 4; j++) { entry.Tiles[j] = dictBlock.ReadUInt16(); } } for (std::int32_t i = 0; i < JJ2LayerCount; i++) { auto& layer = _layers[i]; if (layer.Used) { layer.Tiles = std::make_unique(layer.InternalWidth * layer.Height); for (std::int32_t y = 0; y < layer.Height; y++) { for (std::int32_t x = 0; x < layer.InternalWidth; x += 4) { std::uint16_t dictIdx = layoutBlock.ReadUInt16(); for (std::int32_t j = 0; j < 4; j++) { if (j + x >= layer.Width) { break; } layer.Tiles[j + x + y * layer.InternalWidth] = dictionary[dictIdx].Tiles[j]; } } } } else { // Array will be initialized with zeros layer.Tiles = std::make_unique(layer.Width * layer.Height); } } } void JJ2Level::LoadMlleData(JJ2Block& block, std::uint32_t version, StringView path, bool strictParser) { if (version > 0x107) { LOGW("MLLE stream version 0x{:x} in level \"{}\" is not supported", version, LevelName); return; } bool isSnowing = block.ReadBool(); bool isSnowingOutdoorsOnly = block.ReadBool(); std::uint8_t snowIntensity = block.ReadByte(); // Weather particles type (Snow, Flower, Rain, Leaf) std::uint8_t snowType = block.ReadByte(); if (isSnowing) { _weatherIntensity = snowIntensity; _weatherType = (WeatherType)(snowType + 1); if (_weatherType != WeatherType::None && isSnowingOutdoorsOnly) { _weatherType |= WeatherType::OutdoorsOnly; } } // TODO: Convert remaining coins to gems when warp is used bool warpsTransmuteCoins = block.ReadBool(); // TODO: Objects spawned from Generators will derive their parameters from tile they first appear at (e.g., after gravitation) bool delayGeneratedCrateOrigins = block.ReadBool(); std::int32_t echo = block.ReadInt32(); std::uint32_t darknessColorBgra = block.ReadUInt32(); _darknessColor = ((darknessColorBgra >> 16) & 0xff) | (darknessColorBgra & 0x0000ff00) | ((darknessColorBgra << 16) & 0x00ff0000); // TODO: Water level change speed float waterChangeSpeed = block.ReadFloat(); // TODO: How player should react to being underwater (PositionBased, Swim, LowGravity) std::uint8_t waterInteraction = block.ReadByte(); std::int32_t waterLayer = block.ReadInt32(); // TODO: How water and ambient lighting should interact in the level (None, Global, Lagunicus) std::uint8_t waterLighting = block.ReadByte(); _waterLevel = (std::uint16_t)block.ReadFloat(); std::uint32_t waterGradientStart = block.ReadUInt32(); std::uint32_t waterGradientStop = block.ReadUInt32(); // Level palette _useLevelPalette = block.ReadBool(); if (_useLevelPalette) { block.ReadRawBytes(_levelPalette, sizeof(_levelPalette)); if (version >= 0x106) { // TODO: Reapply palette on death bool reapplyPaletteOnDeath = block.ReadBool(); } } // Additional palettes for SPRITE::MAPPING and 24-bit tile sets if (version >= 0x106) { std::uint8_t extraPaletteCount = block.ReadByte(); while (extraPaletteCount-- != 0) { auto& palette = AlternatePalettes.emplace_back(); std::int32_t nameLength = block.ReadUint7bitEncoded(); palette.Name = String(NoInit, nameLength); block.ReadRawBytes((std::uint8_t*)palette.Name.data(), nameLength); block.ReadRawBytes(palette.Colors, sizeof(palette.Colors)); } } // TODO: Recolorable sprites std::int32_t recolorableSpriteListSize = (version >= 0x105 ? 20 : 11); for (std::int32_t i = 0; i < recolorableSpriteListSize; ++i) { // NOTE: Recolorable sprite list was expanded in MLLE-Include-1.5 if (block.ReadBool()) { block.DiscardBytes(256); } } // Extra tilesets std::int32_t extraTilesetCount = block.ReadByte(); for (std::int32_t i = 0; i < extraTilesetCount; i++) { auto& tileset = ExtraTilesets.emplace_back(); std::int32_t tilesetNameLength = block.ReadUint7bitEncoded(); tileset.Name = block.ReadString(tilesetNameLength, false); tileset.Offset = block.ReadUInt16(); tileset.Count = block.ReadUInt16(); tileset.ColorMode = (TilesetColorMode)block.ReadByte(); switch (tileset.ColorMode) { case TilesetColorMode::Original8bit: case TilesetColorMode::Original24bit: // No additional parameters break; case TilesetColorMode::Remapped8bit: block.ReadRawBytes(tileset.PaletteRemapping, sizeof(tileset.PaletteRemapping)); break; case TilesetColorMode::AlternatePalette24bit: tileset.AlternatePaletteMappingID24Bit = block.ReadByte(); break; } } // Additional layers if (version >= 0x102) { std::int32_t layerCount = block.ReadInt32(); for (std::int32_t i = 8; i < layerCount; i += 8) { char numberBuffer[16]; i32tos(i / 8, numberBuffer); StringView foundDot = path.findLastOr('.', path.end()); String extraLayersPath = fs::FindPathCaseInsensitive(path.prefix(foundDot.begin()) + "-MLLE-Data-"_s + numberBuffer + ".j2l"_s); JJ2Level extraLayersFile; if (extraLayersFile.Open(extraLayersPath, strictParser)) { for (std::int32_t j = 0; j < 8 && (i + j) < layerCount; j++) { _layers.push_back(std::move(extraLayersFile._layers[j])); } } else { for (std::int32_t j = 0; j < 8 && (i + j) < layerCount; j++) { _layers.emplace_back(); } } } SmallVector layerOrder(layerCount); std::int32_t nextExtraLayerIdx = JJ2LayerCount; for (std::int32_t i = 0; i < layerCount; i++) { std::int8_t id = (std::int8_t)block.ReadByte(); std::int32_t idx; if (id >= 0) { idx = id; } else { idx = nextExtraLayerIdx++; } auto& layer = _layers[idx]; layerOrder[idx] = i; std::int32_t layerNameLength = block.ReadUint7bitEncoded(); //String layerName = block.ReadString(layerNameLength, false); block.DiscardBytes(layerNameLength); layer.Visible = !block.ReadBool(); layer.SpriteMode = block.ReadByte(); layer.SpriteParam = block.ReadByte(); std::int32_t rotationAngle = block.ReadInt32(); std::int32_t rotationRadiusMult = block.ReadInt32(); if (version >= 0x106) { layer.SpeedModelX = (LayerSectionSpeedModel)block.ReadByte(); layer.SpeedModelY = (LayerSectionSpeedModel)block.ReadByte(); // TODO: Texture surface effect (Untextured, Legacy, Fullscreen, InnerWindow, InnerLayer) //layer.TextureSurface = block.ReadByte(); //layer.Fade = block.ReadByte(); //layer.FadeX = block.ReadFloat(); //layer.FadeY = block.ReadFloat(); //layer.InnerSpeedX = block.ReadFloat(); //layer.InnerSpeedY = block.ReadFloat(); //layer.InnerAutoSpeedX = block.ReadFloat(); //layer.InnerAutoSpeedY = block.ReadFloat(); block.DiscardBytes(26); std::int8_t texture = (std::int8_t)block.ReadByte(); if (texture < 0) { //layer.TextureImage = block.ReadBytes(256 * 256); block.DiscardBytes(256 * 256); } } } // Sprite layer has zero depth std::int32_t zeroDepthIdx = layerOrder[3]; // Adjust depth of all layers for (std::size_t i = 0; i < _layers.size(); i++) { std::int32_t newIdx = layerOrder[i]; auto& layer = _layers[i]; layer.Depth = (newIdx - zeroDepthIdx) * 100; if (layer.Depth < -200) { layer.Depth = -200 - (200 - layer.Depth) / 20; } else if (layer.Depth > 300) { layer.Depth = 300 + (layer.Depth - 300) / 20; } } // Edited tiles were added in MLLE-Include-1.3 if (version >= 0x103) { std::int16_t editedTilesCount = block.ReadInt16(); for (std::int32_t i = 0; i < editedTilesCount; i++) { auto& tile = _overridenTileDiffuses.emplace_back(); tile.TileID = block.ReadInt16(); block.ReadRawBytes(tile.Diffuse, 32 * 32); } editedTilesCount = block.ReadInt16(); for (std::int32_t i = 0; i < editedTilesCount; i++) { auto& tile = _overridenTileMasks.emplace_back(); tile.TileID = block.ReadInt16(); block.ReadRawBytes(tile.Mask, 32 * 32); } } // TODO: Weapons were added in MLLE-Include-1.5(w) if (version >= 0x105) { // TODO: DefaultWeaponHook constructor argument was added in MLLE-Include-1.6(w) if (version >= 0x106) { std::int16_t weaponHookPrefix = block.ReadInt16(); } for (std::int32_t weaponId = 0; weaponId < 9; weaponId++) { bool customWeapon = block.ReadBool(); std::int32_t maximum = block.ReadInt32(); // comesFromBirds, comesFromBirdsPowerup, comesFromGunCrates, gemsLost, gemsLostPowerup, infinite, replenishes constexpr std::int32_t NumberOfCommonOptions = 5; for (std::int32_t optionId = 0; optionId < NumberOfCommonOptions; optionId++) { std::uint8_t optionValue = block.ReadByte(); } if (weaponId >= 6) { std::uint8_t ammoCrate = block.ReadByte(); } if (customWeapon) { std::int32_t weaponNameLength = block.ReadUint7bitEncoded(); String weaponName{NoInit, (std::size_t)weaponNameLength}; block.ReadRawBytes((std::uint8_t*)weaponName.data(), weaponNameLength); std::int32_t weaponParamsLength = block.ReadInt32(); block.DiscardBytes(weaponParamsLength); } else if (weaponId == 7) { std::uint8_t gun8Style = block.ReadByte(); // Pepper style } } } // Off-grid objects were added in MLLE-Include-1.6 if (version >= 0x106) { std::int16_t objectCount = block.ReadInt16(); for (std::int32_t i = 0; i < objectCount; i++) { std::int16_t x = block.ReadInt16(); std::int16_t y = block.ReadInt16(); std::uint32_t eventData = block.ReadUInt32(); auto& offGridEvent = _offGridEvents.emplace_back(); offGridEvent.X = x; offGridEvent.Y = y; offGridEvent.EventType = (JJ2Event)(std::uint8_t)(eventData & 0x000000FF); offGridEvent.Difficulty = (std::uint8_t)((eventData & 0x00000300) >> 8); offGridEvent.Illuminate = ((eventData & 0x00000400) >> 10 == 1); offGridEvent.TileParams = ((eventData & 0xFFFFF000) >> 12); } } } } void JJ2Level::Convert(StringView targetPath, EventConverter& eventConverter, Function&& levelTokenConversion) { auto so = fs::Open(targetPath, FileAccess::Write); DEATH_ASSERT(so->IsValid(), "Cannot open file for writing", ); so->WriteValueAsLE(0x2095A59FF0BFBBEF); so->WriteValue(ContentResolver::LevelFile); // Preprocess events first, so they can change some level properties bool hasMultiplayerSpawnPoints = false; for (std::int32_t y = 0; y < _layers[3].Height; y++) { for (std::int32_t x = 0; x < _layers[3].Width; x++) { auto& tileEvent = _events[x + y * _layers[3].Width]; JJ2Event eventType; if (tileEvent.EventType == JJ2Event::MODIFIER_GENERATOR) { // Generators are converted differently std::uint8_t eventParams[8]; EventConverter::ConvertParamInt(tileEvent.TileParams, { { JJ2ParamUInt, 8 }, // Event { JJ2ParamUInt, 8 }, // Delay { JJ2ParamBool, 1 } // Initial Delay }, eventParams); eventType = (JJ2Event)eventParams[0]; tileEvent.GeneratorDelay = eventParams[1]; tileEvent.GeneratorFlags = (std::uint8_t)eventParams[2]; } else { eventType = tileEvent.EventType; tileEvent.GeneratorDelay = -1; tileEvent.GeneratorFlags = 0; if (tileEvent.EventType == JJ2Event::MP_LEVEL_START) { hasMultiplayerSpawnPoints = true; } } tileEvent.Converted = eventConverter.TryConvert(this, eventType, tileEvent.TileParams); } } for (std::size_t i = 0; i < _offGridEvents.size(); i++) { auto& offGridEvent = _offGridEvents[i]; JJ2Event eventType; if (offGridEvent.EventType == JJ2Event::MODIFIER_GENERATOR) { // Generators are converted differently std::uint8_t eventParams[8]; EventConverter::ConvertParamInt(offGridEvent.TileParams, { { JJ2ParamUInt, 8 }, // Event { JJ2ParamUInt, 8 }, // Delay { JJ2ParamBool, 1 } // Initial Delay }, eventParams); eventType = (JJ2Event)eventParams[0]; offGridEvent.GeneratorDelay = eventParams[1]; offGridEvent.GeneratorFlags = (std::uint8_t)eventParams[2]; } else { eventType = offGridEvent.EventType; offGridEvent.GeneratorDelay = -1; offGridEvent.GeneratorFlags = 0; } offGridEvent.Converted = eventConverter.TryConvert(this, eventType, offGridEvent.TileParams); } CheckWaterLevelAroundStart(); // Flags LevelFlags flags = LevelFlags::None; if (_hasPit) { flags |= LevelFlags::HasPit; } if (_hasPitInstantDeath) { flags |= LevelFlags::HasPitInstantDeath; } if (_useLevelPalette) { flags |= LevelFlags::UseLevelPalette; } if (_isHidden) { flags |= LevelFlags::IsHidden; } if (_isMpLevel) { flags |= LevelFlags::IsMultiplayerLevel; if (_hasLaps) { flags |= LevelFlags::HasLaps; } if (_hasCTF) { flags |= LevelFlags::HasCaptureTheFlag; } } if (_verticalMPSplitscreen) { flags |= LevelFlags::HasVerticalSplitscreen; } if (hasMultiplayerSpawnPoints) { flags |= LevelFlags::HasMultiplayerSpawnPoints; } so->WriteValueAsLE(std::uint16_t(flags)); MemoryStream ms(1024 * 1024); { DeflateWriter co(ms); String formattedName = JJ2Strings::RecodeString(DisplayName, true); co.WriteValue(std::uint8_t(formattedName.size())); co.Write(formattedName.data(), formattedName.size()); StringUtils::lowercaseInPlace(NextLevel); StringUtils::lowercaseInPlace(SecretLevel); StringUtils::lowercaseInPlace(BonusLevel); WriteLevelName(co, NextLevel, std::move(levelTokenConversion)); WriteLevelName(co, SecretLevel, std::move(levelTokenConversion)); WriteLevelName(co, BonusLevel, std::move(levelTokenConversion)); // Default Tileset StringUtils::lowercaseInPlace(Tileset); if (Tileset.hasSuffix(".j2t"_s)) { Tileset = Tileset.exceptSuffix(".j2t"_s); } if (LevelName == "arace2"_s && Tileset == "hauntedh"_s) { // arace2.j2l uses hauntedh.j2t, but this file doesn't exist in some distributions Tileset = "hauntedh1"_s; } co.WriteValue((std::uint8_t)Tileset.size()); co.Write(Tileset.data(), Tileset.size()); // Default Music StringUtils::lowercaseInPlace(Music); if (Music.find('.') == nullptr) { String music = Music + ".j2b"_s; co.WriteValue(std::uint8_t(music.size())); co.Write(music.data(), music.size()); } else { co.WriteValue(std::uint8_t(Music.size())); co.Write(Music.data(), Music.size()); } co.WriteValue(_darknessColor & 0xff); co.WriteValue((_darknessColor >> 8) & 0xff); co.WriteValue((_darknessColor >> 16) & 0xff); co.WriteValue(std::uint8_t(std::min(LightingStart * 255 / 64, 255))); co.WriteValue(std::uint8_t(_weatherType)); co.WriteValue(_weatherIntensity); co.WriteValueAsLE(_waterLevel); // Find caption tile std::uint16_t maxTiles = std::uint16_t(GetMaxSupportedTiles()); std::uint16_t captionTileId = 0; for (std::uint16_t i = 0; i < maxTiles; i++) { if (_staticTiles[i].Type == 4) { captionTileId = i; break; } } co.WriteValueAsLE(captionTileId); // Custom palettes if (_useLevelPalette) { for (std::int32_t i = 0; i < sizeof(_levelPalette); i += 3) { // Expand JJ2+ RGB palette to RGBA // The first palette entry is fixed to transparent black std::uint32_t color = (i != 0 ? ((std::uint32_t)_levelPalette[i] | ((std::uint32_t)_levelPalette[i + 1] << 8) | ((std::uint32_t)_levelPalette[i + 2] << 16) | 0xff000000) : 0x00000000); co.WriteValueAsLE(color); } } std::uint8_t additionalPaletteCount = (std::uint8_t)AlternatePalettes.size(); co.WriteValue(additionalPaletteCount); for (std::int32_t i = 0; i < additionalPaletteCount; i++) { const auto& palette = AlternatePalettes[i]; co.WriteValue((std::uint8_t)palette.Name.size()); co.Write(palette.Name.data(), palette.Name.size()); for (std::int32_t i = 0; i < sizeof(palette.Colors); i += 3) { // Expand JJ2+ RGB palette to RGBA // The first palette entry is fixed to transparent black std::uint32_t color = (i != 0 ? ((std::uint32_t)palette.Colors[i] | ((std::uint32_t)palette.Colors[i + 1] << 8) | ((std::uint32_t)palette.Colors[i + 2] << 16) | 0xff000000) : 0x00000000); co.WriteValueAsLE(color); } } // Extra Tilesets co.WriteValue((std::uint8_t)ExtraTilesets.size()); for (auto& tileset : ExtraTilesets) { std::uint8_t tilesetFlags = 0; if (tileset.ColorMode == TilesetColorMode::Remapped8bit || tileset.ColorMode == TilesetColorMode::AlternatePalette24bit) { tilesetFlags |= 0x01; // Remapped } if (tileset.ColorMode == TilesetColorMode::Original24bit || tileset.ColorMode == TilesetColorMode::AlternatePalette24bit) { tilesetFlags |= 0x02; // 24-bit } co.WriteValue(tilesetFlags); StringUtils::lowercaseInPlace(tileset.Name); if (tileset.Name.hasSuffix(".j2t"_s)) { tileset.Name = tileset.Name.exceptSuffix(".j2t"_s); } co.WriteValue((std::uint8_t)tileset.Name.size()); co.Write(tileset.Name.data(), tileset.Name.size()); co.WriteValueAsLE(tileset.Offset); co.WriteValueAsLE(tileset.Count); if (tileset.ColorMode == TilesetColorMode::Remapped8bit) { co.Write(tileset.PaletteRemapping, sizeof(tileset.PaletteRemapping)); } else if (tileset.ColorMode == TilesetColorMode::AlternatePalette24bit) { co.WriteValue(tileset.AlternatePaletteMappingID24Bit); } } // Overriden tiles co.WriteVariableUint32((std::uint32_t)_overridenTileDiffuses.size()); for (auto& tile : _overridenTileDiffuses) { co.WriteValueAsLE(tile.TileID); co.Write(tile.Diffuse, sizeof(tile.Diffuse)); } co.WriteVariableUint32((std::uint32_t)_overridenTileMasks.size()); for (auto& tile : _overridenTileMasks) { co.WriteValueAsLE(tile.TileID); co.Write(tile.Mask, sizeof(tile.Mask)); } // Text Event Strings co.WriteValue(TextEventStringsCount); for (std::int32_t i = 0; i < TextEventStringsCount; i++) { String& text = _textEventStrings[i]; bool isLevelToken = false; for (std::uint8_t textId : _levelTokenTextIds) { if (i == textId) { isLevelToken = true; break; } } if (isLevelToken) { String adjustedText; auto levelTokens = text.split('|'); for (int j = 0; j < levelTokens.size(); j++) { if (j != 0) { adjustedText += "|"_s; } StringUtils::lowercaseInPlace(levelTokens[j]); LevelToken token = levelTokenConversion(levelTokens[j]); if (!token.Episode.empty()) { adjustedText += token.Episode + "/"_s; } adjustedText += token.Level; } co.WriteValueAsLE(adjustedText.size()); co.Write(adjustedText.data(), adjustedText.size()); } else { String formattedText = JJ2Strings::RecodeString(text); co.WriteValueAsLE(formattedText.size()); co.Write(formattedText.data(), formattedText.size()); } } std::uint16_t lastTilesetTileIndex = std::uint16_t(maxTiles - _animCount); co.WriteValueAsLE(lastTilesetTileIndex); // Animated Tiles co.WriteValueAsLE(_animCount); for (std::int32_t i = 0; i < _animCount; i++) { auto& tile = _animatedTiles[i]; co.WriteValue(tile.FrameCount); co.WriteValueAsLE(std::uint16_t(tile.Speed == 0 ? 0 : 16 * 50 / tile.Speed)); co.WriteValueAsLE(tile.Delay); co.WriteValueAsLE(tile.DelayJitter); co.WriteValue(tile.IsPingPong ? 1 : 0); co.WriteValueAsLE(tile.ReverseDelay); for (std::int32_t j = 0; j < tile.FrameCount; j++) { // Max. tiles is either 0x0400 or 0x1000 and doubles as a mask to separate flipped tiles. // In J2L, each flipped tile had a separate entry in the tile list, probably to make // the dictionary concept easier to handle. bool flipX = false, flipY = false; std::uint16_t tileIdx = tile.Frames[j]; if ((tileIdx & maxTiles) != 0) { flipX = true; tileIdx &= ~maxTiles; } if ((tileIdx & 0x2000) != 0) { flipY = true; tileIdx &= ~0x2000; } if (tileIdx >= lastTilesetTileIndex) { std::uint16_t animIndex = tileIdx - lastTilesetTileIndex; if (animIndex < _animCount) { tileIdx = _animatedTiles[animIndex].Frames[0]; } else { LOGE("Animated tile references undefined tile {} in level \"{}\" (max. tile count is {}, anim. count is {})", tileIdx, LevelName, maxTiles, _animCount); tileIdx = 0; } } std::uint8_t tileFlags = 0x00; if (flipX) { tileFlags |= 0x01; // Flip X } if (flipY) { tileFlags |= 0x02; // Flip Y } if (_staticTiles[tileIdx].Type == 1) { tileFlags |= 0x10; // Legacy Translucent } else if (_staticTiles[tileIdx].Type == 3) { tileFlags |= 0x20; // Invisible } co.WriteValue(tileFlags); co.WriteValueAsLE(tileIdx); } } // Layers std::int32_t layerCount = 0; for (std::int32_t i = 0; i < _layers.size(); i++) { auto& layer = _layers[i]; if (layer.Width == 0 || layer.Height == 0) { layer.Used = false; } if (layer.Used) { layerCount++; } } co.WriteValue(layerCount); for (std::int32_t i = 0; i < _layers.size(); i++) { auto& layer = _layers[i]; if (layer.Used) { bool isSky = (i == 7); bool isSprite = (i == 3); co.WriteValue(isSprite ? 2 : (isSky ? 1 : 0)); // Layer type std::uint16_t flags = (std::uint16_t)(layer.Flags & (0x01 | 0x02 | 0x04)); // RepeatX, RepeatY, UseInherentOffset are mapped 1:1 if (layer.Visible) { flags |= 0x08; } co.WriteValueAsLE(flags); // Layer flags co.WriteValueAsLE(layer.Width); co.WriteValueAsLE(layer.Height); if (!isSprite) { Tiles::LayerSpeedModel speedModelX, speedModelY; switch (layer.SpeedModelX) { case LayerSectionSpeedModel::Legacy: speedModelX = Tiles::LayerSpeedModel::AlwaysOnTop; break; case LayerSectionSpeedModel::FitLevel: speedModelX = Tiles::LayerSpeedModel::FitLevel; break; case LayerSectionSpeedModel::SpeedMultipliers: speedModelX = Tiles::LayerSpeedModel::SpeedMultipliers; break; default: speedModelX = Tiles::LayerSpeedModel::Default; break; } switch (layer.SpeedModelY) { case LayerSectionSpeedModel::Legacy: speedModelY = Tiles::LayerSpeedModel::AlwaysOnTop; break; case LayerSectionSpeedModel::FitLevel: speedModelY = Tiles::LayerSpeedModel::FitLevel; break; case LayerSectionSpeedModel::SpeedMultipliers: speedModelY = Tiles::LayerSpeedModel::SpeedMultipliers; break; default: speedModelY = Tiles::LayerSpeedModel::Default; break; } std::uint8_t combinedSpeedModel = (std::uint8_t)(((std::int32_t)speedModelX & 0x0f) | (((std::int32_t)speedModelY & 0x0f) << 4)); co.WriteValue(combinedSpeedModel); bool hasTexturedBackground = ((layer.Flags & 0x08) == 0x08); if (isSky && !hasTexturedBackground && layer.SpeedModelX <= LayerSectionSpeedModel::Legacy && layer.SpeedModelY <= LayerSectionSpeedModel::Legacy) { co.WriteValueAsLE(180.0f); co.WriteValueAsLE(-300.0f); } else { co.WriteValueAsLE(layer.OffsetX); co.WriteValueAsLE(layer.OffsetY); } float speedX = layer.SpeedX; float speedY = layer.SpeedY; float autoSpeedX = layer.AutoSpeedX; float autoSpeedY = layer.AutoSpeedY; if (layer.SpeedModelX == LayerSectionSpeedModel::FitLevel || (layer.SpeedModelX <= LayerSectionSpeedModel::Legacy && !hasTexturedBackground && std::abs(autoSpeedX) > 0.0f)) { speedX = 0.0f; } if (layer.SpeedModelY == LayerSectionSpeedModel::FitLevel || (layer.SpeedModelY <= LayerSectionSpeedModel::Legacy && !hasTexturedBackground && std::abs(autoSpeedY) > 0.0f)) { speedY = 0.0f; } if (layer.SpeedModelX == LayerSectionSpeedModel::FitLevel) { autoSpeedX = 0.0f; } if (layer.SpeedModelY == LayerSectionSpeedModel::FitLevel) { autoSpeedY = 0.0f; } co.WriteValueAsLE(speedX); co.WriteValueAsLE(speedY); co.WriteValueAsLE(autoSpeedX); co.WriteValueAsLE(autoSpeedY); co.WriteValueAsLE(layer.Depth); if (isSky && hasTexturedBackground) { co.WriteValue(layer.TexturedBackgroundType + (std::uint8_t)Tiles::LayerRendererType::Sky); co.WriteValue(layer.TexturedParams1); co.WriteValue(layer.TexturedParams2); co.WriteValue(layer.TexturedParams3); co.WriteValue((layer.Flags & 0x10) == 0x10 ? 255 : 0); // ParallaxStarsEnabled } else if (layer.SpriteMode == 2) { co.WriteValue((std::uint8_t)Tiles::LayerRendererType::Tinted); co.WriteValue(layer.SpriteParam); co.WriteValue(0); co.WriteValue(0); co.WriteValue(255); } else { co.WriteValue((std::uint8_t)Tiles::LayerRendererType::Default); co.WriteValue(255); co.WriteValue(255); co.WriteValue(255); co.WriteValue(255); } } for (std::int32_t y = 0; y < layer.Height; y++) { for (std::int32_t x = 0; x < layer.Width; x++) { std::uint16_t tileIdx = layer.Tiles[y * layer.InternalWidth + x]; bool flipX = false, flipY = false; if ((tileIdx & 0x2000) != 0) { flipY = true; tileIdx -= 0x2000; } if ((tileIdx & ~(maxTiles | (maxTiles - 1))) != 0) { // Fix of bug in updated Psych2.j2l tileIdx = (std::uint16_t)((tileIdx & (maxTiles | (maxTiles - 1))) | maxTiles); } // Max. tiles is either 0x0400 or 0x1000 and doubles as a mask to separate flipped tiles. // In J2L, each flipped tile had a separate entry in the tile list, probably to make // the dictionary concept easier to handle. if ((tileIdx & maxTiles) > 0) { flipX = true; tileIdx -= maxTiles; } bool legacyTranslucent = false; bool invisible = false; if (tileIdx < lastTilesetTileIndex) { legacyTranslucent = (_staticTiles[tileIdx].Type == 1); invisible = (_staticTiles[tileIdx].Type == 3); } std::uint8_t tileFlags = 0; if (flipX) { tileFlags |= 0x01; } if (flipY) { tileFlags |= 0x02; } if (legacyTranslucent) { tileFlags |= 0x10; } else if (invisible) { tileFlags |= 0x20; } co.WriteValue(tileFlags); co.WriteValueAsLE(tileIdx); } } } } // Events for (std::int32_t y = 0; y < _layers[3].Height; y++) { for (std::int32_t x = 0; x < _layers[3].Width; x++) { auto& tileEvent = _events[x + y * _layers[3].Width]; // TODO: Flag 0x08 not used std::int32_t flags = 0; if (tileEvent.Illuminate) { flags |= 0x04; // Illuminated } if (tileEvent.Difficulty != 2 /*Hard*/) { flags |= 0x10; // Difficulty: Easy } if (tileEvent.Difficulty == 0 /*All*/) { flags |= 0x20; // Difficulty: Normal } if (tileEvent.Difficulty != 1 /*Easy*/) { flags |= 0x40; // Difficulty: Hard } if (tileEvent.Difficulty == 3 /*Multiplayer*/) { flags |= 0x80; // Multiplayer Only } co.WriteValueAsLE(std::uint16_t(tileEvent.Converted.Type)); bool allZeroes = true; if (tileEvent.Converted.Type != EventType::Empty) { for (std::int32_t i = 0; i < std::int32_t(arraySize(tileEvent.Converted.Params)); i++) { if (tileEvent.Converted.Params[i] != 0) { allZeroes = false; break; } } } if (allZeroes) { if (tileEvent.GeneratorDelay == -1) { co.WriteValue(flags | 0x01 /*NoParams*/); } else { co.WriteValue(flags | 0x01 /*NoParams*/ | 0x02 /*Generator*/); co.WriteValue(tileEvent.GeneratorFlags); co.WriteValue(tileEvent.GeneratorDelay); } } else { if (tileEvent.GeneratorDelay == -1) { co.WriteValue(flags); } else { co.WriteValue(flags | 0x02 /*Generator*/); co.WriteValue(tileEvent.GeneratorFlags); co.WriteValue(tileEvent.GeneratorDelay); } co.Write(tileEvent.Converted.Params, sizeof(tileEvent.Converted.Params)); } } } co.WriteVariableUint32((std::uint32_t)_offGridEvents.size()); for (std::size_t i = 0; i < _offGridEvents.size(); i++) { auto& offGridEvent = _offGridEvents[i]; co.WriteVariableUint32(offGridEvent.X); co.WriteVariableUint32(offGridEvent.Y); // TODO: Flag 0x08 not used std::int32_t flags = 0; if (offGridEvent.Illuminate) { flags |= 0x04; // Illuminated } if (offGridEvent.Difficulty != 2 /*Hard*/) { flags |= 0x10; // Difficulty: Easy } if (offGridEvent.Difficulty == 0 /*All*/) { flags |= 0x20; // Difficulty: Normal } if (offGridEvent.Difficulty != 1 /*Easy*/) { flags |= 0x40; // Difficulty: Hard } if (offGridEvent.Difficulty == 3 /*Multiplayer*/) { flags |= 0x80; // Multiplayer Only } co.WriteValueAsLE(std::uint16_t(offGridEvent.Converted.Type)); bool allZeroes = true; if (offGridEvent.Converted.Type != EventType::Empty) { for (std::int32_t i = 0; i < std::int32_t(arraySize(offGridEvent.Converted.Params)); i++) { if (offGridEvent.Converted.Params[i] != 0) { allZeroes = false; break; } } } if (allZeroes) { if (offGridEvent.GeneratorDelay == -1) { co.WriteValue(flags | 0x01 /*NoParams*/); } else { co.WriteValue(flags | 0x01 /*NoParams*/ | 0x02 /*Generator*/); co.WriteValue(offGridEvent.GeneratorFlags); co.WriteValue(offGridEvent.GeneratorDelay); } } else { if (offGridEvent.GeneratorDelay == -1) { co.WriteValue(flags); } else { co.WriteValue(flags | 0x02 /*Generator*/); co.WriteValue(offGridEvent.GeneratorFlags); co.WriteValue(offGridEvent.GeneratorDelay); } co.Write(offGridEvent.Converted.Params, sizeof(offGridEvent.Converted.Params)); } } } so->WriteValueAsLE(std::int32_t(ms.GetSize())); so->Write(ms.GetBuffer(), ms.GetSize()); #if defined(DEATH_DEBUG) /*auto episodeName = fs::GetFileName(fs::GetDirectoryName(targetPath)); if (episodeName != "unknown"_s) { std::int32_t lastStringIdx = -1; for (std::int32_t i = TextEventStringsCount - 1; i >= 0; i--) { String& text = _textEventStrings[i]; if (!text.empty()) { lastStringIdx = i; break; } } if (lastStringIdx >= 0) { auto so = fs::Open(targetPath + ".h"_s, FileAccessMode::Write); DEATH_ASSERT(so->IsValid(), "Cannot open file for writing", ); for (std::int32_t i = 0; i <= lastStringIdx; i++) { String& text = _textEventStrings[i]; bool isLevelToken = false; for (std::uint8_t textId : _levelTokenTextIds) { if (i == textId) { isLevelToken = true; break; } } String formattedText = JJ2Strings::RecodeString(text, false, true); if (isLevelToken || formattedText.empty()) { so->Write("// [Empty text]\n", sizeof("// [Empty text]\n") - 1); continue; } String levelFullName = episodeName + "/"_s + fs::GetFileNameWithoutExtension(targetPath); so->Write("_x(\"", sizeof("__(\"") - 1); so->Write(levelFullName.data(), levelFullName.size()); so->Write("\", \"", sizeof("\", \"") - 1); so->Write(formattedText.data(), formattedText.size()); so->Write("\"); // ", sizeof("\"); // ") - 1); char buffer[32]; u32tos((uint32_t)i, buffer); so->Write(buffer, std::strlen(buffer)); so->Write("\n", sizeof("\n") - 1); } } }*/ #endif } void JJ2Level::AddLevelTokenTextID(std::uint8_t textId) { _levelTokenTextIds.push_back(textId); } void JJ2Level::CheckWaterLevelAroundStart() { bool waterAround = true; bool waterShouldResetLighting = true; std::int32_t waterLevelAround = INT32_MIN; // Check neighbor tiles of level start events if they contain water event, then set it as initial water level instead for (std::int32_t y = 0; y < _layers[3].Height; y++) { for (std::int32_t x = 0; x < _layers[3].Width; x++) { auto& tileEvent = _events[x + y * _layers[3].Width]; if (tileEvent.Converted.Type == EventType::LevelStart || tileEvent.Converted.Type == EventType::LevelStartMultiplayer) { std::int32_t y2 = std::min(y + 1, _layers[3].Height - 1); if (x - 1 >= 0) { auto& waterTile = _events[(x - 1) + y2 * _layers[3].Width]; if (waterTile.Converted.Type != EventType::ModifierSetWater || waterTile.Difficulty != 0 /*!All*/ || waterTile.Converted.Params[2] == 0 /*!Instant*/) { waterAround = false; } else { // Check if water level matches other tiles std::int32_t waterLevel = waterTile.Converted.Params[0] | (waterTile.Converted.Params[1] << 8); if (waterLevelAround == INT32_MIN) { waterLevelAround = waterLevel; } else if (waterLevelAround != waterLevel) { waterAround = false; } if (waterTile.Converted.Params[3] != 0 /* !IgnoreLighting */) { waterShouldResetLighting = false; } } } if (y2 != y) { auto& waterTile = _events[x + y2 * _layers[3].Width]; if (waterTile.Converted.Type != EventType::ModifierSetWater || waterTile.Difficulty != 0 /*!All*/ || waterTile.Converted.Params[2] == 0 /*!Instant*/) { waterAround = false; } else { // Check if water level matches other tiles std::int32_t waterLevel = waterTile.Converted.Params[0] | (waterTile.Converted.Params[1] << 8); if (waterLevelAround == INT32_MIN) { waterLevelAround = waterLevel; } else if (waterLevelAround != waterLevel) { waterAround = false; } if (waterTile.Converted.Params[3] != 0 /* !IgnoreLighting */) { waterShouldResetLighting = false; } } } if (x + 1 < _layers[3].Width) { auto& waterTile = _events[(x + 1) + y2 * _layers[3].Width]; if (waterTile.Converted.Type != EventType::ModifierSetWater || waterTile.Difficulty != 0 /*!All*/ || waterTile.Converted.Params[2] == 0 /*!Instant*/) { waterAround = false; } else { // Check if water level matches other tiles std::int32_t waterLevel = waterTile.Converted.Params[0] | (waterTile.Converted.Params[1] << 8); if (waterLevelAround == INT32_MIN) { waterLevelAround = waterLevel; } else if (waterLevelAround != waterLevel) { waterAround = false; } if (waterTile.Converted.Params[3] != 0 /* !IgnoreLighting */) { waterShouldResetLighting = false; } } } } } } if (waterAround && waterLevelAround != INT32_MIN) { // Override water level of the level _waterLevel = std::uint16_t(waterLevelAround); if (waterShouldResetLighting) { // Reset also ambient lighting, because the original game ignored ambient lighting if water was used LightingStart = 64; } } } void JJ2Level::WriteLevelName(Stream& so, MutableStringView value, Function&& levelTokenConversion) { if (!value.empty()) { MutableStringView adjustedValue = value; if (StringHasSuffixIgnoreCase(adjustedValue, ".j2l"_s) || StringHasSuffixIgnoreCase(adjustedValue, ".lev"_s)) { adjustedValue = adjustedValue.exceptSuffix(4); } if (levelTokenConversion) { LevelToken token = levelTokenConversion(adjustedValue); if (!token.Episode.empty()) { String fullName = token.Episode + '/' + token.Level; so.WriteValue((std::uint8_t)fullName.size()); so.Write(fullName.data(), fullName.size()); } else { so.WriteValue((std::uint8_t)token.Level.size()); so.Write(token.Level.data(), token.Level.size()); } } else { so.WriteValue((std::uint8_t)adjustedValue.size()); so.Write(adjustedValue.data(), adjustedValue.size()); } } else { so.WriteValue(0); } } bool JJ2Level::StringHasSuffixIgnoreCase(StringView value, StringView suffix) { const std::size_t size = value.size(); const std::size_t suffixSize = suffix.size(); if (size < suffixSize) return false; for (std::size_t i = 0; i < suffixSize; i++) { if (tolower(value[size - suffixSize + i]) != suffix[i]) { return false; } } return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Level.h000066400000000000000000000152411512772601700260750ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Block.h" #include "JJ2Event.h" #include "JJ2Version.h" #include "EventConverter.h" #include "../WeatherType.h" #include #include #include #include #include using namespace Death::Containers; using namespace Death::IO; namespace Jazz2::Compatibility { class JJ2Episode; /** @brief Parses original `.j2l` level files */ class JJ2Level { friend class JJ2Episode; public: /** @brief Episode name and level name */ struct LevelToken { /** @brief Episode name */ String Episode; /** @brief Level name */ String Level; }; /** @brief Alternate palette */ struct AlternatePalette { /** @brief Palette name */ String Name; /** @brief Colors (24-bit) */ std::uint8_t Colors[256 * 3]; }; /** @brief Color mode of tile set */ enum class TilesetColorMode : std::uint8_t { Original8bit, /**< Original 8-bit palette is used */ Remapped8bit, /**< 8-bit palette is remapped according to @ref PaletteRemapping */ Original24bit, /**< Original 24-bit palette is used */ AlternatePalette24bit /**< Alternate 24-bit palette is used */ }; /** @brief Extra tileset used in the level */ struct ExtraTilesetEntry { /** @brief Tile set name */ String Name; /** @brief Offset tile index */ std::uint16_t Offset; /** @brief Number of tiles */ std::uint16_t Count; /** @brief Color mode */ TilesetColorMode ColorMode; union { /** @brief Palette remapping */ std::uint8_t PaletteRemapping[256]; /** @brief Alternate palette mapping ID for 24-bit */ std::uint8_t AlternatePaletteMappingID24Bit; }; }; /** @brief Number of layers in the original game */ static constexpr std::int32_t JJ2LayerCount = 8; /** @brief Number of level text entries in the original game */ static constexpr std::int32_t TextEventStringsCount = 16; String LevelName; String DisplayName; String Tileset; SmallVector AlternatePalettes; SmallVector ExtraTilesets; String Music; String NextLevel; String BonusLevel; String SecretLevel; std::uint8_t LightingMin, LightingStart; JJ2Level() : _version(JJ2Version::Unknown), _animCount(0), _verticalMPSplitscreen(false), _isMpLevel(false), _hasPit(false), _hasPitInstantDeath(false), _hasCTF(false), _hasLaps(false), _useLevelPalette(false) { } bool Open(StringView path, bool strictParser); void Convert(StringView targetPath, EventConverter& eventConverter, Function&& levelTokenConversion = {}); void AddLevelTokenTextID(std::uint8_t textId); /** @brief Returns target version of the level */ JJ2Version GetVersion() const { return _version; } /** @brief Returns maximum number of supported tiles */ std::int32_t GetMaxSupportedTiles() const { return (_version == JJ2Version::BaseGame ? 1024 : 4096); } /** @brief Returns maximum number of supported animations */ std::int32_t GetMaxSupportedAnims() const { return (_version == JJ2Version::BaseGame ? 128 : 256); } private: enum class LayerSectionSpeedModel { Normal, Legacy, BothSpeeds, FromStart, FitLevel, SpeedMultipliers }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct LayerSection { std::uint32_t Flags; std::uint8_t Type; // Ignored bool Used; bool Visible; std::uint8_t DetailLevel; // Ignored std::int32_t Width; std::int32_t InternalWidth; std::int32_t Height; std::int32_t Depth; float OffsetX; float OffsetY; float SpeedX; float SpeedY; float AutoSpeedX; float AutoSpeedY; LayerSectionSpeedModel SpeedModelX; LayerSectionSpeedModel SpeedModelY; std::uint8_t TexturedBackgroundType; std::uint8_t TexturedParams1; std::uint8_t TexturedParams2; std::uint8_t TexturedParams3; std::uint8_t SpriteMode; std::uint8_t SpriteParam; std::unique_ptr Tiles; }; struct TileEventSection { JJ2Event EventType; std::uint8_t GeneratorFlags; std::uint8_t Difficulty; bool Illuminate; std::uint32_t TileParams; // Partially supported std::int32_t GeneratorDelay; ConversionResult Converted; }; struct TilePropertiesSection { TileEventSection Event; bool Flipped; std::uint8_t Type; }; struct AnimatedTileSection { std::uint16_t Delay; std::uint16_t DelayJitter; std::uint16_t ReverseDelay; bool IsPingPong; std::uint8_t Speed; std::uint8_t FrameCount; std::uint16_t Frames[64]; }; struct OverridenTileDiffuse { std::uint16_t TileID; std::uint8_t Diffuse[32 * 32]; }; struct OverridenTileMask { std::uint16_t TileID; std::uint8_t Mask[32 * 32]; }; struct OffGridEvent { std::uint16_t X; std::uint16_t Y; JJ2Event EventType; std::uint8_t GeneratorFlags; std::uint8_t Difficulty; bool Illuminate; std::uint32_t TileParams; // Partially supported std::int32_t GeneratorDelay; ConversionResult Converted; }; #endif JJ2Version _version; std::uint16_t _animCount; bool _isHidden, _verticalMPSplitscreen, _isMpLevel; bool _hasPit, _hasPitInstantDeath, _hasCTF, _hasLaps; std::uint32_t _darknessColor; WeatherType _weatherType; std::uint8_t _weatherIntensity; std::uint16_t _waterLevel; String _textEventStrings[TextEventStringsCount]; std::uint8_t _levelPalette[256 * 3]; bool _useLevelPalette; SmallVector _layers; std::unique_ptr _staticTiles; std::unique_ptr _animatedTiles; SmallVector _overridenTileDiffuses; SmallVector _overridenTileMasks; std::unique_ptr _events; SmallVector _levelTokenTextIds; SmallVector _offGridEvents; void LoadMetadata(JJ2Block& block, bool strictParser); void LoadStaticTileData(JJ2Block& block, bool strictParser); void LoadAnimatedTiles(JJ2Block& block, bool strictParser); void LoadLayerMetadata(JJ2Block& block, bool strictParser); void LoadEvents(JJ2Block& block, bool strictParser); void LoadLayers(JJ2Block& dictBlock, std::int32_t dictLength, JJ2Block& layoutBlock, bool strictParser); void LoadMlleData(JJ2Block& block, std::uint32_t version, StringView path, bool strictParser); void CheckWaterLevelAroundStart(); static void WriteLevelName(Stream& so, MutableStringView value, Function&& levelTokenConversion = {}); static bool StringHasSuffixIgnoreCase(StringView value, StringView suffix); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Strings.cpp000066400000000000000000000226541512772601700270200ustar00rootroot00000000000000#include "JJ2Strings.h" #include "../../nCine/Base/Algorithms.h" #include #include #include #include using namespace Death::Containers::Literals; using namespace Death::IO; using namespace nCine; static const std::uint16_t Windows1250_Utf8[256] = { 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f, 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f, 0x20ac, 0xffff, 0x201a, 0xffff, 0x201e, 0x2026, 0x2020, 0x2021, 0xffff, 0x2030, 0x0160, 0x2039, 0x015a, 0x0164, 0x017d, 0x0179, 0xffff, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x2013, 0x2014, 0xffff, 0x2122, 0x0161, 0x203a, 0x015b, 0x0165, 0x017e, 0x017a, 0x00a0, 0x02c7, 0x02d8, 0x0141, 0x00a4, 0x0104, 0x00a6, 0x00a7, 0x00a8, 0x00a9, 0x015e, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x017b, 0x00b0, 0x00b1, 0x02db, 0x0142, 0x00b4, 0x00b5, 0x00b6, 0x00b7, 0x00b8, 0x0105, 0x015f, 0x00bb, 0x013d, 0x02dd, 0x013e, 0x017c, 0x0154, 0x00c1, 0x00c2, 0x0102, 0x00c4, 0x0139, 0x0106, 0x00c7, 0x010c, 0x00c9, 0x0118, 0x00cb, 0x011a, 0x00cd, 0x00ce, 0x010e, 0x0110, 0x0143, 0x0147, 0x00d3, 0x00d4, 0x0150, 0x00d6, 0x00d7, 0x0158, 0x016e, 0x00da, 0x0170, 0x00dc, 0x00dd, 0x0162, 0x00df, 0x0155, 0x00e1, 0x00e2, 0x0103, 0x00e4, 0x013a, 0x0107, 0x00e7, 0x010d, 0x00e9, 0x0119, 0x00eb, 0x011b, 0x00ed, 0x00ee, 0x010f, 0x0111, 0x0144, 0x0148, 0x00f3, 0x00f4, 0x0151, 0x00f6, 0x00f7, 0x0159, 0x016f, 0x00fa, 0x0171, 0x00fc, 0x00fd, 0x0163, 0x02d9 }; static const std::uint32_t DefaultFontColors[] = { 0x707485, 0x409062, 0x629040, 0x804040, 0x6270d0, 0xc07050, 0xa05c6a }; namespace Jazz2::Compatibility { bool JJ2Strings::Open(StringView path) { auto s = fs::Open(path, FileAccess::Read); if (!s->IsValid()) { LOGE("Cannot open file \"{}\" for reading", path); return false; } Name = fs::GetFileNameWithoutExtension(path); StringUtils::lowercaseInPlace(Name); std::uint32_t offsetArraySize = s->ReadValueAsLE(); std::uint32_t textArraySize = s->ReadValueAsLE(); std::unique_ptr textArray = std::make_unique(textArraySize); s->Read(textArray.get(), textArraySize); CommonTexts.reserve(offsetArraySize); for (std::uint32_t i = 0; i < offsetArraySize; i++) { std::uint32_t offset = s->ReadValueAsLE(); CommonTexts.emplace_back(&textArray[offset]); } std::uint32_t levelEntryCount = s->ReadValueAsLE(); SmallVector counts, offsets; counts.reserve(levelEntryCount); offsets.reserve(levelEntryCount + 1); LevelTexts.reserve(levelEntryCount); for (std::uint32_t i = 0; i < levelEntryCount; i++) { char levelName[8 + 1]; s->Read(levelName, 8); levelName[8] = '\0'; LevelEntry& levelEntry = LevelTexts.emplace_back(levelName); StringUtils::lowercaseInPlace(levelEntry.Name); /*uint8_t unknown =*/ s->ReadValue(); counts.emplace_back(s->ReadValue()); offsets.emplace_back(s->ReadValueAsLE()); } std::uint32_t textArray2Size = s->ReadValueAsLE(); offsets.emplace_back(textArray2Size); std::uint32_t k = 0; for (std::uint32_t i = 0; i < levelEntryCount; i++) { auto offset = offsets[i + 1]; auto count = counts[i]; auto& level = LevelTexts[i]; level.TextEvents.resize(count + 1); while (k < offset) { std::uint8_t index = s->ReadValue() % 16; k++; std::uint8_t size = s->ReadValue(); k++; String text{NoInit, size}; s->Read(text.data(), size); level.TextEvents[index] = text; k += size; } } return true; } void JJ2Strings::Convert(StringView targetPath, Function&& levelTokenConversion) { auto so = fs::Open(targetPath, FileAccess::Write); DEATH_ASSERT(so->IsValid(), "Cannot open file for writing", ); so->Write("\xEF\xBB\xBF// Common\n", sizeof("\xEF\xBB\xBF// Common\n") - 1); for (std::size_t i = 0; i < CommonTexts.size(); i++) { String formattedText = RecodeString(CommonTexts[i], false, true); if (formattedText.empty()) { so->Write("// [Empty text]\n", sizeof("// [Empty text]\n") - 1); continue; } so->Write("//__(\"", sizeof("//__(\"") - 1); so->Write(formattedText.data(), formattedText.size()); so->Write("\"); // ", sizeof("\"); // ") - 1); char buffer[32]; u32tos((uint32_t)i, buffer); so->Write(buffer, std::strlen(buffer)); so->Write("\n", sizeof("\n") - 1); } so->Write("\n// Levels\n", sizeof("\n// Levels\n") - 1); for (auto& level : LevelTexts) { if (level.TextEvents.empty()) { continue; } String levelName; if (levelTokenConversion) { auto token = levelTokenConversion(level.Name); if (!token.Episode.empty()) { levelName = "/"_s.joinWithoutEmptyParts({ token.Episode, token.Level }); } else { levelName = "unknown/"_s + token.Level; } } else { levelName = "unknown/"_s + level.Name; } for (std::size_t i = 0; i < level.TextEvents.size(); i++) { String formattedText = RecodeString(level.TextEvents[i], false, true); if (formattedText.empty()) { so->Write("// [Empty text]\n", sizeof("// [Empty text]\n") - 1); continue; } so->Write("_x(\"", sizeof("__(\"") - 1); so->Write(levelName.data(), levelName.size()); so->Write("\", \"", sizeof("\", \"") - 1); so->Write(formattedText.data(), formattedText.size()); so->Write("\"); // ", sizeof("\"); // ") - 1); char buffer[32]; u32tos((std::uint32_t)i, buffer); so->Write(buffer, std::strlen(buffer)); so->Write("\n", sizeof("\n") - 1); } so->Write("\n", sizeof("\n") - 1); } } String JJ2Strings::RecodeString(StringView text, bool stripFormatting, bool escaped) { if (text.empty()) { return {}; } char buffer[1024]; std::int32_t colorIndex = 0; bool colorRandom = false; bool colorEmitted = true; bool colorFrozen = true; std::size_t length = text.size(); std::size_t j = 0; for (std::size_t i = 0; i < length && j < sizeof(buffer) - 16; i++) { char current = text[i]; if (current == '@') { // New line if (!stripFormatting) { if (escaped) { buffer[j++] = '\\'; buffer[j++] = 'n'; } else { buffer[j++] = '\n'; } } else { buffer[j++] = ' '; } } else if (current == '\xA7' && (std::isdigit(text[i + 1]) || text[i + 1] == '/')) { // Char spacing (§) i++; if (!stripFormatting) { if (escaped) { buffer[j++] = '\\'; buffer[j++] = 'f'; } else { buffer[j++] = '\f'; } if (text[i] == '/') { buffer[j++] = '['; buffer[j++] = '/'; buffer[j++] = 'w'; buffer[j++] = ']'; } else { std::uint32_t spacing = text[i] - '0'; std::uint32_t converted = 100 - (spacing * 10); j += formatInto({ &buffer[j], 16 }, "[w:{}]", converted); if (colorRandom && !colorFrozen) { colorIndex++; } } } } else if (current == '#') { // Turn on colorization colorRandom = true; colorEmitted = false; colorFrozen = false; } else if (current == '~' && colorRandom) { // Freeze active color if (!colorFrozen) { colorFrozen = true; } else { colorRandom = false; colorEmitted = false; colorFrozen = false; colorIndex = 0; } } // TODO: '|' is usually also used as separator in AS else if (current == '|') { // Skip one color if (!colorFrozen) { colorIndex++; colorEmitted = false; } } else { if (!stripFormatting && !colorEmitted) { colorEmitted = true; if (escaped) { buffer[j++] = '\\'; buffer[j++] = 'f'; } else { buffer[j++] = '\f'; } std::int32_t colorIndex2 = colorIndex % (std::int32_t(arraySize(DefaultFontColors)) + 1); if (colorIndex2 == 0) { buffer[j++] = '['; buffer[j++] = '/'; buffer[j++] = 'c'; buffer[j++] = ']'; } else { j += formatInto({ &buffer[j], 16 }, "[c:#{:.8x}]", DefaultFontColors[colorIndex2 - 1]); } } const std::uint16_t c = Windows1250_Utf8[(std::uint8_t)current]; if (c < 0x80) { buffer[j++] = (char)c; } else if (c < 0x800) { buffer[j++] = 0xc0 | (c >> 6); buffer[j++] = 0x80 | (c & 0x3f); } else if (c < 0xffff) { buffer[j++] = 0xe0 | (c >> 12); buffer[j++] = 0x80 | ((c >> 6) & 0x3f); buffer[j++] = 0x80 | (c & 0x3f); } if (colorRandom && !colorFrozen && c != ' ' && text[i + 1] != '~') { colorIndex++; colorEmitted = false; } } } return String(buffer, j); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Strings.h000066400000000000000000000016231512772601700264560ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Level.h" #include #include #include using namespace Death::Containers; namespace Jazz2::Compatibility { /** @brief Parses original `.j2s` localization files */ class JJ2Strings { public: /** @brief Texts for specific level */ struct LevelEntry { String Name; SmallVector TextEvents; LevelEntry(String name) : Name(name) { } }; String Name; SmallVector CommonTexts; SmallVector LevelTexts; JJ2Strings() { } JJ2Strings(StringView name) : Name(name) { } bool Open(StringView path); void Convert(StringView targetPath, Function&& levelTokenConversion); static String RecodeString(StringView text, bool stripFormatting = false, bool escaped = false); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Tileset.cpp000066400000000000000000000250111512772601700267660ustar00rootroot00000000000000#include "JJ2Tileset.h" #include "JJ2Anims.h" #include "JJ2Block.h" #include "../ContentResolver.h" #include #include #include using namespace Death::IO; using namespace Death::IO::Compression; namespace Jazz2::Compatibility { bool JJ2Tileset::Open(StringView path, bool strictParser) { auto s = fs::Open(path, FileAccess::Read); if (!s->IsValid()) { LOGE("Cannot open file \"{}\" for reading", path); return false; } // Skip copyright notice s->Seek(180, SeekOrigin::Current); JJ2Block headerBlock(s, 262 - 180); std::uint32_t magic = headerBlock.ReadUInt32(); DEATH_ASSERT(magic == 0x454C4954 /*TILE*/, "Invalid magic string", false); std::uint32_t signature = headerBlock.ReadUInt32(); DEATH_ASSERT(signature == 0xAFBEADDE, "Invalid signature", false); _name = headerBlock.ReadString(32, true); std::uint16_t versionNum = headerBlock.ReadUInt16(); _version = (versionNum == 0x300 ? JJ2Version::PlusExtension : (versionNum <= 0x200 ? JJ2Version::BaseGame : JJ2Version::TSF)); std::int32_t recordedSize = headerBlock.ReadInt32(); DEATH_ASSERT(!strictParser || s->GetSize() == recordedSize, "Unexpected file size", false); // Get the CRC; would check here if it matches if we knew what variant it is AND what it applies to // Test file across all CRC32 variants + Adler had no matches to the value obtained from the file // so either the variant is something else or the CRC is not applied to the whole file but on a part /*int recordedCRC =*/ headerBlock.ReadInt32(); // Read the lengths, uncompress the blocks and bail if any block could not be uncompressed // This could look better without all the copy-paste, but meh. std::int32_t infoBlockPackedSize = headerBlock.ReadInt32(); std::int32_t infoBlockUnpackedSize = headerBlock.ReadInt32(); std::int32_t imageBlockPackedSize = headerBlock.ReadInt32(); std::int32_t imageBlockUnpackedSize = headerBlock.ReadInt32(); std::int32_t alphaBlockPackedSize = headerBlock.ReadInt32(); std::int32_t alphaBlockUnpackedSize = headerBlock.ReadInt32(); std::int32_t maskBlockPackedSize = headerBlock.ReadInt32(); std::int32_t maskBlockUnpackedSize = headerBlock.ReadInt32(); JJ2Block infoBlock(s, infoBlockPackedSize, infoBlockUnpackedSize); JJ2Block imageBlock(s, imageBlockPackedSize, imageBlockUnpackedSize); JJ2Block alphaBlock(s, alphaBlockPackedSize, alphaBlockUnpackedSize); JJ2Block maskBlock(s, maskBlockPackedSize, maskBlockUnpackedSize); LoadMetadata(infoBlock); LoadImageData(imageBlock, alphaBlock); LoadMaskData(maskBlock); return true; } void JJ2Tileset::LoadMetadata(JJ2Block& block) { for (std::int32_t i = 0; i < 256; i++) { std::uint32_t color = block.ReadUInt32(); color = (color & 0x00ffffff) | ((255 - ((color >> 24) & 0xff)) << 24); _palette[i] = color; } _tileCount = block.ReadInt32(); // TODO: Use _tileCount instead of maxTiles ??? std::int32_t maxTiles = GetMaxSupportedTiles(); _tiles = std::make_unique(maxTiles); for (std::int32_t i = 0; i < maxTiles; i++) { _tiles[i].Opaque = block.ReadBool(); } // Block of unknown values, skip block.DiscardBytes(maxTiles); for (std::int32_t i = 0; i < maxTiles; i++) { _tiles[i].ImageDataOffset = block.ReadUInt32(); } // Block of unknown values, skip block.DiscardBytes(4 * maxTiles); for (std::int32_t i = 0; i < maxTiles; i++) { _tiles[i].AlphaDataOffset = block.ReadUInt32(); } // Block of unknown values, skip block.DiscardBytes(4 * maxTiles); for (std::int32_t i = 0; i < maxTiles; i++) { _tiles[i].MaskDataOffset = block.ReadUInt32(); } // We don't care about the flipped masks, those are generated on runtime block.DiscardBytes(4 * maxTiles); } void JJ2Tileset::LoadImageData(JJ2Block& imageBlock, JJ2Block& alphaBlock) { std::uint8_t imageBuffer[BlockSize * BlockSize * 4]; std::uint8_t maskBuffer[BlockSize * BlockSize / 8]; std::int32_t maxTiles = GetMaxSupportedTiles(); for (std::int32_t i = 0; i < maxTiles; i++) { auto& tile = _tiles[i]; imageBlock.SeekTo(tile.ImageDataOffset & ~Is32bitTile); if (tile.ImageDataOffset & Is32bitTile) { // 32-bit image imageBlock.ReadRawBytes(imageBuffer, BlockSize * BlockSize * 4); } else { // 8-bit palette image imageBlock.ReadRawBytes(imageBuffer, BlockSize * BlockSize); } alphaBlock.SeekTo(tile.AlphaDataOffset); alphaBlock.ReadRawBytes(maskBuffer, sizeof(maskBuffer)); if (tile.ImageDataOffset & Is32bitTile) { std::memcpy(tile.Image, imageBuffer, BlockSize * BlockSize * 4); } else { for (std::int32_t j = 0; j < (BlockSize * BlockSize); j++) { std::uint8_t idx = imageBuffer[j]; if (((maskBuffer[j / 8] >> (j % 8)) & 0x01) == 0x00) { // Empty mask idx = 0; } tile.Image[j] = idx; } } } } void JJ2Tileset::LoadMaskData(JJ2Block& block) { //std::uint8_t maskBuffer[BlockSize * BlockSize / 8]; std::int32_t maxTiles = GetMaxSupportedTiles(); for (std::int32_t i = 0; i < maxTiles; i++) { auto& tile = _tiles[i]; block.SeekTo(tile.MaskDataOffset); /*block.ReadRawBytes(maskBuffer, sizeof(maskBuffer)); for (std::int32_t j = 0; j < 128; j++) { std::uint8_t idx = maskBuffer[j]; for (std::int32_t k = 0; k < 8; k++) { std::int32_t pixelIdx = 8 * j + k; std::int32_t x = pixelIdx % BlockSize; std::int32_t y = pixelIdx / BlockSize; if (((idx >> k) & 0x01) == 0) { tile.Mask[y * BlockSize + x] = 0; } else { tile.Mask[y * BlockSize + x] = 1; } } }*/ block.ReadRawBytes(tile.Mask, sizeof(tile.Mask)); } // Try to fix some known bugs in tilesets if (_name == "Castle 1"_s || _name == "Castle 1 Night"_s) { LOGI("Applying \"{}\" tileset mask fix", _name); // Spikes with empty mask auto& spikesWithEmptyMask = _tiles[189]; auto& spikesWithCorrectMask = _tiles[184]; std::memcpy(spikesWithEmptyMask.Mask, spikesWithCorrectMask.Mask, sizeof(spikesWithEmptyMask.Mask)); } else if (_name == "Inferno Night"_s) { LOGI("Applying \"{}\" tileset mask fix", _name); // Solid tiles with empty mask static const std::int32_t SolidTilesWithEmptyMask[] = { 142, 143, 146, 152, 153, 156 }; for (std::int32_t tileIdx : SolidTilesWithEmptyMask) { std::memset(_tiles[tileIdx].Mask, 0xFF, sizeof(_tiles[tileIdx].Mask)); } } else if (_name == "Town House 1"_s || _name == "Town House 2"_s) { LOGI("Applying \"{}\" tileset mask fix", _name); // Vine/chain with empty mask for (std::int32_t tileIdx = 826; tileIdx <= 829; tileIdx++) { auto& mask = _tiles[tileIdx].Mask; if (std::all_of(mask, mask + sizeof(mask), [](std::uint8_t p) { return p == 0; })) { for (std::int32_t y = 6; y < 6 + 3; y++) { for (std::int32_t x = 0; x < BlockSize / 8; x++) { mask[y * (BlockSize / 8) + x] = 0xFF; } } } } } } void JJ2Tileset::Convert(StringView targetPath) const { // Rearrange tiles from '10 tiles per row' to '30 tiles per row' constexpr std::int32_t TilesPerRow = 30; std::int32_t tileCount = _tileCount; std::int32_t width = TilesPerRow * BlockSize; std::int32_t height = ((tileCount - 1) / TilesPerRow + 1) * BlockSize; auto so = fs::Open(targetPath, FileAccess::Write); DEATH_ASSERT(so->IsValid(), "Cannot open file for writing", ); constexpr std::uint8_t flags = 0x20 | 0x40; // Mask and palette included so->WriteValueAsLE(0xB8EF8498E2BFBBEF); so->WriteValueAsLE(0x208F); so->WriteValue(2); // Version 2 is reserved for sprites (or bigger images) so->WriteValue(flags); // Flags // TODO: Use single channel instead so->WriteValue(4); so->WriteValueAsLE(width); so->WriteValueAsLE(height); so->WriteValueAsLE(tileCount); MemoryStream ms(1024 * 1024); { DeflateWriter co(ms); // Palette std::uint32_t palette[PaletteSize]; std::memcpy(palette, _palette, sizeof(_palette)); bool hasAlphaChannel = false; for (std::int32_t i = 1; i < std::int32_t(arraySize(palette)); i++) { if ((palette[i] & 0xff000000) != 0) { hasAlphaChannel = true; break; } } if (!hasAlphaChannel) { for (std::int32_t i = 1; i < std::int32_t(arraySize(palette)); i++) { palette[i] |= 0xff000000; } } // The first palette entry is fixed to transparent black palette[0] = 0x00000000; for (std::size_t i = 0; i < arraySize(palette); i++) { co.WriteValueAsLE(palette[i]); } // Mark individual tiles as 32-bit or 8-bit for (std::int32_t i = 0; i < (tileCount + 7) / 8; i++) { std::uint32_t is32bitAggregated = 0; std::int32_t tilesPerByte = std::min(8, tileCount - i * 8); for (std::int32_t j = 0; j < tilesPerByte; j++) { const auto& tile = _tiles[i * 8 + j]; if (tile.ImageDataOffset & Is32bitTile) { is32bitAggregated |= (1 << j); } } co.WriteValue(is32bitAggregated); } // Mask co.WriteValueAsLE(tileCount * sizeof(_tiles[0].Mask)); for (std::int32_t i = 0; i < tileCount; i++) { const auto& tile = _tiles[i]; co.Write(tile.Mask, sizeof(tile.Mask)); } } so->WriteValueAsLE(std::int32_t(ms.GetSize())); so->Write(ms.GetBuffer(), ms.GetSize()); // Diffuse std::unique_ptr pixels = std::make_unique(width * height * 4); for (std::int32_t i = 0; i < tileCount; i++) { const auto& tile = _tiles[i]; std::int32_t ox = (i % TilesPerRow) * BlockSize; std::int32_t oy = (i / TilesPerRow) * BlockSize; if (tile.ImageDataOffset & Is32bitTile) { for (std::int32_t y = 0; y < BlockSize; y++) { for (std::int32_t x = 0; x < BlockSize; x++) { const auto* src = &tile.Image[(y * BlockSize + x) * 4]; std::int32_t pixelIdx = (width * (oy + y) + ox + x) * 4; pixels[pixelIdx] = src[0]; pixels[pixelIdx + 1] = src[1]; pixels[pixelIdx + 2] = src[2]; pixels[pixelIdx + 3] = src[3]; } } } else { for (std::int32_t y = 0; y < BlockSize; y++) { for (std::int32_t x = 0; x < BlockSize; x++) { const auto& src = tile.Image[y * BlockSize + x]; std::int32_t pixelIdx = (width * (oy + y) + ox + x) * 4; pixels[pixelIdx] = src; pixels[pixelIdx + 1] = src; pixels[pixelIdx + 2] = src; pixels[pixelIdx + 3] = (src == 0 ? 0 : 255); } } } } // TODO: Use single channel for 8-bit palette tiles instead JJ2Anims::WriteImageContent(*so, pixels.get(), width, height, 4); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Tileset.h000066400000000000000000000030461512772601700264370ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "JJ2Block.h" #include "JJ2Version.h" #include #include #include using namespace Death::Containers; namespace Jazz2::Compatibility { /** @brief Parses original `.j2t` tile set files */ class JJ2Tileset { public: /** @{ @name Constants */ /** @brief Size of a tile */ static constexpr std::int32_t BlockSize = 32; /** @} */ JJ2Tileset() : _version(JJ2Version::Unknown), _tileCount(0) { } bool Open(StringView path, bool strictParser); void Convert(StringView targetPath) const; /** @brief Returns maximum number of supported tiles */ std::int32_t GetMaxSupportedTiles() const { return (_version == JJ2Version::BaseGame ? 1024 : 4096); } private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct TilesetTileSection { bool Opaque; std::uint32_t ImageDataOffset; std::uint32_t AlphaDataOffset; std::uint32_t MaskDataOffset; std::uint8_t Image[BlockSize * BlockSize * 4]; std::uint8_t Mask[BlockSize * BlockSize / 8]; }; #endif static constexpr std::int32_t PaletteSize = 256; static constexpr std::uint32_t Is32bitTile = 0x80000000u; String _name; JJ2Version _version; std::uint32_t _palette[PaletteSize]; std::unique_ptr _tiles; std::int32_t _tileCount; void LoadMetadata(JJ2Block& block); void LoadImageData(JJ2Block& imageBlock, JJ2Block& alphaBlock); void LoadMaskData(JJ2Block& block); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Compatibility/JJ2Version.h000066400000000000000000000006301512772601700264470ustar00rootroot00000000000000#pragma once #include "../../Main.h" namespace Jazz2::Compatibility { /** @brief Version of the original game, supports a bitwise combination of its member values */ enum class JJ2Version : std::uint16_t { Unknown = 0x0000, BaseGame = 0x0001, TSF = 0x0002, HH = 0x0004, CC = 0x0008, PlusExtension = 0x0100, SharewareDemo = 0x0200, All = 0xffff }; DEATH_ENUM_FLAGS(JJ2Version); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/ContentResolver.Shaders.h000066400000000000000000002362141512772601700264400ustar00rootroot00000000000000#pragma once #ifndef DOXYGEN_GENERATING_OUTPUT namespace Jazz2::Shaders { constexpr std::uint64_t Version = 8; constexpr char LightingVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec4 vTexCoords; out vec4 vColor; void main() { vec2 aPosition = vec2(0.5 - float(gl_VertexID >> 1), 0.5 - float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vTexCoords = texRect; vColor = vec4(color.x, color.y, aPosition.x * 2.0, aPosition.y * 2.0); } )"; constexpr char BatchedLightingVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; struct Instance { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; layout (std140) uniform InstancesBlock { #ifndef BATCH_SIZE #define BATCH_SIZE (585) // 64 Kb / 112 b #endif Instance[BATCH_SIZE] instances; } block; out vec4 vTexCoords; out vec4 vColor; #define i block.instances[gl_VertexID / 6] void main() { vec2 aPosition = vec2(-0.5 + float(((gl_VertexID + 2) / 3) % 2), -0.5 + float(((gl_VertexID + 1) / 3) % 2)); vec4 position = vec4(aPosition.x * i.spriteSize.x, aPosition.y * i.spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * i.modelMatrix * position; vTexCoords = i.texRect; vColor = vec4(i.color.x, i.color.y, aPosition.x * 2.0, aPosition.y * 2.0); } )"; constexpr char LightingFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec4 vTexCoords; in vec4 vColor; out vec4 fragColor; float lightBlend(float t) { return t * t * t; } void main() { vec2 center = vTexCoords.xy; float radiusNear = vTexCoords.z; float intensity = vColor.r; float brightness = vColor.g; float dist = length(vColor.zw); if (dist > 1.0) { fragColor = vec4(0.0, 0.0, 0.0, 1.0); return; } // TODO: Normal maps /*vec4 clrNormal = texture(uTexture, vec2(gl_FragCoord) / ViewSize); vec3 normal = normalize(clrNormal.xyz - vec3(0.5, 0.5, 0.5)); normal.z = -normal.z; vec3 lightDir = vec3((center.x - gl_FragCoord.x), (center.y - gl_FragCoord.y), 0); // Diffuse lighting float diffuseFactor = 1.0 - max(dot(normal, normalize(lightDir)), 0.0); diffuseFactor = diffuseFactor * 0.8 + 0.2;*/ float diffuseFactor = 1.0f; float strength = diffuseFactor * lightBlend(clamp(1.0 - ((dist - radiusNear) / (1.0 - radiusNear)), 0.0, 1.0)); fragColor = vec4(strength * intensity, strength * brightness, 0.0, 1.0); } )"; constexpr char BlurFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; uniform vec2 uPixelOffset; uniform vec2 uDirection; in vec2 vTexCoords; out vec4 fragColor; void main() { vec4 color = vec4(0.0); vec2 off1 = vec2(1.3846153846) * uPixelOffset * uDirection; vec2 off2 = vec2(3.2307692308) * uPixelOffset * uDirection; color += texture(uTexture, vTexCoords) * 0.2270270270; color += texture(uTexture, vTexCoords + off1) * 0.3162162162; color += texture(uTexture, vTexCoords - off1) * 0.3162162162; color += texture(uTexture, vTexCoords + off2) * 0.0702702703; color += texture(uTexture, vTexCoords - off2) * 0.0702702703; fragColor = color; } )"; constexpr char DownsampleFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; uniform vec2 uPixelOffset; in vec2 vTexCoords; out vec4 fragColor; void main() { vec4 color = texture(uTexture, vTexCoords); color += texture(uTexture, vTexCoords + vec2(0.0, uPixelOffset.y)); color += texture(uTexture, vTexCoords + vec2(uPixelOffset.x, 0.0)); color += texture(uTexture, vTexCoords + uPixelOffset); fragColor = vec4(0.25) * color; } )"; constexpr char CombineVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vTexCoords; out vec2 vViewSize; out vec2 vViewSizeInv; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vTexCoords = vec2(aPosition.x * texRect.x + texRect.y, aPosition.y * texRect.z + texRect.w); vViewSize = spriteSize; vViewSizeInv = vec2(1.0) / spriteSize; } )"; constexpr char CombineFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; uniform sampler2D uTextureLighting; uniform sampler2D uTextureBlurHalf; uniform sampler2D uTextureBlurQuarter; uniform vec4 uAmbientColor; uniform float uTime; in vec2 vTexCoords; in vec2 vViewSize; in vec2 vViewSizeInv; out vec4 fragColor; vec2 hash2D(in vec2 p) { float h = dot(p, vec2(12.9898, 78.233)); float h2 = dot(p, vec2(37.271, 377.632)); return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453)); } vec2 noiseTexCoords(vec2 position) { vec2 seed = position + fract(uTime * 0.01); return clamp(position + hash2D(seed) * vViewSizeInv * 1.4, vec2(0.0), vec2(1.0)); } void main() { vec4 blur1 = texture(uTextureBlurHalf, vTexCoords); vec4 blur2 = texture(uTextureBlurQuarter, vTexCoords); vec4 main = texture(uTexture, vTexCoords); vec4 light = texture(uTextureLighting, noiseTexCoords(vTexCoords)); vec4 blur = (blur1 + blur2) * vec4(0.5); float gray = dot(blur.rgb, vec3(0.299, 0.587, 0.114)); blur = vec4(gray, gray, gray, blur.a); fragColor = mix(mix( main * (1.0 + light.g) + max(light.g - 0.7, 0.0) * vec4(1.0), blur, vec4(clamp((1.0 - light.r) / sqrt(max(uAmbientColor.w, 0.35)), 0.0, 1.0)) ), uAmbientColor, vec4(1.0 - light.r)); fragColor.a = 1.0; } )"; constexpr char CombineWithWaterFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision highp float; #endif uniform sampler2D uTexture; uniform sampler2D uTextureLighting; uniform sampler2D uTextureBlurHalf; uniform sampler2D uTextureBlurQuarter; uniform sampler2D uTextureNoise; uniform vec4 uAmbientColor; uniform float uTime; uniform vec2 uCameraPos; uniform float uWaterLevel; in vec2 vTexCoords; in vec2 vViewSize; in vec2 vViewSizeInv; out vec4 fragColor; vec2 hash2D(in vec2 p) { float h = dot(p, vec2(12.9898, 78.233)); float h2 = dot(p, vec2(37.271, 377.632)); return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453)); } vec2 noiseTexCoords(vec2 position) { vec2 seed = position + fract(uTime * 0.01); return clamp(position + hash2D(seed) * vViewSizeInv * 1.4, vec2(0.0), vec2(1.0)); } float wave(float x, float time) { float waveOffset = cos((x - time) * 60.0) * 0.004 + cos((x - 2.0 * time) * 20.0) * 0.008 + sin((x + 2.0 * time) * 35.0) * 0.01 + cos((x + 4.0 * time) * 70.0) * 0.001; return waveOffset * 0.4; } float aastep(float threshold, float value) { float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; return smoothstep(threshold - afwidth, threshold + afwidth, value); } // Simplex Noise vec3 permute(vec3 x) { return mod(((x*34.0)+1.0)*x, 289.0); } float snoise(vec2 v) { const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); vec2 i = floor(v + dot(v, C.yy)); vec2 x0 = v - i + dot(i, C.xx); vec2 i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); vec4 x12 = x0.xyxy + C.xxzz; x12.xy -= i1; i = mod(i, 289.0); vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0 )); vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw, x12.zw)), 0.0); m = m * m; m = m * m; vec3 x = 2.0 * fract(p * C.www) - 1.0; vec3 h = abs(x) - 0.5; vec3 ox = floor(x + 0.5); vec3 a0 = x - ox; m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); vec3 g; g.x = a0.x * x0.x + h.x * x0.y; g.yz = a0.yz * x12.xz + h.yz * x12.yw; return 130.0 * dot(m, g); } void main() { vec3 waterColor = vec3(0.4, 0.6, 0.8); vec2 uvLocal = vTexCoords; vec2 uvWorldCenter = (uCameraPos.xy * vViewSizeInv.xy); vec2 uvWorld = uvLocal + uvWorldCenter; float waveHeight = wave(uvWorld.x, uTime); float isTexelBelow = aastep(waveHeight, uvLocal.y - uWaterLevel); float isTexelAbove = 1.0 - isTexelBelow; // Displacement vec2 disPos = uvWorld * vec2(0.1) + vec2(mod(uTime * 0.4, 2.0)); vec2 dis = (texture(uTextureNoise, disPos).xy - vec2(0.5)) * vec2(0.01); vec2 uv = clamp(uvLocal + (vec2(0.004 * sin(uTime * 16.0 + uvWorld.y * 20.0), 0.0) + dis) * vec2(isTexelBelow), vec2(0.0), vec2(1.0)); vec4 main = texture(uTexture, uv); // Chromatic Aberration float aberration = abs(uvLocal.x - 0.5) * 0.012; float red = texture(uTexture, vec2(uv.x - aberration, uv.y)).r; float blue = texture(uTexture, vec2(uv.x + aberration, uv.y)).b; main.rgb = mix(main.rgb, waterColor * (0.4 + 1.2 * vec3(red, main.g, blue)), vec3(isTexelBelow * 0.5)); // Rays vec2 uvNormalized = mod(uvLocal / vViewSizeInv, 720.0) / 720.0; float noisePos = uvWorldCenter.x * 6.0 + uvLocal.x * 1.4 + uvWorldCenter.y * 0.5 + (1.0 - uvNormalized.x * 1.2 - uvNormalized.y) * -5.0; float rays = snoise(vec2(noisePos, uTime * 5.0 + uvWorldCenter.y)) * 0.55 + 0.3; main.rgb += vec3(rays * isTexelBelow * max(1.0 - uvLocal.y * 1.4, 0.0) * 0.6); // Waves float topDist = abs(uvLocal.y - uWaterLevel - waveHeight); float isNearTop = 1.0 - aastep(vViewSizeInv.y * 2.8, topDist); float isVeryNearTop = 1.0 - aastep(vViewSizeInv.y * (0.8 - 100.0 * waveHeight), topDist); float topColorBlendFac = isNearTop * isTexelBelow * 0.6; main.rgb = mix(main.rgb, texture(uTexture, vec2(uvLocal.x, (uWaterLevel - uvLocal.y + uWaterLevel) * 0.97 - waveHeight + vViewSizeInv.y )).rgb, vec3(topColorBlendFac)); main.rgb += vec3(0.2 * isVeryNearTop); // Lighting vec4 blur1 = texture(uTextureBlurHalf, uv); vec4 blur2 = texture(uTextureBlurQuarter, uv); vec4 light = texture(uTextureLighting, noiseTexCoords(uv)); vec4 blur = (blur1 + blur2) * vec4(0.5); float gray = dot(blur.rgb, vec3(0.299, 0.587, 0.114)); blur = vec4(gray, gray, gray, blur.a); float darknessStrength = (1.0 - light.r); // Darkness above water if (uWaterLevel < 0.4) { float aboveWaterDarkness = isTexelAbove * (0.4 - uWaterLevel); darknessStrength = min(1.0, darknessStrength + aboveWaterDarkness); } fragColor = mix(mix( main * (1.0 + light.g) + max(light.g - 0.7, 0.0) * vec4(1.0), blur, vec4(clamp((1.0 - light.r) / sqrt(max(uAmbientColor.w, 0.35)), 0.0, 1.0)) ), uAmbientColor, vec4(darknessStrength)); fragColor.a = 1.0; } )"; constexpr char CombineWithWaterLowFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; uniform sampler2D uTextureLighting; uniform sampler2D uTextureBlurHalf; uniform sampler2D uTextureBlurQuarter; uniform vec4 uAmbientColor; uniform float uTime; uniform vec2 uCameraPos; uniform float uWaterLevel; in vec2 vTexCoords; in vec2 vViewSize; in vec2 vViewSizeInv; out vec4 fragColor; vec2 hash2D(in vec2 p) { float h = dot(p, vec2(12.9898, 78.233)); float h2 = dot(p, vec2(37.271, 377.632)); return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453)); } vec2 noiseTexCoords(vec2 position) { vec2 seed = position + fract(uTime * 0.01); return clamp(position + hash2D(seed) * vViewSizeInv * 1.4, vec2(0.0), vec2(1.0)); } void main() { vec3 waterColor = vec3(0.4, 0.6, 0.8); vec2 uvLocal = vTexCoords; vec2 uvWorldCenter = (uCameraPos.xy * vViewSizeInv.xy); vec2 uvWorld = uvLocal + uvWorldCenter; float isTexelBelow = 1.0 - step(uvLocal.y, uWaterLevel); float isTexelAbove = 1.0 - isTexelBelow; vec2 uv = clamp(uvLocal + vec2(0.008 * sin(uTime * 16.0 + uvWorld.y * 20.0) * isTexelBelow, 0.0), vec2(0.0), vec2(1.0)); vec4 main = texture(uTexture, uv); // Waves float topDist = abs(uvLocal.y - uWaterLevel); float topGradient = max(1.0 - topDist, 0.0); float isNearTop = 0.2 * topGradient * topGradient; float isVeryNearTop = 1.0 - step(vViewSizeInv.y, topDist); main.rgb = mix(main.rgb, waterColor, vec3(isTexelBelow * 0.4)) + vec3((isNearTop + 0.2 * isVeryNearTop) * isTexelBelow); // Lighting vec4 blur1 = texture(uTextureBlurHalf, uv); vec4 blur2 = texture(uTextureBlurQuarter, uv); vec4 light = texture(uTextureLighting, noiseTexCoords(uv)); vec4 blur = (blur1 + blur2) * vec4(0.5); float gray = dot(blur.rgb, vec3(0.299, 0.587, 0.114)); blur = vec4(gray, gray, gray, blur.a); float darknessStrength = (1.0 - light.r); // Darkness above water if (uWaterLevel < 0.4) { float aboveWaterDarkness = isTexelAbove * (0.4 - uWaterLevel); darknessStrength = min(1.0, darknessStrength + aboveWaterDarkness); } fragColor = mix(mix( main * (1.0 + light.g) + max(light.g - 0.7, 0.0) * vec4(1.0), blur, vec4(clamp((1.0 - light.r) / sqrt(max(uAmbientColor.w, 0.35)), 0.0, 1.0)) ), uAmbientColor, vec4(darknessStrength)); fragColor.a = 1.0; } )"; constexpr char TexturedBackgroundFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision highp float; #endif uniform sampler2D uTexture; uniform vec2 uViewSize; uniform vec2 uCameraPos; uniform vec4 uHorizonColor; uniform vec2 uShift; in vec2 vTexCoords; out vec4 fragColor; vec2 hash2D(in vec2 p) { float h = dot(p, vec2(12.9898, 78.233)); float h2 = dot(p, vec2(37.271, 377.632)); return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453)); } vec3 voronoi(in vec2 p) { vec2 n = floor(p); vec2 f = fract(p); vec2 mg, mr; float md = 8.0; for (int j = -1; j <= 1; ++j) { for (int i = -1; i <= 1; ++i) { vec2 g = vec2(float(i), float(j)); vec2 o = hash2D(n + g); vec2 r = g + o - f; float d = dot(r, r); if (d < md) { md = d; mr = r; mg = g; } } } return vec3(md, mr); } float addStarField(vec2 samplePosition, float threshold) { vec3 starValue = voronoi(samplePosition); if (starValue.x < threshold) { float power = 1.0 - (starValue.x / threshold); return min(power * power * power, 0.5); } return 0.0; } void main() { // Distance to center of screen from top or bottom (1: center of screen, 0: edge of screen) float distance = 1.3 - abs(2.0 * vTexCoords.y - 1.0); float horizonDepth = pow(distance, 1.4); float yShift = (vTexCoords.y > 0.5 ? 1.0 : 0.0); float correction = ((uViewSize.x * 9.0) / (uViewSize.y * 16.0)); vec2 texturePos = vec2( (uShift.x / 256.0) + (vTexCoords.x - 0.5 ) * (0.5 + (1.5 * horizonDepth)) * correction, (uShift.y / 256.0) + (vTexCoords.y - yShift) * 1.4 * distance ); vec4 texColor = texture(uTexture, texturePos); #ifdef DITHER texturePos += hash2D(vTexCoords * uViewSize + (uCameraPos + uShift) * 0.001).xy * 8.0 / uViewSize; texColor = mix(texColor, texture(uTexture, texturePos), 0.333); #endif float horizonOpacity = clamp(pow(distance, 1.5) - 0.3, 0.0, 1.0); vec4 horizonColorWithStars = vec4(uHorizonColor.xyz, 1.0); if (uHorizonColor.w > 0.0) { vec2 samplePosition = (vTexCoords * uViewSize / uViewSize.xx) + uCameraPos.xy * 0.00012; horizonColorWithStars += vec4(addStarField(samplePosition * 7.0, 0.00008)); samplePosition = (vTexCoords * uViewSize / uViewSize.xx) + uCameraPos.xy * 0.00018 + 0.5; horizonColorWithStars += vec4(addStarField(samplePosition * 7.0, 0.00008)); } fragColor = mix(texColor, horizonColorWithStars, horizonOpacity); fragColor.a = 1.0; } )"; constexpr char TexturedBackgroundCircleFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision highp float; #endif uniform sampler2D uTexture; uniform vec2 uViewSize; uniform vec2 uCameraPos; uniform vec4 uHorizonColor; uniform vec2 uShift; in vec2 vTexCoords; out vec4 fragColor; #define INV_PI 0.31830988618379067153776752675 vec2 hash2D(in vec2 p) { float h = dot(p, vec2(12.9898, 78.233)); float h2 = dot(p, vec2(37.271, 377.632)); return -1.0 + 2.0 * vec2(fract(sin(h) * 43758.5453), fract(sin(h2) * 43758.5453)); } vec3 voronoi(in vec2 p) { vec2 n = floor(p); vec2 f = fract(p); vec2 mg, mr; float md = 8.0; for (int j = -1; j <= 1; ++j) { for (int i = -1; i <= 1; ++i) { vec2 g = vec2(float(i), float(j)); vec2 o = hash2D(n + g); vec2 r = g + o - f; float d = dot(r, r); if (d < md) { md = d; mr = r; mg = g; } } } return vec3(md, mr); } float addStarField(vec2 samplePosition, float threshold) { vec3 starValue = voronoi(samplePosition); if (starValue.x < threshold) { float power = 1.0 - (starValue.x / threshold); return min(power * power * power, 0.5); } return 0.0; } void main() { // Position of pixel on screen (between -1 and 1) vec2 targetCoord = vec2(2.0) * vTexCoords - vec2(1.0); // Aspect ratio correction, so display circle instead of ellipse targetCoord.x *= uViewSize.x / uViewSize.y; // Distance to center of screen float distance = length(targetCoord); // x-coordinate of tunnel float xShift = (targetCoord.x == 0.0 ? sign(targetCoord.y) * 0.5 : atan(targetCoord.y, targetCoord.x) * INV_PI); vec2 texturePos = vec2( (xShift) * 1.0 + (uShift.x * 0.01), (1.0 / distance) * 1.4 + (uShift.y * 0.002) ); vec4 texColor = texture(uTexture, texturePos); #ifdef DITHER texturePos += hash2D(vTexCoords * uViewSize + (uCameraPos + uShift) * 0.001).xy * 8.0 / uViewSize; texColor = mix(texColor, texture(uTexture, texturePos), 0.333); #endif float horizonOpacity = 1.0 - clamp(pow(distance, 1.4) - 0.3, 0.0, 1.0); vec4 horizonColorWithStars = vec4(uHorizonColor.xyz, 1.0); if (uHorizonColor.w > 0.0) { vec2 samplePosition = (vTexCoords * uViewSize / uViewSize.xx) + uCameraPos.xy * 0.00012; horizonColorWithStars += vec4(addStarField(samplePosition * 7.0, 0.00008)); samplePosition = (vTexCoords * uViewSize / uViewSize.xx) + uCameraPos.xy * 0.00018 + 0.5; horizonColorWithStars += vec4(addStarField(samplePosition * 7.0, 0.00008)); } fragColor = mix(texColor, horizonColorWithStars, horizonOpacity); fragColor.a = 1.0; } )"; constexpr char ColorizedFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexCoords; in vec4 vColor; out vec4 fragColor; void main() { vec4 dye = vec4(1.0) + (vColor - vec4(0.5)) * vec4(4.0); vec4 original = texture(uTexture, vTexCoords); float average = (original.r + original.g + original.b) * 0.5; vec4 gray = vec4(average, average, average, original.a); fragColor = gray * dye; } )"; constexpr char TintedFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexCoords; in vec4 vColor; out vec4 fragColor; void main() { vec4 original = texture(uTexture, vTexCoords); vec3 tinted = mix(original.rgb, vColor.rgb, 0.45); fragColor = vec4(tinted.r, tinted.g, tinted.b, original.a * vColor.a); } )"; // Subtle shadow has been added in v2.5.0, previous implementation was: // fragColor = mix(color, vec4(vColor.z, vColor.z, vColor.z, vColor.w), outline - color.a); constexpr char OutlineFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexCoords; in vec4 vColor; out vec4 fragColor; float aastep(float threshold, float value) { float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; return smoothstep(threshold - afwidth, threshold + afwidth, value); } void main() { vec2 size = vColor.xy; float outline = texture(uTexture, vTexCoords + vec2(-size.x, 0)).a; outline += texture(uTexture, vTexCoords + vec2(0, size.y)).a; outline += texture(uTexture, vTexCoords + vec2(size.x, 0)).a; outline += texture(uTexture, vTexCoords + vec2(0, -size.y)).a; outline += texture(uTexture, vTexCoords + vec2(-size.x, size.y)).a; outline += texture(uTexture, vTexCoords + vec2(size.x, size.y)).a; outline += texture(uTexture, vTexCoords + vec2(-size.x, -size.y)).a; outline += texture(uTexture, vTexCoords + vec2(size.x, -size.y)).a; outline = aastep(1.0, outline); float outline2 = texture(uTexture, vTexCoords + vec2(-2.0 * size.x, 0)).a; outline2 += texture(uTexture, vTexCoords + vec2(0, 2.0 * size.y)).a; outline2 += texture(uTexture, vTexCoords + vec2(2.0 * size.x, 0)).a; outline2 += texture(uTexture, vTexCoords + vec2(0, -2.0 * size.y)).a; outline2 += texture(uTexture, vTexCoords + vec2(-2.0 * size.x, 2.0 * size.y)).a; outline2 += texture(uTexture, vTexCoords + vec2(2.0 * size.x, 2.0 * size.y)).a; outline2 += texture(uTexture, vTexCoords + vec2(-2.0 * size.x, -2.0 * size.y)).a; outline2 += texture(uTexture, vTexCoords + vec2(2.0 * size.x, -2.0 * size.y)).a; outline2 = aastep(1.0, outline2); vec4 color = texture(uTexture, vTexCoords); fragColor = mix(color, mix(vec4(0.0, 0.0, 0.0, vColor.w * 0.5), vec4(vColor.z, vColor.z, vColor.z, vColor.w), outline), max(outline, outline2) - color.a); } )"; constexpr char WhiteMaskFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexCoords; in vec4 vColor; out vec4 fragColor; void main() { vec4 tex = texture(uTexture, vTexCoords); float color = min((0.299 * tex.r + 0.587 * tex.g + 0.114 * tex.b) * 6.0f, 1.0f); fragColor = vec4(color, color, color, tex.a) * vColor; } )"; constexpr char PartialWhiteMaskFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexCoords; in vec4 vColor; out vec4 fragColor; void main() { vec4 tex = texture(uTexture, vTexCoords); float color = min((0.299 * tex.r + 0.587 * tex.g + 0.114 * tex.b) * 2.5f, 1.0f); fragColor = vec4(color, color, color, tex.a) * vColor; } )"; constexpr char FrozenMaskFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexCoords; in vec4 vColor; out vec4 fragColor; float aastep(float threshold, float value) { float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; return smoothstep(threshold - afwidth, threshold + afwidth, value); } void main() { vec2 size = vColor.xy * vColor.a * 2.0; vec4 tex = texture(uTexture, vTexCoords); vec4 tex1 = texture(uTexture, vTexCoords + vec2(-size.x, 0)); vec4 tex2 = texture(uTexture, vTexCoords + vec2(0, size.y)); vec4 tex3 = texture(uTexture, vTexCoords + vec2(size.x, 0)); vec4 tex4 = texture(uTexture, vTexCoords + vec2(0, -size.y)); float outline = tex1.a; outline += tex2.a; outline += tex3.a; outline += tex4.a; outline += texture(uTexture, vTexCoords + vec2(-size.x, size.y)).a; outline += texture(uTexture, vTexCoords + vec2(size.x, size.y)).a; outline += texture(uTexture, vTexCoords + vec2(-size.x, -size.y)).a; outline += texture(uTexture, vTexCoords + vec2(size.x, -size.y)).a; outline = aastep(1.0, outline); vec4 color = (tex + tex + tex1 + tex2 + tex3 + tex4) / 6.0; float grey = min((0.299 * color.r + 0.587 * color.g + 0.114 * color.b) * 2.6f, 1.0f); fragColor = mix(tex, vec4(0.2 * grey, 0.2 + grey * 0.62, 0.6 + 0.2 * grey, outline * 0.95), vColor.a); } )"; constexpr char ShieldVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec4 vTexCoords; out vec4 vColor; out vec2 vPos; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vTexCoords = texRect; vColor = color; vPos = aPosition * vec2(2.0) - vec2(1.0); } )"; constexpr char BatchedShieldVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; struct Instance { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; layout (std140) uniform InstancesBlock { #ifndef BATCH_SIZE #define BATCH_SIZE (585) // 64 Kb / 112 b #endif Instance[BATCH_SIZE] instances; } block; out vec4 vTexCoords; out vec4 vColor; out vec2 vPos; #define i block.instances[gl_VertexID / 6] void main() { vec2 aPosition = vec2(1.0 - float(((gl_VertexID + 2) / 3) % 2), 1.0 - float(((gl_VertexID + 1) / 3) % 2)); vec4 position = vec4(aPosition.x * i.spriteSize.x, aPosition.y * i.spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * i.modelMatrix * position; vTexCoords = i.texRect; vColor = i.color; vPos = aPosition * vec2(2.0) - vec2(1.0); } )"; constexpr char ShieldFireFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec4 vTexCoords; in vec4 vColor; in vec2 vPos; out vec4 fragColor; #define PI 3.1415926 float aastep(float threshold, float value) { float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; return smoothstep(threshold - afwidth, threshold + afwidth, value); } float triangleWave(float x, float period) { float p = x / period; float f = fract(p); return abs(f - 0.5) * 2.0; } void main() { vec2 scale = vColor.xy; vec2 shift1 = vTexCoords.xy; vec2 shift2 = vTexCoords.zw; float darkness = vColor.z; float alpha = vColor.w; float dist = length(vPos); if (dist > 1.0) { fragColor = vec4(0.0, 0.0, 0.0, 0.0); return; } vec3 v = vec3(vPos.x, vPos.y, sqrt(1.0 - vPos.x * vPos.x - vPos.y * vPos.y)); vec3 n = normalize(v); float b = dot(n, vec3(0.0, 0.0, 1.0)); vec2 q = vec2(0.5 - 0.5 * atan(n.z, n.x) / PI, -acos(vPos.y) / PI); float isNearBorder = 1.0 - aastep(0.96, dist); float mask1 = texture(uTexture, mod(shift1 + (q * scale), 1.0)).r; float maskNormalized1 = max(1.0 - (abs(triangleWave(shift2.y + 0.5, 2.0) - mask1) * 6.0), 0.0); float mask2 = texture(uTexture, mod(shift2 + (q * scale), 1.0)).r; float maskNormalized2 = max(1.0 - (abs(triangleWave(shift1.x, 1.333) - mask2) * 6.0), 0.0); float maskSum = min(maskNormalized1 + maskNormalized2, 1.0); fragColor = vec4(mix(vec3(1.0, 0.3, 0.0), vec3(1.0, 1.0, 1.0), max((maskSum * 2.0) - 1.0, 0.0)) * darkness, min(maskSum * b * isNearBorder * 1.3 + 0.1, 1.0) * alpha); } )"; constexpr char ShieldLightningFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec4 vTexCoords; in vec4 vColor; in vec2 vPos; out vec4 fragColor; #define PI 3.1415926 float aastep(float threshold, float value) { float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757; return smoothstep(threshold - afwidth, threshold + afwidth, value); } float triangleWave(float x, float period) { float p = x / period; float f = fract(p); return abs(f - 0.5) * 2.0; } void main() { vec2 scale = vColor.xy; vec2 shift1 = vTexCoords.xy; vec2 shift2 = vTexCoords.zw; float darkness = vColor.z; float alpha = vColor.w; float dist = length(vPos); if (dist > 1.0) { fragColor = vec4(0.0, 0.0, 0.0, 0.0); return; } vec3 v = vec3(vPos.x, vPos.y, sqrt(1.0 - vPos.x * vPos.x - vPos.y * vPos.y)); vec3 n = normalize(v); float b = dot(n, vec3(0.0, 0.0, 1.0)); vec2 q = vec2(0.5 - 0.5 * atan(n.z, n.x) / PI, -acos(vPos.y) / PI); float isNearBorder = 1.0 - aastep(0.96, dist); float mask = texture(uTexture, mod(shift1 + (q * scale), 1.0)).r; float maskNormalized = max(1.0 - (abs(triangleWave(shift2.y + 0.5, 2.0) - mask) * 8.0), 0.0); float isVeryNearBorder = 1.0 - aastep(0.024, abs(dist - 0.94)); float maskSum = max(maskNormalized, isVeryNearBorder); fragColor = vec4(mix(vec3(0.1, 1.0, 0.0), vec3(1.0, 1.0, 1.0), max((maskSum * 2.0) - 1.0, 0.0)) * darkness, min(maskSum * b * isNearBorder * 1.3 + 0.1, 1.0) * alpha); } )"; constexpr char ResizeHQ2xVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vTexCoords0; out vec4 vTexCoords1; out vec4 vTexCoords2; out vec4 vTexCoords3; out vec4 vTexCoords4; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; float x = 0.5 / texRect.x; float y = 0.5 / texRect.y; vec2 dg1 = vec2( x, y); vec2 dg2 = vec2(-x, y); vec2 dx = vec2(x, 0.0); vec2 dy = vec2(0.0, y); vTexCoords0.xy = aPosition; vTexCoords1.xy = aPosition - dg1; vTexCoords1.zw = aPosition - dy; vTexCoords2.xy = aPosition - dg2; vTexCoords2.zw = aPosition + dx; vTexCoords3.xy = aPosition + dg1; vTexCoords3.zw = aPosition + dy; vTexCoords4.xy = aPosition + dg2; vTexCoords4.zw = aPosition - dx; } )"; constexpr char ResizeHQ2xFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif const float mx = 0.325; const float k = -0.250; const float max_w = 0.25; const float min_w = -0.05; const float lum_add = 0.25; uniform sampler2D uTexture; in vec2 vTexCoords0; in vec4 vTexCoords1; in vec4 vTexCoords2; in vec4 vTexCoords3; in vec4 vTexCoords4; out vec4 fragColor; void main() { vec3 c00 = texture(uTexture, vTexCoords1.xy).xyz; vec3 c10 = texture(uTexture, vTexCoords1.zw).xyz; vec3 c20 = texture(uTexture, vTexCoords2.xy).xyz; vec3 c01 = texture(uTexture, vTexCoords4.zw).xyz; vec3 c11 = texture(uTexture, vTexCoords0.xy).xyz; vec3 c21 = texture(uTexture, vTexCoords2.zw).xyz; vec3 c02 = texture(uTexture, vTexCoords4.xy).xyz; vec3 c12 = texture(uTexture, vTexCoords3.zw).xyz; vec3 c22 = texture(uTexture, vTexCoords3.xy).xyz; vec3 dt = vec3(1.0, 1.0, 1.0); float md1 = dot(abs(c00 - c22), dt); float md2 = dot(abs(c02 - c20), dt); highp float w1 = dot(abs(c22 - c11), dt) * md2; highp float w2 = dot(abs(c02 - c11), dt) * md1; highp float w3 = dot(abs(c00 - c11), dt) * md2; highp float w4 = dot(abs(c20 - c11), dt) * md1; highp float t1 = w1 + w3; highp float t2 = w2 + w4; highp float ww = max(t1, t2) + 0.0001; highp vec3 cx = (w1 * c00 + w2 * c20 + w3 * c22 + w4 * c02 + ww * c11) / (t1 + t2 + ww); highp float lc1 = k / (0.12 * dot(c10 + c12 + cx, dt) + lum_add); highp float lc2 = k / (0.12 * dot(c01 + c21 + cx, dt) + lum_add); w1 = clamp(lc1 * dot(abs(cx - c10), dt) + mx, min_w, max_w); w2 = clamp(lc2 * dot(abs(cx - c21), dt) + mx, min_w, max_w); w3 = clamp(lc1 * dot(abs(cx - c12), dt) + mx, min_w, max_w); w4 = clamp(lc2 * dot(abs(cx - c01), dt) + mx, min_w, max_w); fragColor.xyz = w1 * c10 + w2 * c21 + w3 * c12 + w4 * c01 + (1.0 - w1 - w2 - w3 - w4) * cx; fragColor.w = 1.0; } )"; constexpr char Resize3xBrzVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vPixelCoords; out vec2 vTexCoords0; out vec4 vTexCoords1; out vec4 vTexCoords2; out vec4 vTexCoords3; out vec4 vTexCoords4; out vec4 vTexCoords5; out vec4 vTexCoords6; out vec4 vTexCoords7; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; float dx = 1.0 / texRect.x; float dy = 1.0 / texRect.y; vec2 texCoord = aPosition + vec2(0.0000001, 0.0000001); vPixelCoords = texCoord * texRect.xy; vTexCoords0 = texCoord; vTexCoords1 = texCoord.xxxy + vec4( -dx, 0, dx, -2.0*dy); // A1 B1 C1 vTexCoords2 = texCoord.xxxy + vec4( -dx, 0, dx, -dy); // A B C vTexCoords3 = texCoord.xxxy + vec4( -dx, 0, dx, 0); // D E F vTexCoords4 = texCoord.xxxy + vec4( -dx, 0, dx, dy); // G H I vTexCoords5 = texCoord.xxxy + vec4( -dx, 0, dx, 2.0*dy); // G5 H5 I5 vTexCoords6 = texCoord.xyyy + vec4(-2.0*dx, -dy, 0, dy); // A0 D0 G0 vTexCoords7 = texCoord.xyyy + vec4( 2.0*dx, -dy, 0, dy); // C4 F4 I4 } )"; constexpr char Resize3xBrzFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif #define BLEND_NONE 0 #define BLEND_NORMAL 1 #define BLEND_DOMINANT 2 #define LUMINANCE_WEIGHT 1.0 #define EQUAL_COLOR_TOLERANCE 30.0/255.0 #define STEEP_DIRECTION_THRESHOLD 2.2 #define DOMINANT_DIRECTION_THRESHOLD 3.6 const float one_third = 1.0 / 3.0; const float two_third = 2.0 / 3.0; uniform sampler2D uTexture; in vec2 vPixelCoords; in vec2 vTexCoords0; in vec4 vTexCoords1; in vec4 vTexCoords2; in vec4 vTexCoords3; in vec4 vTexCoords4; in vec4 vTexCoords5; in vec4 vTexCoords6; in vec4 vTexCoords7; out vec4 fragColor; float Reduce(vec3 color) { return dot(color, vec3(65536.0, 256.0, 1.0)); } float DistYCbCr(vec3 pixA, vec3 pixB) { const vec3 w = vec3(0.2627, 0.6780, 0.0593); const float scaleB = 0.5 / (1.0 - w.b); const float scaleR = 0.5 / (1.0 - w.r); vec3 diff = pixA - pixB; float Y = dot(diff, w); float Cb = scaleB * (diff.b - Y); float Cr = scaleR * (diff.r - Y); return sqrt( ((LUMINANCE_WEIGHT * Y) * (LUMINANCE_WEIGHT * Y)) + (Cb * Cb) + (Cr * Cr) ); } bool IsPixEqual(vec3 pixA, vec3 pixB) { return (DistYCbCr(pixA, pixB) < EQUAL_COLOR_TOLERANCE); } bool IsBlendingNeeded(ivec4 blend) { return any(notEqual(blend, ivec4(BLEND_NONE))); } void ScalePixel(ivec4 blend, vec3 k[9], inout vec3 dst[9]) { float v0 = Reduce(k[0]); float v4 = Reduce(k[4]); float v5 = Reduce(k[5]); float v7 = Reduce(k[7]); float v8 = Reduce(k[8]); float dist_01_04 = DistYCbCr(k[1], k[4]); float dist_03_08 = DistYCbCr(k[3], k[8]); bool haveShallowLine = (STEEP_DIRECTION_THRESHOLD * dist_01_04 <= dist_03_08) && (v0 != v4) && (v5 != v4); bool haveSteepLine = (STEEP_DIRECTION_THRESHOLD * dist_03_08 <= dist_01_04) && (v0 != v8) && (v7 != v8); bool needBlend = (blend[2] != BLEND_NONE); bool doLineBlend = ( blend[2] >= BLEND_DOMINANT || !((blend[1] != BLEND_NONE && !IsPixEqual(k[0], k[4])) || (blend[3] != BLEND_NONE && !IsPixEqual(k[0], k[8])) || (IsPixEqual(k[4], k[3]) && IsPixEqual(k[3], k[2]) && IsPixEqual(k[2], k[1]) && IsPixEqual(k[1], k[8]) && !IsPixEqual(k[0], k[2])))); vec3 blendPix = (DistYCbCr(k[0], k[1]) <= DistYCbCr(k[0], k[3])) ? k[1] : k[3]; dst[1] = mix(dst[1], blendPix, (needBlend && doLineBlend) ? ((haveSteepLine) ? 0.750 : ((haveShallowLine) ? 0.250 : 0.125)) : 0.000); dst[2] = mix(dst[2], blendPix, (needBlend) ? ((doLineBlend) ? ((!haveShallowLine && !haveSteepLine) ? 0.875 : 1.000) : 0.4545939598) : 0.000); dst[3] = mix(dst[3], blendPix, (needBlend && doLineBlend) ? ((haveShallowLine) ? 0.750 : ((haveSteepLine) ? 0.250 : 0.125)) : 0.000); dst[4] = mix(dst[4], blendPix, (needBlend && doLineBlend && haveShallowLine) ? 0.250 : 0.000); dst[8] = mix(dst[8], blendPix, (needBlend && doLineBlend && haveSteepLine) ? 0.250 : 0.000); } void main() { vec2 f = fract(vPixelCoords); vec3 src[25]; src[21] = texture(uTexture, vTexCoords1.xw).rgb; src[22] = texture(uTexture, vTexCoords1.yw).rgb; src[23] = texture(uTexture, vTexCoords1.zw).rgb; src[ 6] = texture(uTexture, vTexCoords2.xw).rgb; src[ 7] = texture(uTexture, vTexCoords2.yw).rgb; src[ 8] = texture(uTexture, vTexCoords2.zw).rgb; src[ 5] = texture(uTexture, vTexCoords3.xw).rgb; src[ 0] = texture(uTexture, vTexCoords3.yw).rgb; src[ 1] = texture(uTexture, vTexCoords3.zw).rgb; src[ 4] = texture(uTexture, vTexCoords4.xw).rgb; src[ 3] = texture(uTexture, vTexCoords4.yw).rgb; src[ 2] = texture(uTexture, vTexCoords4.zw).rgb; src[15] = texture(uTexture, vTexCoords5.xw).rgb; src[14] = texture(uTexture, vTexCoords5.yw).rgb; src[13] = texture(uTexture, vTexCoords5.zw).rgb; src[19] = texture(uTexture, vTexCoords6.xy).rgb; src[18] = texture(uTexture, vTexCoords6.xz).rgb; src[17] = texture(uTexture, vTexCoords6.xw).rgb; src[ 9] = texture(uTexture, vTexCoords7.xy).rgb; src[10] = texture(uTexture, vTexCoords7.xz).rgb; src[11] = texture(uTexture, vTexCoords7.xw).rgb; float v[9]; v[0] = Reduce(src[0]); v[1] = Reduce(src[1]); v[2] = Reduce(src[2]); v[3] = Reduce(src[3]); v[4] = Reduce(src[4]); v[5] = Reduce(src[5]); v[6] = Reduce(src[6]); v[7] = Reduce(src[7]); v[8] = Reduce(src[8]); ivec4 blendResult = ivec4(BLEND_NONE); if (!((v[0] == v[1] && v[3] == v[2]) || (v[0] == v[3] && v[1] == v[2]))) { float dist_03_01 = DistYCbCr(src[ 4], src[ 0]) + DistYCbCr(src[ 0], src[ 8]) + DistYCbCr(src[14], src[ 2]) + DistYCbCr(src[ 2], src[10]) + (4.0 * DistYCbCr(src[ 3], src[ 1])); float dist_00_02 = DistYCbCr(src[ 5], src[ 3]) + DistYCbCr(src[ 3], src[13]) + DistYCbCr(src[ 7], src[ 1]) + DistYCbCr(src[ 1], src[11]) + (4.0 * DistYCbCr(src[ 0], src[ 2])); bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_03_01) < dist_00_02; blendResult[2] = ((dist_03_01 < dist_00_02) && (v[0] != v[1]) && (v[0] != v[3])) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; } if (!((v[5] == v[0] && v[4] == v[3]) || (v[5] == v[4] && v[0] == v[3]))) { float dist_04_00 = DistYCbCr(src[17] , src[ 5]) + DistYCbCr(src[ 5], src[ 7]) + DistYCbCr(src[15], src[ 3]) + DistYCbCr(src[ 3], src[ 1]) + (4.0 * DistYCbCr(src[ 4], src[ 0])); float dist_05_03 = DistYCbCr(src[18] , src[ 4]) + DistYCbCr(src[ 4], src[14]) + DistYCbCr(src[ 6], src[ 0]) + DistYCbCr(src[ 0], src[ 2]) + (4.0 * DistYCbCr(src[ 5], src[ 3])); bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_05_03) < dist_04_00; blendResult[3] = ((dist_04_00 > dist_05_03) && (v[0] != v[5]) && (v[0] != v[3])) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; } if (!((v[7] == v[8] && v[0] == v[1]) || (v[7] == v[0] && v[8] == v[1]))) { float dist_00_08 = DistYCbCr(src[ 5], src[ 7]) + DistYCbCr(src[ 7], src[23]) + DistYCbCr(src[ 3], src[ 1]) + DistYCbCr(src[ 1], src[ 9]) + (4.0 * DistYCbCr(src[ 0], src[ 8])); float dist_07_01 = DistYCbCr(src[ 6], src[ 0]) + DistYCbCr(src[ 0], src[ 2]) + DistYCbCr(src[22], src[ 8]) + DistYCbCr(src[ 8], src[10]) + (4.0 * DistYCbCr(src[ 7], src[ 1])); bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_07_01) < dist_00_08; blendResult[1] = ((dist_00_08 > dist_07_01) && (v[0] != v[7]) && (v[0] != v[1])) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; } if (!((v[6] == v[7] && v[5] == v[0]) || (v[6] == v[5] && v[7] == v[0]))) { float dist_05_07 = DistYCbCr(src[18], src[ 6]) + DistYCbCr(src[ 6], src[22]) + DistYCbCr(src[ 4], src[ 0]) + DistYCbCr(src[ 0], src[ 8]) + (4.0 * DistYCbCr(src[ 5], src[ 7])); float dist_06_00 = DistYCbCr(src[19], src[ 5]) + DistYCbCr(src[ 5], src[ 3]) + DistYCbCr(src[21], src[ 7]) + DistYCbCr(src[ 7], src[ 1]) + (4.0 * DistYCbCr(src[ 6], src[ 0])); bool dominantGradient = (DOMINANT_DIRECTION_THRESHOLD * dist_05_07) < dist_06_00; blendResult[0] = ((dist_05_07 < dist_06_00) && (v[0] != v[5]) && (v[0] != v[7])) ? ((dominantGradient) ? BLEND_DOMINANT : BLEND_NORMAL) : BLEND_NONE; } vec3 dst[9]; dst[0] = src[0]; dst[1] = src[0]; dst[2] = src[0]; dst[3] = src[0]; dst[4] = src[0]; dst[5] = src[0]; dst[6] = src[0]; dst[7] = src[0]; dst[8] = src[0]; // Scale pixel if (IsBlendingNeeded(blendResult)) { vec3 k[9]; vec3 tempDst8; vec3 tempDst7; k[8] = src[8]; k[7] = src[7]; k[6] = src[6]; k[5] = src[5]; k[4] = src[4]; k[3] = src[3]; k[2] = src[2]; k[1] = src[1]; k[0] = src[0]; ScalePixel(blendResult.xyzw, k, dst); k[8] = src[6]; k[7] = src[5]; k[6] = src[4]; k[5] = src[3]; k[4] = src[2]; k[3] = src[1]; k[2] = src[8]; k[1] = src[7]; tempDst8 = dst[8]; tempDst7 = dst[7]; dst[8] = dst[6]; dst[7] = dst[5]; dst[6] = dst[4]; dst[5] = dst[3]; dst[4] = dst[2]; dst[3] = dst[1]; dst[2] = tempDst8; dst[1] = tempDst7; ScalePixel(blendResult.wxyz, k, dst); k[8] = src[4]; k[7] = src[3]; k[6] = src[2]; k[5] = src[1]; k[4] = src[8]; k[3] = src[7]; k[2] = src[6]; k[1] = src[5]; tempDst8 = dst[8]; tempDst7 = dst[7]; dst[8] = dst[6]; dst[7] = dst[5]; dst[6] = dst[4]; dst[5] = dst[3]; dst[4] = dst[2]; dst[3] = dst[1]; dst[2] = tempDst8; dst[1] = tempDst7; ScalePixel(blendResult.zwxy, k, dst); k[8] = src[2]; k[7] = src[1]; k[6] = src[8]; k[5] = src[7]; k[4] = src[6]; k[3] = src[5]; k[2] = src[4]; k[1] = src[3]; tempDst8 = dst[8]; tempDst7 = dst[7]; dst[8] = dst[6]; dst[7] = dst[5]; dst[6] = dst[4]; dst[5] = dst[3]; dst[4] = dst[2]; dst[3] = dst[1]; dst[2] = tempDst8; dst[1] = tempDst7; ScalePixel(blendResult.yzwx, k, dst); tempDst8 = dst[8]; tempDst7 = dst[7]; dst[8] = dst[6]; dst[7] = dst[5]; dst[6] = dst[4]; dst[5] = dst[3]; dst[4] = dst[2]; dst[3] = dst[1]; dst[2] = tempDst8; dst[1] = tempDst7; } vec3 res = mix(mix(dst[6], mix(dst[7], dst[8], step(two_third, f.x)), step(one_third, f.x)), mix(mix(dst[5], mix(dst[0], dst[1], step(two_third, f.x)), step(one_third, f.x)), mix(dst[4], mix(dst[3], dst[2], step(two_third, f.x)), step(one_third, f.x)), step(two_third, f.y)), step(one_third, f.y)); fragColor = vec4(res, 1.0); } )"; constexpr char ResizeSabrVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vTexSize; out vec2 tc; out vec4 xyp_1_2_3; out vec4 xyp_5_10_15; out vec4 xyp_6_7_8; out vec4 xyp_9_14_9; out vec4 xyp_11_12_13; out vec4 xyp_16_17_18; out vec4 xyp_21_22_23; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vTexSize = texRect.xy; tc = aPosition * vec2(1.0004, 1.0); float x = 1.0 / vTexSize.x; float y = 1.0 / vTexSize.y; xyp_1_2_3 = tc.xxxy + vec4( -x, 0.0, x, -2.0 * y); xyp_6_7_8 = tc.xxxy + vec4( -x, 0.0, x, -y); xyp_11_12_13 = tc.xxxy + vec4( -x, 0.0, x, 0.0); xyp_16_17_18 = tc.xxxy + vec4( -x, 0.0, x, y); xyp_21_22_23 = tc.xxxy + vec4( -x, 0.0, x, 2.0 * y); xyp_5_10_15 = tc.xyyy + vec4(-2.0 * x, -y, 0.0, y); xyp_9_14_9 = tc.xyyy + vec4( 2.0 * x, -y, 0.0, y); } )"; constexpr char ResizeSabrFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vTexSize; in vec2 tc; in vec4 xyp_1_2_3; in vec4 xyp_5_10_15; in vec4 xyp_6_7_8; in vec4 xyp_9_14_9; in vec4 xyp_11_12_13; in vec4 xyp_16_17_18; in vec4 xyp_21_22_23; out vec4 fragColor; const vec4 Ai = vec4( 1.0, -1.0, -1.0, 1.0); const vec4 B45 = vec4( 1.0, 1.0, -1.0, -1.0); const vec4 C45 = vec4( 1.5, 0.5, -0.5, 0.5); const vec4 B30 = vec4( 0.5, 2.0, -0.5, -2.0); const vec4 C30 = vec4( 1.0, 1.0, -0.5, 0.0); const vec4 B60 = vec4( 2.0, 0.5, -2.0, -0.5); const vec4 C60 = vec4( 2.0, 0.0, -1.0, 0.5); const vec4 M45 = vec4(0.4, 0.4, 0.4, 0.4); const vec4 M30 = vec4(0.2, 0.4, 0.2, 0.4); const vec4 M60 = M30.yxwz; const vec4 Mshift = vec4(0.2); const float coef = 2.0; const vec4 threshold = vec4(0.32); const vec3 lum = vec3(0.21, 0.72, 0.07); bvec4 _and_(bvec4 A, bvec4 B) { return bvec4(A.x && B.x, A.y && B.y, A.z && B.z, A.w && B.w); } bvec4 _or_(bvec4 A, bvec4 B) { return bvec4(A.x || B.x, A.y || B.y, A.z || B.z, A.w || B.w); } vec4 lum_to(vec3 v0, vec3 v1, vec3 v2, vec3 v3) { return vec4(dot(lum, v0), dot(lum, v1), dot(lum, v2), dot(lum, v3)); } vec4 lum_df(vec4 A, vec4 B) { return abs(A - B); } bvec4 lum_eq(vec4 A, vec4 B) { return lessThan(lum_df(A, B), threshold); } vec4 lum_wd(vec4 a, vec4 b, vec4 c, vec4 d, vec4 e, vec4 f, vec4 g, vec4 h) { return lum_df(a, b) + lum_df(a, c) + lum_df(d, e) + lum_df(d, f) + 4.0 * lum_df(g, h); } float c_df(vec3 c1, vec3 c2) { vec3 df = abs(c1 - c2); return df.r + df.g + df.b; } void main() { // Get mask values by performing texture lookup with the uniform sampler vec3 P1 = texture(uTexture, xyp_1_2_3.xw ).rgb; vec3 P2 = texture(uTexture, xyp_1_2_3.yw ).rgb; vec3 P3 = texture(uTexture, xyp_1_2_3.zw ).rgb; vec3 P6 = texture(uTexture, xyp_6_7_8.xw ).rgb; vec3 P7 = texture(uTexture, xyp_6_7_8.yw ).rgb; vec3 P8 = texture(uTexture, xyp_6_7_8.zw ).rgb; vec3 P11 = texture(uTexture, xyp_11_12_13.xw).rgb; vec3 P12 = texture(uTexture, xyp_11_12_13.yw).rgb; vec3 P13 = texture(uTexture, xyp_11_12_13.zw).rgb; vec3 P16 = texture(uTexture, xyp_16_17_18.xw).rgb; vec3 P17 = texture(uTexture, xyp_16_17_18.yw).rgb; vec3 P18 = texture(uTexture, xyp_16_17_18.zw).rgb; vec3 P21 = texture(uTexture, xyp_21_22_23.xw).rgb; vec3 P22 = texture(uTexture, xyp_21_22_23.yw).rgb; vec3 P23 = texture(uTexture, xyp_21_22_23.zw).rgb; vec3 P5 = texture(uTexture, xyp_5_10_15.xy ).rgb; vec3 P10 = texture(uTexture, xyp_5_10_15.xz ).rgb; vec3 P15 = texture(uTexture, xyp_5_10_15.xw ).rgb; vec3 P9 = texture(uTexture, xyp_9_14_9.xy ).rgb; vec3 P14 = texture(uTexture, xyp_9_14_9.xz ).rgb; vec3 P19 = texture(uTexture, xyp_9_14_9.xw ).rgb; // Store luminance values of each point in groups of 4 // so that we may operate on all four corners at once vec4 p7 = lum_to(P7, P11, P17, P13); vec4 p8 = lum_to(P8, P6, P16, P18); vec4 p11 = p7.yzwx; // P11, P17, P13, P7 vec4 p12 = lum_to(P12, P12, P12, P12); vec4 p13 = p7.wxyz; // P13, P7, P11, P17 vec4 p14 = lum_to(P14, P2, P10, P22); vec4 p16 = p8.zwxy; // P16, P18, P8, P6 vec4 p17 = p7.zwxy; // P17, P13, P7, P11 vec4 p18 = p8.wxyz; // P18, P8, P6, P16 vec4 p19 = lum_to(P19, P3, P5, P21); vec4 p22 = p14.wxyz; // P22, P14, P2, P10 vec4 p23 = lum_to(P23, P9, P1, P15); // Scale current texel coordinate to [0..1] vec2 fp = fract(tc * vTexSize); // Determine amount of "smoothing" or mixing that could be done on texel corners vec4 ma45 = smoothstep(C45 - M45, C45 + M45, Ai * fp.y + B45 * fp.x); vec4 ma30 = smoothstep(C30 - M30, C30 + M30, Ai * fp.y + B30 * fp.x); vec4 ma60 = smoothstep(C60 - M60, C60 + M60, Ai * fp.y + B60 * fp.x); vec4 marn = smoothstep(C45 - M45 + Mshift, C45 + M45 + Mshift, Ai * fp.y + B45 * fp.x); // Perform edge weight calculations vec4 e45 = lum_wd(p12, p8, p16, p18, p22, p14, p17, p13); vec4 econt = lum_wd(p17, p11, p23, p13, p7, p19, p12, p18); vec4 e30 = lum_df(p13, p16); vec4 e60 = lum_df(p8, p17); // Calculate rule results for interpolation bvec4 r45_1 = _and_(notEqual(p12, p13), notEqual(p12, p17)); bvec4 r45_2 = _and_(not(lum_eq(p13, p7)), not(lum_eq(p13, p8))); bvec4 r45_3 = _and_(not(lum_eq(p17, p11)), not(lum_eq(p17, p16))); bvec4 r45_4_1 = _and_(not(lum_eq(p13, p14)), not(lum_eq(p13, p19))); bvec4 r45_4_2 = _and_(not(lum_eq(p17, p22)), not(lum_eq(p17, p23))); bvec4 r45_4 = _and_(lum_eq(p12, p18), _or_(r45_4_1, r45_4_2)); bvec4 r45_5 = _or_(lum_eq(p12, p16), lum_eq(p12, p8)); bvec4 r45 = _and_(r45_1, _or_(_or_(_or_(r45_2, r45_3), r45_4), r45_5)); bvec4 r30 = _and_(notEqual(p12, p16), notEqual(p11, p16)); bvec4 r60 = _and_(notEqual(p12, p8), notEqual(p7, p8)); // Combine rules with edge weights bvec4 edr45 = _and_(lessThan(e45, econt), r45); bvec4 edrrn = lessThanEqual(e45, econt); bvec4 edr30 = _and_(lessThanEqual(coef * e30, e60), r30); bvec4 edr60 = _and_(lessThanEqual(coef * e60, e30), r60); // Finalize interpolation rules and cast to float (0.0 for false, 1.0 for true) vec4 final45 = vec4(_and_(_and_(not(edr30), not(edr60)), edr45)); vec4 final30 = vec4(_and_(_and_(edr45, not(edr60)), edr30)); vec4 final60 = vec4(_and_(_and_(edr45, not(edr30)), edr60)); vec4 final36 = vec4(_and_(_and_(edr60, edr30), edr45)); vec4 finalrn = vec4(_and_(not(edr45), edrrn)); // Determine the color to mix with for each corner vec4 px = step(lum_df(p12, p17), lum_df(p12, p13)); // Determine the mix amounts by combining the final rule result and corresponding // mix amount for the rule in each corner vec4 mac = final36 * max(ma30, ma60) + final30 * ma30 + final60 * ma60 + final45 * ma45 + finalrn * marn; vec3 res1 = P12; res1 = mix(res1, mix(P13, P17, px.x), mac.x); res1 = mix(res1, mix(P7, P13, px.y), mac.y); res1 = mix(res1, mix(P11, P7, px.z), mac.z); res1 = mix(res1, mix(P17, P11, px.w), mac.w); vec3 res2 = P12; res2 = mix(res2, mix(P17, P11, px.w), mac.w); res2 = mix(res2, mix(P11, P7, px.z), mac.z); res2 = mix(res2, mix(P7, P13, px.y), mac.y); res2 = mix(res2, mix(P13, P17, px.x), mac.x); fragColor = vec4(mix(res1, res2, step(c_df(P12, res1), c_df(P12, res2))), 1.0); } )"; constexpr char ResizeCleanEdgeVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 v_px; out vec2 size; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; size = texRect.xy + 0.0001; v_px = aPosition.xy * size; } )"; constexpr char ResizeCleanEdgeFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 v_px; in vec2 size; out vec4 fragColor; #define SLOPE #define CLEANUP vec3 highestColor = vec3(1.,1.,1.); float similarThreshold = 0.0; float lineWidth = 1.0; const float scale = 4.0; const mat3 yuv_matrix = mat3(vec3(0.299, 0.587, 0.114), vec3(-0.169, -0.331, 0.5), vec3(0.5, -0.419, -0.081)); vec3 yuv(vec3 col){ mat3 yuv = transpose(yuv_matrix); return yuv * col; } bool similar(vec4 col1, vec4 col2){ return (col1.a == 0. && col2.a == 0.) || distance(col1, col2) <= similarThreshold; } bool similar3(vec4 col1, vec4 col2, vec4 col3){ return similar(col1, col2) && similar(col2, col3); } bool similar4(vec4 col1, vec4 col2, vec4 col3, vec4 col4){ return similar(col1, col2) && similar(col2, col3) && similar(col3, col4); } bool similar5(vec4 col1, vec4 col2, vec4 col3, vec4 col4, vec4 col5){ return similar(col1, col2) && similar(col2, col3) && similar(col3, col4) && similar(col4, col5); } bool higher(vec4 thisCol, vec4 otherCol){ if(similar(thisCol, otherCol)) return false; if(thisCol.a == otherCol.a){ // return yuv(thisCol.rgb).x > yuv(otherCol.rgb).x; // return distance(yuv(thisCol.rgb), yuv(highestColor)) < distance(yuv(otherCol.rgb), yuv(highestColor)); return distance(thisCol.rgb, highestColor) < distance(otherCol.rgb, highestColor); } else { return thisCol.a > otherCol.a; } } vec4 higherCol(vec4 thisCol, vec4 otherCol){ return higher(thisCol, otherCol) ? thisCol : otherCol; } float cd(vec4 col1, vec4 col2){ return distance(col1.rgba, col2.rgba); } float distToLine(vec2 testPt, vec2 pt1, vec2 pt2, vec2 dir){ vec2 lineDir = pt2 - pt1; vec2 perpDir = vec2(lineDir.y, -lineDir.x); vec2 dirToPt1 = pt1 - testPt; return (dot(perpDir, dir) > 0.0 ? 1.0 : -1.0) * (dot(normalize(perpDir), dirToPt1)); } vec4 sliceDist(vec2 point, vec2 mainDir, vec2 pointDir, vec4 ub, vec4 u, vec4 uf, vec4 uff, vec4 b, vec4 c, vec4 f, vec4 ff, vec4 db, vec4 d, vec4 df, vec4 dff, vec4 ddb, vec4 dd, vec4 ddf){ //clamped range prevents inacccurate identity (no change) result, feel free to disable if necessary #ifdef SLOPE float minWidth = 0.45; float maxWidth = 1.142; #else float minWidth = 0.0; float maxWidth = 1.4; #endif float _lineWidth = max(minWidth, min(maxWidth, lineWidth)); point = mainDir * (point - 0.5) + 0.5; //flip point //edge detection float distAgainst = 4.0*cd(f,d) + cd(uf,c) + cd(c,db) + cd(ff,df) + cd(df,dd); float distTowards = 4.0*cd(c,df) + cd(u,f) + cd(f,dff) + cd(b,d) + cd(d,ddf); bool shouldSlice = (distAgainst < distTowards) || (distAgainst < distTowards + 0.001) && !higher(c, f); //equivalent edges edge case if(similar4(f, d, b, u) && similar4(uf, df, db, ub) && !similar(c, f)){ //checkerboard edge case shouldSlice = false; } if(!shouldSlice) return vec4(-1.0); //only applicable for very large lineWidth (>1.3) // if(similar3(c, f, df)){ //don't make slice for same color // return vec4(-1.0); // } float dist = 1.0; bool flip = false; vec2 center = vec2(0.5,0.5); #ifdef SLOPE if(similar3(f, d, db) && !similar3(f, d, b) && !similar(uf, db)){ //lower shallow 2:1 slant if(similar(c, df) && higher(c, f)){ //single pixel wide diagonal, dont flip } else { //priority edge cases if(higher(c, f)){ flip = true; } if(similar(u, f) && !similar(c, df) && !higher(c, u)){ flip = true; } } if(flip){ dist = _lineWidth-distToLine(point, center+vec2(1.5, -1.0)*pointDir, center+vec2(-0.5, 0.0)*pointDir, -pointDir); //midpoints of neighbor two-pixel groupings } else { dist = distToLine(point, center+vec2(1.5, 0.0)*pointDir, center+vec2(-0.5, 1.0)*pointDir, pointDir); //midpoints of neighbor two-pixel groupings } //cleanup slant transitions #ifdef CLEANUP if(!flip && similar(c, uf) && !(similar3(c, uf, uff) && !similar3(c, uf, ff) && !similar(d, uff))){ //shallow float dist2 = distToLine(point, center+vec2(2.0, -1.0)*pointDir, center+vec2(-0.0, 1.0)*pointDir, pointDir); dist = min(dist, dist2); } #endif dist -= (_lineWidth/2.0); return dist <= 0.0 ? ((cd(c,f) <= cd(c,d)) ? f : d) : vec4(-1.0); } else if(similar3(uf, f, d) && !similar3(u, f, d) && !similar(uf, db)){ //forward steep 2:1 slant if(similar(c, df) && higher(c, d)){ //single pixel wide diagonal, dont flip } else { //priority edge cases if(higher(c, d)){ flip = true; } if(similar(b, d) && !similar(c, df) && !higher(c, d)){ flip = true; } } if(flip){ dist = _lineWidth-distToLine(point, center+vec2(0.0, -0.5)*pointDir, center+vec2(-1.0, 1.5)*pointDir, -pointDir); //midpoints of neighbor two-pixel groupings } else { dist = distToLine(point, center+vec2(1.0, -0.5)*pointDir, center+vec2(0.0, 1.5)*pointDir, pointDir); //midpoints of neighbor two-pixel groupings } //cleanup slant transitions #ifdef CLEANUP if(!flip && similar(c, db) && !(similar3(c, db, ddb) && !similar3(c, db, dd) && !similar(f, ddb))){ //steep float dist2 = distToLine(point, center+vec2(1.0, 0.0)*pointDir, center+vec2(-1.0, 2.0)*pointDir, pointDir); dist = min(dist, dist2); } #endif dist -= (_lineWidth/2.0); return dist <= 0.0 ? ((cd(c,f) <= cd(c,d)) ? f : d) : vec4(-1.0); } else #endif if(similar(f, d)) { //45 diagonal if(similar(c, df) && higher(c, f)){ //single pixel diagonal along neighbors, dont flip if(!similar(c, dd) && !similar(c, ff)){ //line against triple color stripe edge case flip = true; } } else { //priority edge cases if(higher(c, f)){ flip = true; } if(!similar(c, b) && similar4(b, f, d, u)){ flip = true; } } //single pixel 2:1 slope, dont flip if((( (similar(f, db) && similar3(u, f, df)) || (similar(uf, d) && similar3(b, d, df)) ) && !similar(c, df))){ flip = true; } if(flip){ dist = _lineWidth-distToLine(point, center+vec2(1.0, -1.0)*pointDir, center+vec2(-1.0, 1.0)*pointDir, -pointDir); //midpoints of own diagonal pixels } else { dist = distToLine(point, center+vec2(1.0, 0.0)*pointDir, center+vec2(0.0, 1.0)*pointDir, pointDir); //midpoints of corner neighbor pixels } //cleanup slant transitions #ifdef SLOPE #ifdef CLEANUP if(!flip && similar3(c, uf, uff) && !similar3(c, uf, ff) && !similar(d, uff)){ //shallow float dist2 = distToLine(point, center+vec2(1.5, 0.0)*pointDir, center+vec2(-0.5, 1.0)*pointDir, pointDir); dist = max(dist, dist2); } if(!flip && similar3(ddb, db, c) && !similar3(dd, db, c) && !similar(ddb, f)){ //steep float dist2 = distToLine(point, center+vec2(1.0, -0.5)*pointDir, center+vec2(0.0, 1.5)*pointDir, pointDir); dist = max(dist, dist2); } #endif #endif dist -= (_lineWidth/2.0); return dist <= 0.0 ? ((cd(c,f) <= cd(c,d)) ? f : d) : vec4(-1.0); } #ifdef SLOPE else if(similar3(ff, df, d) && !similar3(ff, df, c) && !similar(uff, d)){ //far corner of shallow slant if(similar(f, dff) && higher(f, ff)){ //single pixel wide diagonal, dont flip } else { //priority edge cases if(higher(f, ff)){ flip = true; } if(similar(uf, ff) && !similar(f, dff) && !higher(f, uf)){ flip = true; } } if(flip){ dist = _lineWidth-distToLine(point, center+vec2(1.5+1.0, -1.0)*pointDir, center+vec2(-0.5+1.0, 0.0)*pointDir, -pointDir); //midpoints of neighbor two-pixel groupings } else { dist = distToLine(point, center+vec2(1.5+1.0, 0.0)*pointDir, center+vec2(-0.5+1.0, 1.0)*pointDir, pointDir); //midpoints of neighbor two-pixel groupings } dist -= (_lineWidth/2.0); return dist <= 0.0 ? ((cd(f,ff) <= cd(f,df)) ? ff : df) : vec4(-1.0); } else if(similar3(f, df, dd) && !similar3(c, df, dd) && !similar(f, ddb)){ //far corner of steep slant if(similar(d, ddf) && higher(d, dd)){ //single pixel wide diagonal, dont flip } else { //priority edge cases if(higher(d, dd)){ flip = true; } if(similar(db, dd) && !similar(d, ddf) && !higher(d, dd)){ flip = true; } // if(!higher(d, dd)){ // return vec4(1.0); // flip = true; // } } if(flip){ dist = _lineWidth-distToLine(point, center+vec2(0.0, -0.5+1.0)*pointDir, center+vec2(-1.0, 1.5+1.0)*pointDir, -pointDir); //midpoints of neighbor two-pixel groupings } else { dist = distToLine(point, center+vec2(1.0, -0.5+1.0)*pointDir, center+vec2(0.0, 1.5+1.0)*pointDir, pointDir); //midpoints of neighbor two-pixel groupings } dist -= (_lineWidth/2.0); return dist <= 0.0 ? ((cd(d,df) <= cd(d,dd)) ? df : dd) : vec4(-1.0); } #endif return vec4(-1.0); } void main() { vec2 local = fract(v_px); vec2 px = ceil(v_px); vec2 pointDir = round(local)*2.0-1.0; //neighbor pixels //Up, Down, Forward, and Back //relative to quadrant of current location within pixel vec4 uub = texture(uTexture, (px+vec2(-1.0,-2.0)*pointDir)/size); vec4 uu = texture(uTexture, (px+vec2( 0.0,-2.0)*pointDir)/size); vec4 uuf = texture(uTexture, (px+vec2( 1.0,-2.0)*pointDir)/size); vec4 ubb = texture(uTexture, (px+vec2(-2.0,-2.0)*pointDir)/size); vec4 ub = texture(uTexture, (px+vec2(-1.0,-1.0)*pointDir)/size); vec4 u = texture(uTexture, (px+vec2( 0.0,-1.0)*pointDir)/size); vec4 uf = texture(uTexture, (px+vec2( 1.0,-1.0)*pointDir)/size); vec4 uff = texture(uTexture, (px+vec2( 2.0,-1.0)*pointDir)/size); vec4 bb = texture(uTexture, (px+vec2(-2.0, 0.0)*pointDir)/size); vec4 b = texture(uTexture, (px+vec2(-1.0, 0.0)*pointDir)/size); vec4 c = texture(uTexture, (px+vec2( 0.0, 0.0)*pointDir)/size); vec4 f = texture(uTexture, (px+vec2( 1.0, 0.0)*pointDir)/size); vec4 ff = texture(uTexture, (px+vec2( 2.0, 0.0)*pointDir)/size); vec4 dbb = texture(uTexture, (px+vec2(-2.0, 1.0)*pointDir)/size); vec4 db = texture(uTexture, (px+vec2(-1.0, 1.0)*pointDir)/size); vec4 d = texture(uTexture, (px+vec2( 0.0, 1.0)*pointDir)/size); vec4 df = texture(uTexture, (px+vec2( 1.0, 1.0)*pointDir)/size); vec4 dff = texture(uTexture, (px+vec2( 2.0, 1.0)*pointDir)/size); vec4 ddb = texture(uTexture, (px+vec2(-1.0, 2.0)*pointDir)/size); vec4 dd = texture(uTexture, (px+vec2( 0.0, 2.0)*pointDir)/size); vec4 ddf = texture(uTexture, (px+vec2( 1.0, 2.0)*pointDir)/size); vec4 col = c; //c_orner, b_ack, and u_p slices // (slices from neighbor pixels will only ever reach these 3 quadrants vec4 c_col = sliceDist(local, vec2( 1.0, 1.0), pointDir, ub, u, uf, uff, b, c, f, ff, db, d, df, dff, ddb, dd, ddf); vec4 b_col = sliceDist(local, vec2(-1.0, 1.0), pointDir, uf, u, ub, ubb, f, c, b, bb, df, d, db, dbb, ddf, dd, ddb); vec4 u_col = sliceDist(local, vec2( 1.0,-1.0), pointDir, db, d, df, dff, b, c, f, ff, ub, u, uf, uff, uub, uu, uuf); if(c_col.r >= 0.0){ col = c_col; } if(b_col.r >= 0.0){ col = b_col; } if(u_col.r >= 0.0){ col = u_col; } fragColor = col; } )"; constexpr char ResizeCrtScanlinesVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out highp vec2 vPixelCoords; out vec2 vTexCoords; out highp vec2 vOutputSize; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vPixelCoords = aPosition * spriteSize.xy; vTexCoords = aPosition; vOutputSize = spriteSize.xy; } )"; constexpr char ResizeCrtScanlinesFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in highp vec2 vPixelCoords; in vec2 vTexCoords; in highp vec2 vOutputSize; out vec4 fragColor; vec3 toYiq(vec3 value) { const mat3 yiqmat = mat3( 0.2989, 0.5870, 0.1140, 0.5959, -0.2744, -0.3216, 0.2115, -0.5229, 0.3114); return value * yiqmat; } vec3 fromYiq(vec3 value) { const mat3 rgbmat = mat3( 1.0, 0.956, 0.6210, 1.0, -0.2720, -0.6474, 1.0, -1.1060, 1.7046); return value * rgbmat; } void main() { float y = vPixelCoords.y; vec2 uv0 = vec2(vTexCoords.x, y / vOutputSize.y); vec3 t0 = texture(uTexture, uv0).rgb; float ymod = mod(vPixelCoords.y, 3.0); if (ymod > 2.0) { vec2 uv1 = vec2(vTexCoords.x, (y + 1.0) / vOutputSize.y); vec3 t1 = texture(uTexture, uv1).rgb; fragColor.rgb = (t0 + t1) * 0.25; } else { t0 = toYiq(t0); t0.r *= 1.1; t0 = fromYiq(t0); fragColor.rgb = t0; } fragColor.a = 1.0; } )"; constexpr char ResizeCrtVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vTexCoords; out vec2 vTexSize; out vec2 vViewSize; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vTexCoords = aPosition; vTexSize = texRect.xy; vViewSize = spriteSize; } )"; constexpr char ResizeCrtShadowMaskFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif #define hardScan -6.0 /*-8.0*/ #define hardPix -3.0 #define warpX 0.031 #define warpY 0.041 #define maskDark 0.55 #define maskLight 1.5 #define scaleInLinearGamma 1 #define brightboost 1.0 #define hardBloomScan -2.0 #define hardBloomPix -1.5 #define bloomAmount 1.0/12.0 /*1.0/16.0*/ #define shape 2.0 uniform sampler2D uTexture; in vec2 vTexCoords; in vec2 vTexSize; in vec2 vViewSize; out vec4 fragColor; #ifdef SIMPLE_LINEAR_GAMMA float ToLinear1(float c) { return c; } vec3 ToLinear(vec3 c) { return c; } vec3 ToSrgb(vec3 c) { return pow(c, 1.0 / 2.2); } #else float ToLinear1(float c) { if (scaleInLinearGamma==0) return c; return(c<=0.04045)?c/12.92:pow((c+0.055)/1.055,2.4); } vec3 ToLinear(vec3 c) { if (scaleInLinearGamma==0) return c; return vec3(ToLinear1(c.r),ToLinear1(c.g),ToLinear1(c.b)); } float ToSrgb1(float c) { if (scaleInLinearGamma==0) return c; return(c<0.0031308?c*12.92:1.055*pow(c,0.41666)-0.055); } vec3 ToSrgb(vec3 c) { if (scaleInLinearGamma==0) return c; return vec3(ToSrgb1(c.r),ToSrgb1(c.g),ToSrgb1(c.b)); } #endif // Nearest emulated sample given floating point position and texel offset. // Also zero's off screen. vec3 Fetch(vec2 pos, vec2 off, vec2 texture_size) { pos=(floor(pos*texture_size.xy+off)+vec2(0.5,0.5))/texture_size.xy; #ifdef SIMPLE_LINEAR_GAMMA return ToLinear(vec3(brightboost) * pow(texture(uTexture,pos.xy).rgb, 2.2)); #else return ToLinear(vec3(brightboost) * texture(uTexture,pos.xy).rgb); #endif } // Distance in emulated pixels to nearest texel vec2 Dist(vec2 pos, vec2 texture_size) { pos=pos*texture_size.xy; return -((pos-floor(pos))-vec2(0.5, 0.5)); } // 1D Gaussian float Gaus(float pos,float scale) { return exp2(scale*pow(abs(pos),shape)); } // 3-tap Gaussian filter along horz line vec3 Horz3(vec2 pos, float off, vec2 texture_size) { vec3 b=Fetch(pos,vec2(-1.0,off),texture_size); vec3 c=Fetch(pos,vec2( 0.0,off),texture_size); vec3 d=Fetch(pos,vec2( 1.0,off),texture_size); float dst=Dist(pos, texture_size).x; // Convert distance to weight. float scale=hardPix; float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); // Return filtered sample. return (b*wb+c*wc+d*wd)/(wb+wc+wd); } // 5-tap Gaussian filter along horz line vec3 Horz5(vec2 pos, float off, vec2 texture_size) { vec3 a=Fetch(pos,vec2(-2.0,off),texture_size); vec3 b=Fetch(pos,vec2(-1.0,off),texture_size); vec3 c=Fetch(pos,vec2( 0.0,off),texture_size); vec3 d=Fetch(pos,vec2( 1.0,off),texture_size); vec3 e=Fetch(pos,vec2( 2.0,off),texture_size); float dst=Dist(pos, texture_size).x; // Convert distance to weight. float scale=hardPix; float wa=Gaus(dst-2.0,scale); float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); float we=Gaus(dst+2.0,scale); // Return filtered sample. return (a*wa+b*wb+c*wc+d*wd+e*we)/(wa+wb+wc+wd+we); } // 7-tap Gaussian filter along horz line vec3 Horz7(vec2 pos, float off, vec2 texture_size) { vec3 a=Fetch(pos,vec2(-3.0,off),texture_size); vec3 b=Fetch(pos,vec2(-2.0,off),texture_size); vec3 c=Fetch(pos,vec2(-1.0,off),texture_size); vec3 d=Fetch(pos,vec2( 0.0,off),texture_size); vec3 e=Fetch(pos,vec2( 1.0,off),texture_size); vec3 f=Fetch(pos,vec2( 2.0,off),texture_size); vec3 g=Fetch(pos,vec2( 3.0,off),texture_size); float dst=Dist(pos, texture_size).x; // Convert distance to weight. float scale=hardBloomPix; float wa=Gaus(dst-3.0,scale); float wb=Gaus(dst-2.0,scale); float wc=Gaus(dst-1.0,scale); float wd=Gaus(dst+0.0,scale); float we=Gaus(dst+1.0,scale); float wf=Gaus(dst+2.0,scale); float wg=Gaus(dst+3.0,scale); // Return filtered sample. return (a*wa+b*wb+c*wc+d*wd+e*we+f*wf+g*wg)/(wa+wb+wc+wd+we+wf+wg); } // Return scanline weight float Scan(vec2 pos,float off, vec2 texture_size) { float dst=Dist(pos, texture_size).y; return Gaus(dst+off,hardScan); } // Return scanline weight for bloom float BloomScan(vec2 pos,float off, vec2 texture_size) { float dst=Dist(pos, texture_size).y; return Gaus(dst+off,hardBloomScan); } // Allow nearest three lines to effect pixel vec3 Tri(vec2 pos, vec2 texture_size){ vec3 a=Horz3(pos,-1.0, texture_size); vec3 b=Horz5(pos, 0.0, texture_size); vec3 c=Horz3(pos, 1.0, texture_size); float wa=Scan(pos,-1.0, texture_size); float wb=Scan(pos, 0.0, texture_size); float wc=Scan(pos, 1.0, texture_size); return a*wa+b*wb+c*wc; } // Small bloom vec3 Bloom(vec2 pos, vec2 texture_size) { vec3 a=Horz5(pos,-2.0, texture_size); vec3 b=Horz7(pos,-1.0, texture_size); vec3 c=Horz7(pos, 0.0, texture_size); vec3 d=Horz7(pos, 1.0, texture_size); vec3 e=Horz5(pos, 2.0, texture_size); float wa=BloomScan(pos,-2.0, texture_size); float wb=BloomScan(pos,-1.0, texture_size); float wc=BloomScan(pos, 0.0, texture_size); float wd=BloomScan(pos, 1.0, texture_size); float we=BloomScan(pos, 2.0, texture_size); return a*wa+b*wb+c*wc+d*wd+e*we; } // Distortion of scanlines, and end of screen alpha vec2 Warp(vec2 pos) { pos=pos*2.0-1.0; pos*=vec2(1.0+(pos.y*pos.y)*warpX,1.0+(pos.x*pos.x)*warpY); return pos*0.5+0.5; } // Shadow mask vec3 Mask(vec2 pos) { vec3 mask = vec3(maskDark,maskDark,maskDark); pos.xy=floor(pos.xy*vec2(1.0,0.5)); pos.x+=pos.y*3.0; pos.x=fract(pos.x/6.0); if(pos.x<0.333)mask.r=maskLight; else if(pos.x<0.666)mask.g=maskLight; else mask.b=maskLight; return mask; } vec4 crt_lottes(vec2 texture_size, vec2 video_size, vec2 output_size, vec2 tex) { vec2 pos=Warp(tex.xy*(texture_size.xy/video_size.xy))*(video_size.xy/texture_size.xy); vec3 outColor = Tri(pos, texture_size); outColor.rgb+=Bloom(pos, texture_size)*bloomAmount; outColor.rgb*=Mask(floor(tex.xy*(texture_size.xy/video_size.xy)*output_size.xy)+vec2(0.5,0.5)); return vec4(ToSrgb(outColor.rgb),1.0); } void main() { fragColor = crt_lottes(vTexSize, vTexSize, vViewSize, vTexCoords); } )"; constexpr char ResizeCrtApertureGrilleFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif #define hardScan -8.0 #define hardPix -3.0 #define warpX 0.0155 #define warpY 0.0205 #define maskDark 0.7 #define maskLight 1.5 #define scaleInLinearGamma 1 #define brightboost 1.1 #define hardBloomScan -2.0 #define hardBloomPix -1.5 #define bloomAmount 1.0/16.0 #define shape 2.0 uniform sampler2D uTexture; in vec2 vTexCoords; in vec2 vTexSize; in vec2 vViewSize; out vec4 fragColor; #ifdef SIMPLE_LINEAR_GAMMA float ToLinear1(float c) { return c; } vec3 ToLinear(vec3 c) { return c; } vec3 ToSrgb(vec3 c) { return pow(c, 1.0 / 2.2); } #else float ToLinear1(float c) { if (scaleInLinearGamma==0) return c; return(c<=0.04045)?c/12.92:pow((c+0.055)/1.055,2.4); } vec3 ToLinear(vec3 c) { if (scaleInLinearGamma==0) return c; return vec3(ToLinear1(c.r),ToLinear1(c.g),ToLinear1(c.b)); } float ToSrgb1(float c) { if (scaleInLinearGamma==0) return c; return(c<0.0031308?c*12.92:1.055*pow(c,0.41666)-0.055); } vec3 ToSrgb(vec3 c) { if (scaleInLinearGamma==0) return c; return vec3(ToSrgb1(c.r),ToSrgb1(c.g),ToSrgb1(c.b)); } #endif // Nearest emulated sample given floating point position and texel offset. // Also zero's off screen. vec3 Fetch(vec2 pos, vec2 off, vec2 texture_size) { pos=(floor(pos*texture_size.xy+off)+vec2(0.5,0.5))/texture_size.xy; #ifdef SIMPLE_LINEAR_GAMMA return ToLinear(vec3(brightboost) * pow(texture(uTexture,pos.xy).rgb, 2.2)); #else return ToLinear(vec3(brightboost) * texture(uTexture,pos.xy).rgb); #endif } // Distance in emulated pixels to nearest texel vec2 Dist(vec2 pos, vec2 texture_size) { pos=pos*texture_size.xy; return -((pos-floor(pos))-vec2(0.5, 0.5)); } // 1D Gaussian float Gaus(float pos,float scale) { return exp2(scale*pow(abs(pos),shape)); } // 3-tap Gaussian filter along horz line vec3 Horz3(vec2 pos, float off, vec2 texture_size) { vec3 b=Fetch(pos,vec2(-1.0,off),texture_size); vec3 c=Fetch(pos,vec2( 0.0,off),texture_size); vec3 d=Fetch(pos,vec2( 1.0,off),texture_size); float dst=Dist(pos, texture_size).x; // Convert distance to weight. float scale=hardPix; float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); // Return filtered sample. return (b*wb+c*wc+d*wd)/(wb+wc+wd); } // 5-tap Gaussian filter along horz line vec3 Horz5(vec2 pos, float off, vec2 texture_size) { vec3 a=Fetch(pos,vec2(-2.0,off),texture_size); vec3 b=Fetch(pos,vec2(-1.0,off),texture_size); vec3 c=Fetch(pos,vec2( 0.0,off),texture_size); vec3 d=Fetch(pos,vec2( 1.0,off),texture_size); vec3 e=Fetch(pos,vec2( 2.0,off),texture_size); float dst=Dist(pos, texture_size).x; // Convert distance to weight. float scale=hardPix; float wa=Gaus(dst-2.0,scale); float wb=Gaus(dst-1.0,scale); float wc=Gaus(dst+0.0,scale); float wd=Gaus(dst+1.0,scale); float we=Gaus(dst+2.0,scale); // Return filtered sample. return (a*wa+b*wb+c*wc+d*wd+e*we)/(wa+wb+wc+wd+we); } // 7-tap Gaussian filter along horz line vec3 Horz7(vec2 pos, float off, vec2 texture_size) { vec3 a=Fetch(pos,vec2(-3.0,off),texture_size); vec3 b=Fetch(pos,vec2(-2.0,off),texture_size); vec3 c=Fetch(pos,vec2(-1.0,off),texture_size); vec3 d=Fetch(pos,vec2( 0.0,off),texture_size); vec3 e=Fetch(pos,vec2( 1.0,off),texture_size); vec3 f=Fetch(pos,vec2( 2.0,off),texture_size); vec3 g=Fetch(pos,vec2( 3.0,off),texture_size); float dst=Dist(pos, texture_size).x; // Convert distance to weight. float scale=hardBloomPix; float wa=Gaus(dst-3.0,scale); float wb=Gaus(dst-2.0,scale); float wc=Gaus(dst-1.0,scale); float wd=Gaus(dst+0.0,scale); float we=Gaus(dst+1.0,scale); float wf=Gaus(dst+2.0,scale); float wg=Gaus(dst+3.0,scale); // Return filtered sample. return (a*wa+b*wb+c*wc+d*wd+e*we+f*wf+g*wg)/(wa+wb+wc+wd+we+wf+wg); } // Return scanline weight float Scan(vec2 pos,float off, vec2 texture_size) { float dst=Dist(pos, texture_size).y; return Gaus(dst+off,hardScan); } // Return scanline weight for bloom float BloomScan(vec2 pos,float off, vec2 texture_size) { float dst=Dist(pos, texture_size).y; return Gaus(dst+off,hardBloomScan); } // Allow nearest three lines to effect pixel vec3 Tri(vec2 pos, vec2 texture_size){ vec3 a=Horz3(pos,-1.0, texture_size); vec3 b=Horz5(pos, 0.0, texture_size); vec3 c=Horz3(pos, 1.0, texture_size); float wa=Scan(pos,-1.0, texture_size); float wb=Scan(pos, 0.0, texture_size); float wc=Scan(pos, 1.0, texture_size); return a*wa+b*wb+c*wc; } // Small bloom vec3 Bloom(vec2 pos, vec2 texture_size) { vec3 a=Horz5(pos,-2.0, texture_size); vec3 b=Horz7(pos,-1.0, texture_size); vec3 c=Horz7(pos, 0.0, texture_size); vec3 d=Horz7(pos, 1.0, texture_size); vec3 e=Horz5(pos, 2.0, texture_size); float wa=BloomScan(pos,-2.0, texture_size); float wb=BloomScan(pos,-1.0, texture_size); float wc=BloomScan(pos, 0.0, texture_size); float wd=BloomScan(pos, 1.0, texture_size); float we=BloomScan(pos, 2.0, texture_size); return a*wa+b*wb+c*wc+d*wd+e*we; } // Distortion of scanlines, and end of screen alpha vec2 Warp(vec2 pos) { pos=pos*2.0-1.0; pos*=vec2(1.0+(pos.y*pos.y)*warpX,1.0+(pos.x*pos.x)*warpY); return pos*0.5+0.5; } // Shadow mask vec3 Mask(vec2 pos) { vec3 mask = vec3(maskDark,maskDark,maskDark); pos.x=fract(pos.x/3.0); if(pos.x<0.333)mask.r=maskLight; else if(pos.x<0.666)mask.g=maskLight; else mask.b=maskLight; return mask; } vec4 crt_lottes(vec2 texture_size, vec2 video_size, vec2 output_size, vec2 tex) { vec2 pos=Warp(tex.xy*(texture_size.xy/video_size.xy))*(video_size.xy/texture_size.xy); vec3 outColor = Tri(pos, texture_size); outColor.rgb+=Bloom(pos, texture_size)*bloomAmount; outColor.rgb*=Mask(floor(tex.xy*(texture_size.xy/video_size.xy)*output_size.xy)+vec2(0.5,0.5)); return vec4(ToSrgb(outColor.rgb),1.0); } void main() { fragColor = crt_lottes(vTexSize, vTexSize, vViewSize, vTexCoords); } )"; constexpr char ResizeMonochromeVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vPixelCoords; out vec2 vTexCoords; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vPixelCoords = aPosition * texRect.xy; vTexCoords = aPosition; } )"; constexpr char ResizeMonochromeFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif mat4 bayerIndex = mat4( vec4(0.0625, 0.5625, 0.1875, 0.6875), vec4(0.8125, 0.3125, 0.9375, 0.4375), vec4(0.25, 0.75, 0.125, 0.625), vec4(1.0, 0.5, 0.875, 0.375)); uniform sampler2D uTexture; in vec2 vPixelCoords; in vec2 vTexCoords; out vec4 fragColor; float dither4x4(vec2 position, float brightness) { float bayerValue = bayerIndex[int(position.x) % 4][int(position.y) % 4]; return brightness + (brightness < bayerValue ? -0.05 : 0.1); } void main() { vec3 color = texture(uTexture, vTexCoords).rgb; float gray = dot(((color - vec3(0.5)) * vec3(1.4, 1.2, 1.0)) + vec3(0.5), vec3(0.3, 0.7, 0.1)); gray = dither4x4(vPixelCoords, gray); float palette = (abs(1.0 - gray) * 0.75) + 0.125; if (palette < 0.25) { color = vec3(0.675, 0.710, 0.420); } else if (palette < 0.5) { color = vec3(0.463, 0.518, 0.283); } else if (palette < 0.75) { color = vec3(0.247, 0.314, 0.247); } else { color = vec3(0.1, 0.134, 0.151); } fragColor = vec4(color, 1.0); } )"; constexpr char AntialiasingVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vPixelCoords; out vec2 vTexSizeInv; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vPixelCoords = aPosition * texRect.xy + 0.5; vTexSizeInv = 1.0 / texRect.xy; } )"; constexpr char AntialiasingFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif uniform sampler2D uTexture; in vec2 vPixelCoords; in vec2 vTexSizeInv; out vec4 fragColor; vec3 cubicHermite(vec3 A, vec3 B, vec3 C, vec3 D, float t) { float t2 = t*t; float t3 = t*t*t; vec3 a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0; vec3 b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0; vec3 c = -A/2.0 + C/2.0; vec3 d = B; return a*t3 + b*t2 + c*t + d; } void main() { vec2 frac = fract(vPixelCoords); vec2 pixel = floor(vPixelCoords) * vTexSizeInv - vec2(vTexSizeInv * 0.5); vec2 vTexSizeInv2 = 2.0 * vTexSizeInv; vec3 C00 = texture(uTexture, pixel + vec2(-vTexSizeInv.x, -vTexSizeInv.y)).rgb; vec3 C10 = texture(uTexture, pixel + vec2(0.0, -vTexSizeInv.y)).rgb; vec3 C20 = texture(uTexture, pixel + vec2(vTexSizeInv.x, -vTexSizeInv.y)).rgb; vec3 C30 = texture(uTexture, pixel + vec2(vTexSizeInv2.x, -vTexSizeInv.y)).rgb; vec3 C01 = texture(uTexture, pixel + vec2(-vTexSizeInv.x, 0.0)).rgb; vec3 C11 = texture(uTexture, pixel + vec2(0.0, 0.0)).rgb; vec3 C21 = texture(uTexture, pixel + vec2(vTexSizeInv.x, 0.0)).rgb; vec3 C31 = texture(uTexture, pixel + vec2(vTexSizeInv2.x, 0.0)).rgb; vec3 C02 = texture(uTexture, pixel + vec2(-vTexSizeInv.x , vTexSizeInv.y)).rgb; vec3 C12 = texture(uTexture, pixel + vec2(0.0, vTexSizeInv.y)).rgb; vec3 C22 = texture(uTexture, pixel + vec2(vTexSizeInv.x , vTexSizeInv.y)).rgb; vec3 C32 = texture(uTexture, pixel + vec2(vTexSizeInv2.x, vTexSizeInv.y)).rgb; vec3 C03 = texture(uTexture, pixel + vec2(-vTexSizeInv.x, vTexSizeInv2.y)).rgb; vec3 C13 = texture(uTexture, pixel + vec2(0.0, vTexSizeInv2.y)).rgb; vec3 C23 = texture(uTexture, pixel + vec2(vTexSizeInv.x, vTexSizeInv2.y)).rgb; vec3 C33 = texture(uTexture, pixel + vec2(vTexSizeInv2.x, vTexSizeInv2.y)).rgb; vec3 CP0X = cubicHermite(C00, C10, C20, C30, frac.x); vec3 CP1X = cubicHermite(C01, C11, C21, C31, frac.x); vec3 CP2X = cubicHermite(C02, C12, C22, C32, frac.x); vec3 CP3X = cubicHermite(C03, C13, C23, C33, frac.x); fragColor = vec4(cubicHermite(CP0X, CP1X, CP2X, CP3X, frac.y), 1.0); } )"; constexpr char TransitionVs[] = "#line " DEATH_LINE_STRING "\n" R"( uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; layout (std140) uniform InstanceBlock { mat4 modelMatrix; vec4 color; vec4 texRect; vec2 spriteSize; }; out vec2 vTexCoords; out vec2 vCorrection; out float vProgressTime; void main() { vec2 aPosition = vec2(1.0 - float(gl_VertexID >> 1), float(gl_VertexID % 2)); vec4 position = vec4(aPosition.x * spriteSize.x, aPosition.y * spriteSize.y, 0.0, 1.0); gl_Position = uProjectionMatrix * uViewMatrix * modelMatrix * position; vTexCoords = vec2(aPosition.x * texRect.x + texRect.y, aPosition.y * texRect.z + texRect.w); vCorrection = spriteSize / vec2(max(spriteSize.x, spriteSize.y)); vProgressTime = color.a; } )"; constexpr char TransitionFs[] = "#line " DEATH_LINE_STRING "\n" R"( #ifdef GL_ES precision mediump float; #endif in vec2 vTexCoords; in vec2 vCorrection; in float vProgressTime; out vec4 fragColor; float rand(vec2 xy) { return fract(sin(dot(xy.xy, vec2(12.9898,78.233))) * 43758.5453); } float ease(float time) { time *= 2.0; if (time < 1.0) { return 0.5 * time * time; } time -= 1.0; return -0.5 * (time * (time - 2.0) - 1.0); } void main() { vec2 uv = (vTexCoords - vec2(0.5)) * vCorrection; float distance = length(uv); float progressInner = vProgressTime - 0.22; distance = (clamp(distance, progressInner, vProgressTime) - progressInner) / (vProgressTime - progressInner); float mixValue = ease(distance); float noise = 1.0 + rand(uv) * 0.1; fragColor = vec4(0.0, 0.0, 0.0, mixValue * noise); } )"; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/ContentResolver.cpp000066400000000000000000002244141512772601700254020ustar00rootroot00000000000000#include "ContentResolver.h" #include "ContentResolver.Shaders.h" #include "Compatibility/JJ2Anims.Palettes.h" #include "LevelFlags.h" #include "LevelHandler.h" #include "Tiles/TileSet.h" #if defined(DEATH_DEBUG) # include "Compatibility/JJ2Anims.h" #endif #include "../nCine/Application.h" #include "../nCine/AppConfiguration.h" #include "../nCine/ServiceLocator.h" #include "../nCine/tracy.h" #include "../nCine/Graphics/ITextureLoader.h" #include "../nCine/Graphics/RenderResources.h" #include "../nCine/Base/Random.h" #if defined(DEATH_TARGET_ANDROID) # include "../nCine/Backends/Android/AndroidJniHelper.h" # include #elif defined(DEATH_TARGET_WINDOWS_RT) # include #endif #include #include #include #include #include "../jsoncpp/json.h" using namespace Death::IO::Compression; using namespace Jazz2::Tiles; static Vector2i GetVector2iFromJson(const Json::Value& value, Vector2i defaultValue = Vector2i::Zero) { if (value.isArray()) { std::int64_t x = 0, y = 0; if (value[0].get(x) == Json::SUCCESS && value[1].get(y) == Json::SUCCESS) { return Vector2i((std::int32_t)x, (std::int32_t)y); } } return defaultValue; } namespace Jazz2 { ContentResolver& ContentResolver::Get() { static ContentResolver current; return current; } ContentResolver::ContentResolver() : _isHeadless(false), _isLoading(false), _cachedMetadata(64), _cachedGraphics(256), #if defined(WITH_AUDIO) _cachedSounds(192), #endif _palettes{} { InitializePaths(); } ContentResolver::~ContentResolver() { } void ContentResolver::Release() { _cachedMetadata.clear(); _cachedGraphics.clear(); #if defined(WITH_AUDIO) _cachedSounds.clear(); #endif for (std::int32_t i = 0; i < (std::int32_t)FontType::Count; i++) { _fonts[i] = nullptr; } for (std::int32_t i = 0; i < (std::int32_t)PrecompiledShader::Count; i++) { _precompiledShaders[i] = nullptr; } } StringView ContentResolver::GetContentPath() const { #if defined(DEATH_TARGET_UNIX) || defined(DEATH_TARGET_WINDOWS_RT) return _contentPath; #elif defined(DEATH_TARGET_ANDROID) return "assets:/"_s; #elif defined(DEATH_TARGET_SWITCH) return "romfs:/"_s; #elif defined(DEATH_TARGET_WINDOWS) return "Content\\"_s; #else return "Content/"_s; #endif } StringView ContentResolver::GetCachePath() const { #if defined(DEATH_TARGET_ANDROID) || defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) || defined(DEATH_TARGET_WINDOWS_RT) return _cachePath; #elif defined(DEATH_TARGET_SWITCH) // Switch has some issues with UTF-8 characters, so use "Jazz2" instead return "sdmc:/Games/Jazz2/Cache/"_s; #elif defined(DEATH_TARGET_WINDOWS) return "Cache\\"_s; #else return "Cache/"_s; #endif } StringView ContentResolver::GetSourcePath() const { #if defined(DEATH_TARGET_ANDROID) || defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) || defined(DEATH_TARGET_WINDOWS_RT) return _sourcePath; #elif defined(DEATH_TARGET_SWITCH) // Switch has some issues with UTF-8 characters, so use "Jazz2" instead return "sdmc:/Games/Jazz2/Source/"_s; #elif defined(DEATH_TARGET_WINDOWS) return "Source\\"_s; #else return "Source/"_s; #endif } bool ContentResolver::IsHeadless() const { return _isHeadless; } void ContentResolver::SetHeadless(bool value) { _isHeadless = value; } void ContentResolver::InitializePaths() { #if defined(DEATH_TARGET_ANDROID) // If `MANAGE_EXTERNAL_STORAGE` permission is granted, try to use also alternative paths if (Backends::AndroidJniWrap_Activity::hasExternalStoragePermission()) { constexpr StringView ExternalPaths[] = { "Games/Jazz² Resurrection/"_s, "Games/Jazz2 Resurrection/"_s, "Download/Jazz² Resurrection/"_s, "Download/Jazz2 Resurrection/"_s }; bool found = false; String externalStorage = fs::GetExternalStorage(); for (std::size_t i = 0; i < arraySize(ExternalPaths); i++) { String externalPath = fs::CombinePath(externalStorage, ExternalPaths[i]); _sourcePath = fs::CombinePath(externalPath, "Source/"_s); if (fs::DirectoryExists(_sourcePath)) { _cachePath = fs::CombinePath(externalPath, "Cache/"_s); found = true; break; } } if (!found) { String deviceBrand = Backends::AndroidJniClass_Version::deviceBrand(); String deviceModel = Backends::AndroidJniClass_Version::deviceModel(); if (deviceBrand == "Windows"_s && deviceModel == "Subsystem for Android(TM)"_s) { // Set special paths if Windows Subsystem for Android™ is used String externalStorageWindows = fs::CombinePath(externalStorage, "Windows/Saved Games/"_s); if (fs::DirectoryExists(externalStorageWindows)) { if (fs::DirectoryExists(fs::CombinePath(externalStorageWindows, "Jazz2 Resurrection/Source/"_s)) && !fs::DirectoryExists(fs::CombinePath(externalStorageWindows, "Jazz² Resurrection/Source/"_s))) { _sourcePath = fs::CombinePath(externalStorageWindows, "Jazz2 Resurrection/Source/"_s); _cachePath = fs::CombinePath(externalStorageWindows, "Jazz2 Resurrection/Cache/"_s); } else { _sourcePath = fs::CombinePath(externalStorageWindows, "Jazz² Resurrection/Source/"_s); _cachePath = fs::CombinePath(externalStorageWindows, "Jazz² Resurrection/Cache/"_s); } found = true; } } if (!found) { String externalPath = fs::CombinePath(externalStorage, ExternalPaths[0]); _sourcePath = fs::CombinePath(externalPath, "Source/"_s); _cachePath = fs::CombinePath(externalPath, "Cache/"_s); } } } else { StringView dataPath = AndroidAssetStream::GetExternalDataPath(); _sourcePath = fs::CombinePath(dataPath, "Source/"_s); _cachePath = fs::CombinePath(dataPath, "Cache/"_s); } #elif defined(DEATH_TARGET_APPLE) // Returns local application data directory on Apple const String& appData = fs::GetSavePath("Jazz² Resurrection"_s); _sourcePath = fs::CombinePath(appData, "Source/"_s); _cachePath = fs::CombinePath(appData, "Cache/"_s); #elif defined(DEATH_TARGET_UNIX) # if defined(NCINE_PACKAGED_CONTENT_PATH) _contentPath = "Content/"_s; # elif defined(NCINE_OVERRIDE_CONTENT_PATH) _contentPath = NCINE_OVERRIDE_CONTENT_PATH; # else _contentPath = NCINE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Content/"; # endif # if defined(NCINE_PACKAGED_CONTENT_PATH) // If Content is packaged with binaries, always use standard XDG paths for everything else auto localStorage = fs::GetLocalStorage(); if (!localStorage.empty()) { auto appData = fs::CombinePath(localStorage, NCINE_LINUX_PACKAGE); _sourcePath = fs::CombinePath(appData, "Source/"_s); _cachePath = fs::CombinePath(appData, "Cache/"_s); } else { _sourcePath = "Source/"_s; _cachePath = "Cache/"_s; } # else if (fs::DirectoryExists(_contentPath)) { // Shared Content exists, try to use standard XDG paths auto localStorage = fs::GetLocalStorage(); if (!localStorage.empty()) { // Use "$XDG_DATA_HOME/Jazz² Resurrection/" if exists (for backward compatibility), otherwise "$XDG_DATA_HOME/{NCINE_LINUX_PACKAGE}/" _cachePath = fs::CombinePath(localStorage, "Jazz² Resurrection/Cache/"_s); if (!fs::DirectoryExists(_cachePath)) { auto appData = fs::CombinePath(localStorage, NCINE_LINUX_PACKAGE); _cachePath = fs::CombinePath(appData, "Cache/"_s); } } else { _cachePath = "Cache/"_s; } // Prefer system-wide Source only if it exists and the local one doesn't exist _sourcePath = fs::CombinePath(fs::GetDirectoryName(_cachePath), "Source/"_s); if (!fs::FindPathCaseInsensitive(fs::CombinePath(_sourcePath, "Anims.j2a"_s)) && !fs::FindPathCaseInsensitive(fs::CombinePath(_sourcePath, "AnimsSw.j2a"_s))) { auto systemWideSource = NCINE_INSTALL_PREFIX "/share/" NCINE_LINUX_PACKAGE "/Source/"; if (fs::FindPathCaseInsensitive(fs::CombinePath(systemWideSource, "Anims.j2a"_s)) || fs::FindPathCaseInsensitive(fs::CombinePath(systemWideSource, "AnimsSw.j2a"_s))) { _sourcePath = systemWideSource; } } } else { // Fallback to relative paths _contentPath = "Content/"_s; _sourcePath = "Source/"_s; _cachePath = "Cache/"_s; } # endif #elif defined(DEATH_TARGET_WINDOWS_RT) bool found = false; if (Environment::CurrentDeviceType == DeviceType::Xbox) { // Try to use external drives (D:, E:, F:) on Xbox, "\\?\" path prefix is required on Xbox String PathTemplate1 = "\\\\?\\X:\\Games\\Jazz² Resurrection\\"_s; String PathTemplate2 = "\\\\?\\X:\\Games\\Jazz2 Resurrection\\"_s; for (char letter = 'D'; letter <= 'G'; letter++) { PathTemplate1[4] = letter; _sourcePath = fs::CombinePath(PathTemplate1, "Source\\"_s); if (fs::DirectoryExists(_sourcePath)) { _cachePath = fs::CombinePath(PathTemplate1, "Cache\\"_s); found = true; break; } PathTemplate2[4] = letter; _sourcePath = fs::CombinePath(PathTemplate2, "Source\\"_s); if (fs::DirectoryExists(_sourcePath)) { _cachePath = fs::CombinePath(PathTemplate2, "Cache\\"_s); found = true; break; } } } if (!found) { // Returns local application data directory on Windows RT const String& appData = fs::GetSavePath("Jazz² Resurrection"_s); _sourcePath = fs::CombinePath(appData, "Source\\"_s); _cachePath = fs::CombinePath(appData, "Cache\\"_s); } _contentPath = "Content\\"_s; #endif #if !defined(DEATH_TARGET_EMSCRIPTEN) RemountPaks(); #endif } #if !defined(DEATH_TARGET_EMSCRIPTEN) void ContentResolver::RemountPaks() { // Unload all already loaded .paks _mountedPaks.clear(); // Load all .paks from `Content` and `Cache` directory for (auto item : fs::Directory(GetContentPath(), fs::EnumerationOptions::SkipDirectories)) { auto extension = fs::GetExtension(item); if (extension != "pak"_s) { continue; } auto& pak = _mountedPaks.emplace_back(std::make_unique(item)); if (pak->IsValid()) { LOGI("File \"{}\" mounted successfully", item); } else { LOGE("Failed to mount file \"{}\"", item); _mountedPaks.pop_back(); } } for (auto item : fs::Directory(GetCachePath(), fs::EnumerationOptions::SkipDirectories)) { auto extension = fs::GetExtension(item); if (extension != "pak"_s) { continue; } auto& pak = _mountedPaks.emplace_back(std::make_unique(item)); if (pak->IsValid()) { LOGI("File \"{}\" mounted successfully", item); } else { LOGE("Failed to mount file \"{}\"", item); _mountedPaks.pop_back(); } } } #endif std::unique_ptr ContentResolver::OpenContentFile(StringView path, std::int32_t bufferSize) { // Search .paks first, then Content directory and Cache directory #if !defined(DEATH_TARGET_EMSCRIPTEN) for (std::size_t i = 0; i < _mountedPaks.size(); i++) { auto mountPoint = _mountedPaks[i]->GetMountPoint(); if (path.hasPrefix(mountPoint)) { auto packedFile = _mountedPaks[i]->OpenFile(path.exceptPrefix(mountPoint.size()), bufferSize); if (packedFile != nullptr && packedFile->IsValid()) { return packedFile; } } } #endif String fullPath = fs::CombinePath(GetContentPath(), path); if (fs::IsReadableFile(fullPath)) { auto realFile = fs::Open(fullPath, FileAccess::Read, bufferSize); if (realFile->IsValid()) { return realFile; } } fullPath = fs::CombinePath(GetCachePath(), path); return fs::Open(fullPath, FileAccess::Read, bufferSize); } void ContentResolver::BeginLoading() { _isLoading = true; // Reset Referenced flag for (auto& resource : _cachedMetadata) { resource.second->Flags &= ~MetadataFlags::Referenced; } for (auto& resource : _cachedGraphics) { resource.second->Flags &= ~GenericGraphicResourceFlags::Referenced; } #if defined(WITH_AUDIO) for (auto& resource : _cachedSounds) { resource.second->Flags &= ~GenericSoundResourceFlags::Referenced; } #endif } void ContentResolver::EndLoading() { #if defined(DEATH_DEBUG) std::int32_t metadataKept = 0, metadataReleased = 0; std::int32_t animationsKept = 0, animationsReleased = 0; std::int32_t soundsKept = 0, soundsReleased = 0; #endif // Release unreferenced metadata { auto it = _cachedMetadata.begin(); while (it != _cachedMetadata.end()) { if ((it->second->Flags & MetadataFlags::Referenced) != MetadataFlags::Referenced) { it = _cachedMetadata.erase(it); #if defined(DEATH_DEBUG) metadataReleased++; #endif } else { ++it; #if defined(DEATH_DEBUG) metadataKept++; #endif } } } // Released unreferenced graphics { auto it = _cachedGraphics.begin(); while (it != _cachedGraphics.end()) { if ((it->second->Flags & GenericGraphicResourceFlags::Referenced) != GenericGraphicResourceFlags::Referenced) { it = _cachedGraphics.erase(it); #if defined(DEATH_DEBUG) animationsReleased++; #endif } else { ++it; #if defined(DEATH_DEBUG) animationsKept++; #endif } } } #if defined(WITH_AUDIO) // Released unreferenced sounds { auto it = _cachedSounds.begin(); while (it != _cachedSounds.end()) { if ((it->second->Flags & GenericSoundResourceFlags::Referenced) != GenericSoundResourceFlags::Referenced) { it = _cachedSounds.erase(it); # if defined(DEATH_DEBUG) soundsReleased++; # endif } else { ++it; # if defined(DEATH_DEBUG) soundsKept++; # endif } } } #endif #if defined(DEATH_DEBUG) LOGW("Metadata: {}|{}, Animations: {}|{}, Sounds: {}|{}", metadataKept, metadataReleased, animationsKept, animationsReleased, soundsKept, soundsReleased); #endif _isLoading = false; } void ContentResolver::OverridePathHandler(Function&& callback) { _pathHandler = std::move(callback); } void ContentResolver::PreloadMetadataAsync(StringView path) { // TODO: Reimplement async preloading RequestMetadata(path); } Metadata* ContentResolver::RequestMetadata(StringView path) { String pathNormalized = fs::ToNativeSeparators(path); auto it = _cachedMetadata.find(pathNormalized); if (it != _cachedMetadata.end()) { // Already loaded - Mark as referenced it->second->Flags |= MetadataFlags::Referenced; for (const auto& resource : it->second->Animations) { resource.Base->Flags |= GenericGraphicResourceFlags::Referenced; } #if defined(WITH_AUDIO) for (const auto& [key, resource] : it->second->Sounds) { for (const auto& base : resource.Buffers) { base->Flags |= GenericSoundResourceFlags::Referenced; } } #endif return it->second.get(); } // Try to load it auto s = fs::Open(fs::CombinePath({ GetContentPath(), "Metadata"_s, String(pathNormalized + ".res"_s) }), FileAccess::Read); auto fileSize = s->GetSize(); if (fileSize < 4 || fileSize > 64 * 1024 * 1024) { // 64 MB file size limit return nullptr; } auto buffer = std::make_unique(fileSize); s->Read(buffer.get(), fileSize); bool multipleAnimsNoStatesWarning = false; std::unique_ptr metadata = std::make_unique(); metadata->Path = std::move(pathNormalized); metadata->Flags |= MetadataFlags::Referenced; Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + fileSize, &doc, &errors)) { metadata->BoundingBox = GetVector2iFromJson(doc["BoundingBox"], Vector2i(InvalidValue, InvalidValue)); const auto& animations = doc["Animations"]; if (animations.isObject()) { std::size_t count = animations.getMemberCount(); metadata->Animations.reserve(count); for (auto it = animations.begin(); it != animations.end(); ++it) { // TODO: Keys are not used std::string_view assetPath; if ((*it)["Path"].get(assetPath) != Json::SUCCESS || assetPath.empty()) { continue; } GraphicResource graphics; graphics.LoopMode = AnimationLoopMode::Loop; //bool keepIndexed = false; std::int64_t flags; if ((*it)["Flags"].get(flags) == Json::SUCCESS) { if ((flags & 0x01) == 0x01) { graphics.LoopMode = AnimationLoopMode::Once; } //if ((flags & 0x02) == 0x02) { // keepIndexed = true; //} } // TODO: Implement true indexed sprites std::int64_t paletteOffset; if ((*it)["PaletteOffset"].get(paletteOffset) != Json::SUCCESS || paletteOffset < 0) { paletteOffset = 0; } graphics.Base = RequestGraphics(assetPath, (std::uint16_t)paletteOffset); if (graphics.Base == nullptr) { continue; } std::int64_t frameOffset; if ((*it)["FrameOffset"].get(frameOffset) != Json::SUCCESS) { frameOffset = 0; } graphics.FrameOffset = (std::int32_t)frameOffset; graphics.AnimDuration = graphics.Base->AnimDuration; graphics.FrameCount = graphics.Base->FrameCount; std::int64_t frameCount; if ((*it)["FrameCount"].get(frameCount) == Json::SUCCESS) { graphics.FrameCount = (std::int32_t)frameCount; } else { graphics.FrameCount -= graphics.FrameOffset; } // TODO: Use AnimDuration instead double frameRate; if ((*it)["FrameRate"].get(frameRate) == Json::SUCCESS) { graphics.AnimDuration = (frameRate <= 0 ? -1.0f : (1.0f / (float)frameRate) * 5.0f); } // If no bounding box is provided, use the first sprite if (metadata->BoundingBox == Vector2i(InvalidValue, InvalidValue)) { // TODO: Remove this bounding box reduction metadata->BoundingBox = graphics.Base->FrameDimensions - Vector2i(2, 2); } const auto& states = (*it)["States"]; if (states.isArray()) { for (const auto& stateItem : states) { std::int64_t state; if (stateItem.get(state) == Json::SUCCESS) { #if defined(DEATH_DEBUG) // Additional checks only for Debug configuration for (const auto& anim : metadata->Animations) { if (anim.State == (AnimState)state) { LOGW("Animation state {} defined twice in file \"{}\"", state, path); break; } } #endif graphics.State = (AnimState)state; metadata->Animations.push_back(graphics); } } } else if (count > 1) { if (!multipleAnimsNoStatesWarning) { multipleAnimsNoStatesWarning = true; LOGW("Multiple animations defined but no states specified in file \"{}\"", path); } } else { graphics.State = AnimState::Default; metadata->Animations.push_back(graphics); } } // Animation states must be sorted, so binary search can be used nCine::sort(metadata->Animations.begin(), metadata->Animations.end()); } const auto& sounds = doc["Sounds"]; if (sounds.isObject()) { std::size_t count = sounds.getMemberCount(); metadata->Sounds.reserve(count); for (auto it = sounds.begin(); it != sounds.end(); ++it) { std::string_view key = it.memberName(); const auto& assetPaths = (*it)["Paths"]; if (key.empty() || !assetPaths.isArray() || assetPaths.empty()) { continue; } SoundResource sound; #if defined(WITH_AUDIO) // Don't load sounds in headless mode if (!_isHeadless) { for (auto assetPathItem : assetPaths) { std::string_view assetPath; if (assetPathItem.get(assetPath) == Json::SUCCESS && !assetPath.empty()) { auto assetPathNormalized = fs::ToNativeSeparators(assetPath); auto it = _cachedSounds.find(assetPathNormalized); if (it != _cachedSounds.end()) { it->second->Flags |= GenericSoundResourceFlags::Referenced; sound.Buffers.push_back(it->second.get()); } else { auto s = OpenContentFile(fs::CombinePath("Animations"_s, assetPathNormalized)); auto res = _cachedSounds.emplace(assetPathNormalized, std::make_unique(std::move(s), assetPathNormalized)); res.first->second->Flags |= GenericSoundResourceFlags::Referenced; sound.Buffers.push_back(res.first->second.get()); } } } } #endif metadata->Sounds.emplace(key, std::move(sound)); } } } return _cachedMetadata.emplace(metadata->Path, std::move(metadata)).first->second.get(); } GenericGraphicResource* ContentResolver::RequestGraphics(StringView path, std::uint16_t paletteOffset) { // First resources are requested, reset _isLoading flag, because palette should be already applied _isLoading = false; auto pathNormalized = fs::ToNativeSeparators(path); auto it = _cachedGraphics.find(Pair(String::nullTerminatedView(pathNormalized), paletteOffset)); if (it != _cachedGraphics.end()) { // Already loaded - Mark as referenced it->second->Flags |= GenericGraphicResourceFlags::Referenced; return it->second.get(); } if (fs::GetExtension(pathNormalized) == "aura"_s) { return RequestGraphicsAura(pathNormalized, paletteOffset); } auto s = fs::Open(fs::CombinePath({ GetContentPath(), "Animations"_s, String(pathNormalized + ".res"_s) }), FileAccess::Read); auto fileSize = s->GetSize(); if (fileSize < 4 || fileSize > 64 * 1024 * 1024) { // 64 MB file size limit, also if not found try to use cache return nullptr; } auto buffer = std::make_unique(fileSize); s->Read(buffer.get(), fileSize); s->Dispose(); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + fileSize, &doc, &errors)) { // Try to load it std::unique_ptr graphics = std::make_unique(); graphics->Flags |= GenericGraphicResourceFlags::Referenced; String fullPath = fs::CombinePath({ GetContentPath(), "Animations"_s, pathNormalized }); std::unique_ptr texLoader = ITextureLoader::createFromFile(fullPath); if (texLoader->hasLoaded()) { auto texFormat = texLoader->texFormat().internalFormat(); if (texFormat != GL_RGBA8 && texFormat != GL_RGB8) { return nullptr; } std::int32_t w = texLoader->width(); std::int32_t h = texLoader->height(); std::uint8_t* pixels = (std::uint8_t*)texLoader->pixels(); const std::uint32_t* palette = _palettes + paletteOffset; bool linearSampling = false; bool needsMask = true; std::int64_t flags; if (doc["Flags"].get(flags) == Json::SUCCESS) { // Palette already applied, keep as is if ((flags & 0x01) != 0x01) { palette = nullptr; // TODO: Apply linear sampling only to these images if ((flags & 0x02) == 0x02) { linearSampling = true; } } if ((flags & 0x08) == 0x08) { needsMask = false; } } if (needsMask) { graphics->Mask = std::make_unique(w * h); for (std::int32_t i = 0; i < w * h; i++) { // Save original alpha value for collision checking graphics->Mask[i] = pixels[(i * PixelSize) + 3]; } } if (palette != nullptr) { for (std::uint32_t i = 0; i < w * h; i++) { std::uint32_t srcIdx = i * PixelSize; std::uint32_t color = palette[pixels[srcIdx]]; std::uint8_t alpha = pixels[srcIdx + 3]; std::uint8_t r = (color >> 0) & 0xFF; std::uint8_t g = (color >> 8) & 0xFF; std::uint8_t b = (color >> 16) & 0xFF; std::uint8_t a = ((color >> 24) & 0xFF) * alpha / 255; pixels[srcIdx + 0] = r; pixels[srcIdx + 1] = g; pixels[srcIdx + 2] = b; pixels[srcIdx + 3] = a; } } if (!_isHeadless) { // Don't load textures in headless mode, only collision masks graphics->TextureDiffuse = std::make_unique(fullPath.data(), Texture::Format::RGBA8, w, h); graphics->TextureDiffuse->LoadFromTexels(pixels, 0, 0, w, h); graphics->TextureDiffuse->SetMinFiltering(linearSampling ? SamplerFilter::Linear : SamplerFilter::Nearest); graphics->TextureDiffuse->SetMagFiltering(linearSampling ? SamplerFilter::Linear : SamplerFilter::Nearest); } double animDuration; if (doc["Duration"].get(animDuration) != Json::SUCCESS) { animDuration = 0.0; } graphics->AnimDuration = (float)animDuration; std::int64_t frameCount; if (doc["FrameCount"].get(frameCount) != Json::SUCCESS) { frameCount = 0; } graphics->FrameCount = (std::int32_t)frameCount; graphics->FrameDimensions = GetVector2iFromJson(doc["FrameSize"]); graphics->FrameConfiguration = GetVector2iFromJson(doc["FrameConfiguration"]); graphics->Hotspot = GetVector2iFromJson(doc["Hotspot"]); graphics->Coldspot = GetVector2iFromJson(doc["Coldspot"], Vector2i(InvalidValue, InvalidValue)); graphics->Gunspot = GetVector2iFromJson(doc["Gunspot"], Vector2i(InvalidValue, InvalidValue)); #if defined(DEATH_DEBUG) MigrateGraphics(pathNormalized); #endif return _cachedGraphics.emplace(Pair(String(pathNormalized), paletteOffset), std::move(graphics)).first->second.get(); } } return nullptr; } GenericGraphicResource* ContentResolver::RequestGraphicsAura(StringView path, std::uint16_t paletteOffset) { auto s = OpenContentFile(fs::CombinePath("Animations"_s, path)); auto fileSize = s->GetSize(); if (fileSize < 16 || fileSize > 64 * 1024 * 1024) { // 64 MB file size limit, also if not found try to use cache return nullptr; } std::uint64_t signature1 = s->ReadValueAsLE(); std::uint32_t signature2 = s->ReadValueAsLE(); std::uint8_t version = s->ReadValue(); std::uint8_t flags = s->ReadValue(); if (signature1 != 0xB8EF8498E2BFBBEF || signature2 != 0x208F || version != 2 || (flags & 0x80) != 0x80) { return nullptr; } std::uint8_t channelCount = s->ReadValue(); std::uint32_t frameDimensionsX = s->ReadValueAsLE(); std::uint32_t frameDimensionsY = s->ReadValueAsLE(); std::uint8_t frameConfigurationX = s->ReadValue(); std::uint8_t frameConfigurationY = s->ReadValue(); std::uint16_t frameCount = s->ReadValueAsLE(); std::uint16_t animDuration = s->ReadValueAsLE(); std::uint16_t hotspotX = s->ReadValueAsLE(); std::uint16_t hotspotY = s->ReadValueAsLE(); std::uint16_t coldspotX = s->ReadValueAsLE(); std::uint16_t coldspotY = s->ReadValueAsLE(); std::uint16_t gunspotX = s->ReadValueAsLE(); std::uint16_t gunspotY = s->ReadValueAsLE(); std::uint32_t width = frameDimensionsX * frameConfigurationX; std::uint32_t height = frameDimensionsY * frameConfigurationY; std::unique_ptr pixels = std::make_unique(width * height * PixelSize); ReadImageFromFile(s, pixels.get(), width, height, channelCount); std::unique_ptr graphics = std::make_unique(); graphics->Flags |= GenericGraphicResourceFlags::Referenced; const std::uint32_t* palette = _palettes + paletteOffset; bool linearSampling = false; bool needsMask = true; if ((flags & 0x01) == 0x01) { palette = nullptr; linearSampling = true; } if ((flags & 0x02) == 0x02) { needsMask = false; } if (needsMask) { graphics->Mask = std::make_unique(width * height); for (std::uint32_t i = 0; i < width * height; i++) { // Save original alpha value for collision checking graphics->Mask[i] = pixels[(i * PixelSize) + 3]; } } if (palette != nullptr) { for (std::uint32_t i = 0; i < width * height; i++) { std::uint32_t srcIdx = i * PixelSize; std::uint32_t color = palette[pixels[srcIdx]]; std::uint8_t alpha = pixels[srcIdx + 3]; std::uint8_t r = (color >> 0) & 0xFF; std::uint8_t g = (color >> 8) & 0xFF; std::uint8_t b = (color >> 16) & 0xFF; std::uint8_t a = ((color >> 24) & 0xFF) * alpha / 255; pixels[srcIdx + 0] = r; pixels[srcIdx + 1] = g; pixels[srcIdx + 2] = b; pixels[srcIdx + 3] = a; } } if (!_isHeadless) { // Don't load textures in headless mode, only collision masks graphics->TextureDiffuse = std::make_unique(path.data(), Texture::Format::RGBA8, width, height); graphics->TextureDiffuse->LoadFromTexels(pixels.get(), 0, 0, width, height); graphics->TextureDiffuse->SetMinFiltering(linearSampling ? SamplerFilter::Linear : SamplerFilter::Nearest); graphics->TextureDiffuse->SetMagFiltering(linearSampling ? SamplerFilter::Linear : SamplerFilter::Nearest); } // AnimDuration is multiplied by 256 before saving, so divide it here back graphics->AnimDuration = animDuration / 256.0f; graphics->FrameDimensions = Vector2i(frameDimensionsX, frameDimensionsY); graphics->FrameConfiguration = Vector2i(frameConfigurationX, frameConfigurationY); graphics->FrameCount = frameCount; if (hotspotX != UINT16_MAX || hotspotY != UINT16_MAX) { graphics->Hotspot = Vector2i(hotspotX, hotspotY); } else { graphics->Hotspot = Vector2i(); } if (coldspotX != UINT16_MAX || coldspotY != UINT16_MAX) { graphics->Coldspot = Vector2i(coldspotX, coldspotY); } else { graphics->Coldspot = Vector2i(InvalidValue, InvalidValue); } if (gunspotX != UINT16_MAX || gunspotY != UINT16_MAX) { graphics->Gunspot = Vector2i(gunspotX, gunspotY); } else { graphics->Gunspot = Vector2i(InvalidValue, InvalidValue); } return _cachedGraphics.emplace(Pair(String(path), paletteOffset), std::move(graphics)).first->second.get(); } void ContentResolver::ReadImageFromFile(std::unique_ptr& s, std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount) { typedef union { struct { unsigned char r, g, b, a; } rgba; unsigned int v; } rgba_t; #define QOI_OP_INDEX 0x00 /* 00xxxxxx */ #define QOI_OP_DIFF 0x40 /* 01xxxxxx */ #define QOI_OP_LUMA 0x80 /* 10xxxxxx */ #define QOI_OP_RUN 0xc0 /* 11xxxxxx */ #define QOI_OP_RGB 0xfe /* 11111110 */ #define QOI_OP_RGBA 0xff /* 11111111 */ #define QOI_MASK_2 0xc0 /* 11000000 */ #define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11) rgba_t index[64] { }; rgba_t px; std::int32_t run = 0; std::int32_t px_len = width * height * channelCount; px.rgba.r = 0; px.rgba.g = 0; px.rgba.b = 0; px.rgba.a = 255; for (std::int32_t px_pos = 0; px_pos < px_len; px_pos += channelCount) { if (run > 0) { run--; } else { std::int32_t b1 = s->ReadValue(); if (b1 == QOI_OP_RGB) { px.rgba.r = s->ReadValue(); px.rgba.g = s->ReadValue(); px.rgba.b = s->ReadValue(); } else if (b1 == QOI_OP_RGBA) { px.rgba.r = s->ReadValue(); px.rgba.g = s->ReadValue(); px.rgba.b = s->ReadValue(); px.rgba.a = s->ReadValue(); } else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX) { px = index[b1]; } else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF) { px.rgba.r += ((b1 >> 4) & 0x03) - 2; px.rgba.g += ((b1 >> 2) & 0x03) - 2; px.rgba.b += (b1 & 0x03) - 2; } else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA) { std::int32_t b2 = s->ReadValue(); std::int32_t vg = (b1 & 0x3f) - 32; px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f); px.rgba.g += vg; px.rgba.b += vg - 8 + (b2 & 0x0f); } else if ((b1 & QOI_MASK_2) == QOI_OP_RUN) { run = (b1 & 0x3f); } index[QOI_COLOR_HASH(px) & (64 - 1)] = px; } *(rgba_t*)(data + px_pos) = px; } } void ContentResolver::ExpandTileDiffuse(std::uint8_t* pixelsOffset, std::uint32_t widthWithPadding) { // Top for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { std::uint32_t from = (1 * widthWithPadding + (x + 1)) * PixelSize; std::uint32_t to = (0 * widthWithPadding + (x + 1)) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } // Bottom for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { std::uint32_t from = (TileSet::DefaultTileSize * widthWithPadding + (x + 1)) * PixelSize; std::uint32_t to = ((TileSet::DefaultTileSize + 1) * widthWithPadding + (x + 1)) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } // Left for (std::uint32_t y = 0; y < TileSet::DefaultTileSize; y++) { std::uint32_t from = ((y + 1) * widthWithPadding + 1) * PixelSize; std::uint32_t to = ((y + 1) * widthWithPadding + 0) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } // Right for (std::uint32_t y = 0; y < TileSet::DefaultTileSize; y++) { std::uint32_t from = ((y + 1) * widthWithPadding + TileSet::DefaultTileSize) * PixelSize; std::uint32_t to = ((y + 1) * widthWithPadding + (TileSet::DefaultTileSize + 1)) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } // Corners (TL, TR, BL, BR) { std::uint32_t from = (0 * widthWithPadding + 1) * PixelSize; std::uint32_t to = (0 * widthWithPadding + 0) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } { std::uint32_t from = (0 * widthWithPadding + TileSet::DefaultTileSize) * PixelSize; std::uint32_t to = (0 * widthWithPadding + (TileSet::DefaultTileSize + 1)) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } { std::uint32_t from = ((TileSet::DefaultTileSize + 1) * widthWithPadding + 1) * PixelSize; std::uint32_t to = ((TileSet::DefaultTileSize + 1) * widthWithPadding + 0) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } { std::uint32_t from = ((TileSet::DefaultTileSize + 1) * widthWithPadding + TileSet::DefaultTileSize) * PixelSize; std::uint32_t to = ((TileSet::DefaultTileSize + 1) * widthWithPadding + (TileSet::DefaultTileSize + 1)) * PixelSize; std::memcpy(&pixelsOffset[to], &pixelsOffset[from], PixelSize); } } std::unique_ptr ContentResolver::RequestTileSet(StringView path, std::uint16_t captionTileId, bool applyPalette, const std::uint8_t* paletteRemapping) { // Try "Content" directory first, then "Cache" directory String fullPath; if (_pathHandler) { fullPath = _pathHandler(String(path + ".j2t"_s)); } if (fullPath.empty()) { fullPath = fs::CombinePath({ GetContentPath(), "Tilesets"_s, String(path + ".j2t"_s) }); if (!fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ GetCachePath(), "Tilesets"_s, String(path + ".j2t"_s) }); } } auto s = fs::Open(fullPath, FileAccess::Read, 16 * 1024); if (!s->IsValid()) { return nullptr; } std::uint64_t signature1 = s->ReadValueAsLE(); std::uint16_t signature2 = s->ReadValueAsLE(); std::uint8_t version = s->ReadValue(); /*std::uint8_t flags =*/ s->ReadValue(); DEATH_ASSERT(signature1 == 0xB8EF8498E2BFBBEF && signature2 == 0x208F && version == 2, ("Tile set \"{}\" has invalid signature", fullPath), nullptr); // TODO: Use single channel instead std::uint8_t channelCount = s->ReadValue(); std::uint32_t width = s->ReadValueAsLE(); std::uint32_t height = s->ReadValueAsLE(); std::uint16_t tileCount = s->ReadValueAsLE(); // Read compressed palette and mask std::int32_t compressedSize = s->ReadValueAsLE(); DeflateStream uc(*s, compressedSize); // Palette if (applyPalette && !_isHeadless) { std::uint32_t newPalette[ColorsPerPalette]; for (std::size_t i = 0; i < arraySize(newPalette); i++) { newPalette[i] = uc.ReadValueAsLE(); } if (std::memcmp(_palettes, newPalette, ColorsPerPalette * sizeof(std::uint32_t)) != 0) { // Palettes differs, drop all cached resources, so it will be reloaded with new palette if (_isLoading) { #if defined(DEATH_DEBUG) LOGW("Releasing all animations because of different palette - Metadata: 0|{}, Animations: 0|{}", _cachedMetadata.size(), _cachedGraphics.size()); #endif _cachedMetadata.clear(); _cachedGraphics.clear(); for (std::int32_t i = 0; i < (std::int32_t)FontType::Count; i++) { _fonts[i] = nullptr; } } std::memcpy(_palettes, newPalette, ColorsPerPalette * sizeof(std::uint32_t)); RecreateGemPalettes(); } } else { uc.Seek(ColorsPerPalette * sizeof(std::uint32_t), SeekOrigin::Current); } // Mark individual tiles as 32-bit or 8-bit std::unique_ptr is32bitTile; if (!_isHeadless) { is32bitTile = std::make_unique((tileCount + 7) / 8); uc.Read(is32bitTile.get(), (tileCount + 7) / 8); } else { uc.Seek((tileCount + 7) / 8, SeekOrigin::Current); } // Mask std::uint32_t maskSize = uc.ReadValueAsLE(); std::unique_ptr mask = std::make_unique(maskSize * 8); for (std::uint32_t j = 0; j < maskSize; j++) { std::uint8_t idx = uc.ReadValue(); for (std::uint32_t k = 0; k < 8; k++) { std::uint32_t pixelIdx = 8 * j + k; mask[pixelIdx] = (((idx >> k) & 0x01) != 0); } } std::unique_ptr textureDiffuse; std::unique_ptr captionTile; if (!_isHeadless) { // Don't load textures in headless mode, only collision masks // Load raw pixels from file std::unique_ptr pixels = std::make_unique(width * height * 4); ReadImageFromFile(s, pixels.get(), width, height, channelCount); // Then add 1px padding to each tile std::uint32_t tilesPerRow = width / TileSet::DefaultTileSize; std::uint32_t tilesPerColumn = height / TileSet::DefaultTileSize; std::uint32_t widthWithPadding = width + (2 * tilesPerRow); std::uint32_t heightWithPadding = height + (2 * tilesPerColumn); std::unique_ptr pixelsWithPadding = std::make_unique(widthWithPadding * heightWithPadding * 4); for (uint32_t i = 0; i < tilesPerColumn; i++) { std::uint32_t yf = i * TileSet::DefaultTileSize; std::uint32_t yt = i * (TileSet::DefaultTileSize + 2); for (uint32_t j = 0; j < tilesPerRow; j++) { std::uint32_t xf = j * TileSet::DefaultTileSize; std::uint32_t xt = j * (TileSet::DefaultTileSize + 2); std::uint8_t* dstTile = &pixelsWithPadding[(yt * widthWithPadding + xt) * 4]; std::int32_t tileIdx = i * tilesPerRow + j; if ((is32bitTile[tileIdx / 8] & (1 << (tileIdx & 7))) != 0) { // 32-bit tile for (std::uint32_t y = 0; y < TileSet::DefaultTileSize; y++) { for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { std::uint32_t srcIdx = ((yf + y) * width + (xf + x)) * 4; std::uint32_t dstIdx = ((y + 1) * widthWithPadding + (x + 1)) * 4; dstTile[dstIdx + 0] = pixels[srcIdx + 0]; // R dstTile[dstIdx + 1] = pixels[srcIdx + 1]; // G dstTile[dstIdx + 2] = pixels[srcIdx + 2]; // B dstTile[dstIdx + 3] = pixels[srcIdx + 3]; // A } } } else if (paletteRemapping != nullptr) { // Remapped 8-bit tile for (std::uint32_t y = 0; y < TileSet::DefaultTileSize; y++) { for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { std::uint32_t srcIdx = ((yf + y) * width + (xf + x)) * 4; std::uint32_t dstIdx = ((y + 1) * widthWithPadding + (x + 1)) * 4; std::uint32_t color = _palettes[paletteRemapping[pixels[srcIdx]]]; std::uint32_t alpha = pixels[srcIdx + 3]; dstTile[dstIdx + 0] = (color >> 0) & 0xFF; dstTile[dstIdx + 1] = (color >> 8) & 0xFF; dstTile[dstIdx + 2] = (color >> 16) & 0xFF; dstTile[dstIdx + 3] = ((color >> 24) & 0xFF) * alpha / 255; } } } else { // Plain 8-bit tile for (std::uint32_t y = 0; y < TileSet::DefaultTileSize; y++) { for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { std::uint32_t srcIdx = ((yf + y) * width + (xf + x)) * 4; std::uint32_t dstIdx = ((y + 1) * widthWithPadding + (x + 1)) * 4; std::uint32_t color = _palettes[pixels[srcIdx]]; std::uint32_t alpha = pixels[srcIdx + 3]; dstTile[dstIdx + 0] = (color >> 0) & 0xFF; dstTile[dstIdx + 1] = (color >> 8) & 0xFF; dstTile[dstIdx + 2] = (color >> 16) & 0xFF; dstTile[dstIdx + 3] = ((color >> 24) & 0xFF) * alpha / 255; } } } ExpandTileDiffuse(dstTile, widthWithPadding); } } textureDiffuse = std::make_unique(fullPath.data(), Texture::Format::RGBA8, widthWithPadding, heightWithPadding); textureDiffuse->LoadFromTexels((std::uint8_t*)pixelsWithPadding.get(), 0, 0, widthWithPadding, heightWithPadding); textureDiffuse->SetMinFiltering(SamplerFilter::Nearest); textureDiffuse->SetMagFiltering(SamplerFilter::Nearest); // Caption Tile if (captionTileId > 0) { std::uint32_t tilesPerRow = width / TileSet::DefaultTileSize; std::uint32_t tx = (captionTileId % tilesPerRow) * TileSet::DefaultTileSize; std::uint32_t ty = (captionTileId / tilesPerRow) * TileSet::DefaultTileSize; if (tx + TileSet::DefaultTileSize <= width && ty + TileSet::DefaultTileSize <= height) { captionTile = std::make_unique(TileSet::DefaultTileSize * TileSet::DefaultTileSize / 3); for (std::uint32_t y = 0; y < TileSet::DefaultTileSize / 3; y++) { for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { // Read 3 rows of pixels and average std::uint32_t idx1 = ((ty + y * 3) * width + (tx + x)) * 4; std::uint32_t idx2 = ((ty + y * 3 + 1) * width + (tx + x)) * 4; std::uint32_t idx3 = ((ty + y * 3 + 2) * width + (tx + x)) * 4; std::uint8_t r = (pixels[idx1 + 0] + pixels[idx2 + 0] + pixels[idx3 + 0]) / 3; std::uint8_t g = (pixels[idx1 + 1] + pixels[idx2 + 1] + pixels[idx3 + 1]) / 3; std::uint8_t b = (pixels[idx1 + 2] + pixels[idx2 + 2] + pixels[idx3 + 2]) / 3; captionTile[y * TileSet::DefaultTileSize + x] = Color(r, g, b); } } } } } if (!uc.IsValid()) { return nullptr; } return std::make_unique(path, tileCount, std::move(textureDiffuse), std::move(mask), maskSize * 8, std::move(captionTile)); } bool ContentResolver::LevelExists(StringView levelName) { // Try "Content" directory first, then "Cache" directory auto pathNormalized = fs::ToNativeSeparators(levelName); return (fs::IsReadableFile(fs::CombinePath({ GetContentPath(), "Episodes"_s, String(pathNormalized + ".j2l"_s) })) || fs::IsReadableFile(fs::CombinePath({ GetCachePath(), "Episodes"_s, String(pathNormalized + ".j2l"_s) }))); } bool ContentResolver::TryLoadLevel(StringView path, GameDifficulty difficulty, LevelDescriptor& descriptor) { // Try "Content" directory first, then "Cache" directory auto pathNormalized = fs::ToNativeSeparators(path); if (_pathHandler) { descriptor.FullPath = _pathHandler(String(pathNormalized + ".j2l"_s)); } if (descriptor.FullPath.empty()) { descriptor.FullPath = fs::CombinePath({ GetContentPath(), "Episodes"_s, String(pathNormalized + ".j2l"_s) }); if (!fs::IsReadableFile(descriptor.FullPath)) { descriptor.FullPath = fs::CombinePath({ GetCachePath(), "Episodes"_s, String(pathNormalized + ".j2l"_s) }); } } auto s = fs::Open(descriptor.FullPath, FileAccess::Read, 16 * 1024); if (!s->IsValid()) return false; std::uint64_t signature = s->ReadValueAsLE(); std::uint8_t fileType = s->ReadValue(); DEATH_ASSERT(signature == 0x2095A59FF0BFBBEF && fileType == LevelFile, ("Level \"{}\" has invalid signature", descriptor.FullPath), false); LevelFlags flags = (LevelFlags)s->ReadValueAsLE(); // Read compressed data std::int32_t compressedSize = s->ReadValueAsLE(); DeflateStream uc(*s, compressedSize); // Read metadata std::uint8_t stringSize = uc.ReadValue(); descriptor.DisplayName = String(NoInit, stringSize); uc.Read(descriptor.DisplayName.data(), stringSize); stringSize = uc.ReadValue(); descriptor.NextLevel = String(NoInit, stringSize); uc.Read(descriptor.NextLevel.data(), stringSize); stringSize = uc.ReadValue(); descriptor.SecretLevel = String(NoInit, stringSize); uc.Read(descriptor.SecretLevel.data(), stringSize); stringSize = uc.ReadValue(); descriptor.BonusLevel = String(NoInit, stringSize); uc.Read(descriptor.BonusLevel.data(), stringSize); // Default Tileset stringSize = uc.ReadValue(); String defaultTileset(NoInit, stringSize); uc.Read(defaultTileset.data(), stringSize); // Default Music stringSize = uc.ReadValue(); descriptor.MusicPath = String(NoInit, stringSize); uc.Read(descriptor.MusicPath.data(), stringSize); uint32_t rawAmbientColor = uc.ReadValueAsLE(); descriptor.AmbientColor = Vector4f((rawAmbientColor & 0xff) / 255.0f, ((rawAmbientColor >> 8) & 0xff) / 255.0f, ((rawAmbientColor >> 16) & 0xff) / 255.0f, ((rawAmbientColor >> 24) & 0xff) / 255.0f); descriptor.Weather = (WeatherType)uc.ReadValue(); descriptor.WeatherIntensity = uc.ReadValue(); descriptor.WaterLevel = uc.ReadValueAsLE(); std::uint16_t captionTileId = uc.ReadValueAsLE(); PitType pitType; if ((flags & LevelFlags::HasPit) == LevelFlags::HasPit) { pitType = ((flags & LevelFlags::HasPitInstantDeath) == LevelFlags::HasPitInstantDeath ? PitType::InstantDeathPit : PitType::FallForever); } else { pitType = PitType::StandOnPlatform; } bool hasCustomPalette = ((flags & LevelFlags::UseLevelPalette) == LevelFlags::UseLevelPalette); if (hasCustomPalette) { std::uint32_t newPalette[ColorsPerPalette]; for (std::size_t i = 0; i < arraySize(newPalette); i++) { newPalette[i] = uc.ReadValueAsLE(); } if (!_isHeadless && std::memcmp(_palettes, newPalette, ColorsPerPalette * sizeof(std::uint32_t)) != 0) { // Palettes differs, drop all cached resources, so it will be reloaded with new palette if (_isLoading) { _cachedMetadata.clear(); _cachedGraphics.clear(); for (std::int32_t i = 0; i < (std::int32_t)FontType::Count; i++) { _fonts[i] = nullptr; } } std::memcpy(_palettes, newPalette, ColorsPerPalette * sizeof(std::uint32_t)); RecreateGemPalettes(); } } std::uint8_t additionalPaletteCount = uc.ReadValue(); for (std::int32_t i = 0; i < additionalPaletteCount; i++) { std::uint8_t nameLength = uc.ReadValue(); String name(NoInit, nameLength); uc.Read(name.data(), nameLength); std::uint32_t palette[ColorsPerPalette]; for (std::size_t i = 0; i < arraySize(palette); i++) { palette[i] = uc.ReadValueAsLE(); } // TODO: Store and use the palette (if not headless) } descriptor.TileMap = std::make_unique(defaultTileset, captionTileId, !hasCustomPalette); descriptor.TileMap->SetPitType(pitType); // Extra Tilesets std::uint8_t extraTilesetCount = uc.ReadValue(); for (std::uint32_t i = 0; i < extraTilesetCount; i++) { std::uint8_t tilesetFlags = uc.ReadValue(); stringSize = uc.ReadValue(); String extraTileset{NoInit, stringSize}; uc.Read(extraTileset.data(), stringSize); std::uint16_t offset = uc.ReadValueAsLE(); std::uint16_t count = uc.ReadValueAsLE(); std::uint8_t paletteRemapping[ColorsPerPalette]; bool isRemapped = ((tilesetFlags & 0x01) == 0x01); bool is24bit = ((tilesetFlags & 0x02) == 0x02); if (isRemapped) { if (is24bit) { // Alternate palette index paletteRemapping[0] = uc.ReadValue(); } else { uc.Read(paletteRemapping, sizeof(paletteRemapping)); } } descriptor.TileMap->AddTileSet(extraTileset, offset, count, isRemapped ? paletteRemapping : nullptr); } if (!descriptor.TileMap->IsValid()) { // Cannot load one of required tilesets (errors already logged by TileMap) return false; } // Overriden tiles (diffuse and mask) std::uint32_t overridenTilesCount = uc.ReadVariableUint32(); if (!_isHeadless) { for (std::uint32_t i = 0; i < overridenTilesCount; i++) { std::uint16_t tileId = uc.ReadValueAsLE(); std::uint8_t tileDiffuseRaw[TileSet::DefaultTileSize * TileSet::DefaultTileSize]; uc.Read(tileDiffuseRaw, sizeof(tileDiffuseRaw)); std::uint32_t tileDiffuse[(TileSet::DefaultTileSize + 2) * (TileSet::DefaultTileSize + 2)]; for (std::uint32_t y = 0; y < TileSet::DefaultTileSize; y++) { for (std::uint32_t x = 0; x < TileSet::DefaultTileSize; x++) { std::uint32_t from = y * TileSet::DefaultTileSize + x; std::uint32_t to = (y + 1) * (TileSet::DefaultTileSize + 2) + (x + 1); std::uint32_t color = _palettes[tileDiffuseRaw[from]]; tileDiffuse[to] = color; } } ExpandTileDiffuse((std::uint8_t*)tileDiffuse, TileSet::DefaultTileSize + 2); descriptor.TileMap->OverrideTileDiffuse(tileId, tileDiffuse); } } else { uc.Seek(overridenTilesCount * (2 + TileSet::DefaultTileSize * TileSet::DefaultTileSize), SeekOrigin::Current); } overridenTilesCount = uc.ReadVariableUint32(); for (std::uint32_t i = 0; i < overridenTilesCount; i++) { std::uint16_t tileId = uc.ReadValueAsLE(); std::uint8_t tileMask[32 * 32]; uc.Read(tileMask, sizeof(tileMask)); descriptor.TileMap->OverrideTileMask(tileId, tileMask); } // Text Event Strings std::uint8_t textEventStringsCount = uc.ReadValue(); descriptor.LevelTexts.reserve(textEventStringsCount); for (std::uint32_t i = 0; i < textEventStringsCount; i++) { std::uint16_t textLength = uc.ReadValueAsLE(); String& text = descriptor.LevelTexts.emplace_back(NoInit, textLength); uc.Read(text.data(), textLength); } // Animated Tiles descriptor.TileMap->ReadAnimatedTiles(uc); // Layers std::uint8_t layerCount = uc.ReadValue(); for (std::uint32_t i = 0; i < layerCount; i++) { descriptor.TileMap->ReadLayerConfiguration(uc); } // Events descriptor.EventMap = std::make_unique(descriptor.TileMap->GetSize()); descriptor.EventMap->SetPitType(pitType); descriptor.EventMap->ReadEvents(uc, descriptor.TileMap, difficulty); DEATH_ASSERT(uc.IsValid(), "File cannot be decompressed", false); return true; } void ContentResolver::ApplyDefaultPalette() { static_assert(sizeof(SpritePalette) == ColorsPerPalette * sizeof(std::uint32_t)); if (!_isHeadless && std::memcmp(_palettes, SpritePalette, ColorsPerPalette * sizeof(std::uint32_t)) != 0) { // Palettes differs, drop all cached resources, so it will be reloaded with new palette if (_isLoading) { _cachedMetadata.clear(); _cachedGraphics.clear(); for (std::int32_t i = 0; i < (std::int32_t)FontType::Count; i++) { _fonts[i] = nullptr; } } std::memcpy(_palettes, SpritePalette, ColorsPerPalette * sizeof(std::uint32_t)); RecreateGemPalettes(); } } std::optional ContentResolver::GetEpisode(StringView name, bool withImages) { String fullPath = fs::CombinePath({ GetContentPath(), "Episodes"_s, String(name + ".j2e"_s) }); if (!fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ GetCachePath(), "Episodes"_s, String(name + ".j2e"_s) }); } return GetEpisodeByPath(fullPath, withImages); } std::optional ContentResolver::GetEpisodeByPath(StringView path, bool withImages) { auto s = fs::Open(path, FileAccess::Read); if (s->GetSize() < 16) { return std::nullopt; } std::uint64_t signature = s->ReadValueAsLE(); std::uint8_t fileType = s->ReadValue(); if (signature != 0x2095A59FF0BFBBEF || fileType != ContentResolver::EpisodeFile) { return std::nullopt; } Episode episode; episode.Name = fs::GetFileNameWithoutExtension(path); /*std::uint16_t flags =*/ s->ReadValueAsLE(); std::uint8_t nameLength = s->ReadValue(); episode.DisplayName = String(NoInit, nameLength); s->Read(episode.DisplayName.data(), nameLength); episode.Position = s->ReadValueAsLE(); nameLength = s->ReadValue(); episode.FirstLevel = String(NoInit, nameLength); s->Read(episode.FirstLevel.data(), nameLength); nameLength = s->ReadValue(); episode.PreviousEpisode = String(NoInit, nameLength); s->Read(episode.PreviousEpisode.data(), nameLength); nameLength = s->ReadValue(); episode.NextEpisode = String(NoInit, nameLength); s->Read(episode.NextEpisode.data(), nameLength); if (withImages && !_isHeadless) { std::uint16_t titleWidth = s->ReadValueAsLE(); std::uint16_t titleHeight = s->ReadValueAsLE(); if (titleWidth > 0 && titleHeight > 0) { std::unique_ptr pixels = std::make_unique(titleWidth * titleHeight); ReadImageFromFile(s, (std::uint8_t*)pixels.get(), titleWidth, titleHeight, 4); episode.TitleImage = std::make_unique(path.data(), Texture::Format::RGBA8, titleWidth, titleHeight); episode.TitleImage->LoadFromTexels((unsigned char*)pixels.get(), 0, 0, titleWidth, titleHeight); episode.TitleImage->SetMinFiltering(SamplerFilter::Nearest); episode.TitleImage->SetMagFiltering(SamplerFilter::Nearest); } std::uint16_t backgroundWidth = s->ReadValueAsLE(); std::uint16_t backgroundHeight = s->ReadValueAsLE(); if (backgroundWidth > 0 && backgroundHeight > 0) { std::unique_ptr pixels = std::make_unique(backgroundWidth * backgroundHeight); ReadImageFromFile(s, (std::uint8_t*)pixels.get(), backgroundWidth, backgroundHeight, 4); episode.BackgroundImage = std::make_unique(path.data(), Texture::Format::RGBA8, backgroundWidth, backgroundHeight); episode.BackgroundImage->LoadFromTexels((unsigned char*)pixels.get(), 0, 0, backgroundWidth, backgroundHeight); episode.BackgroundImage->SetMinFiltering(SamplerFilter::Linear); episode.BackgroundImage->SetMagFiltering(SamplerFilter::Linear); } } return episode; } std::unique_ptr ContentResolver::GetMusic(StringView path) { #if defined(WITH_AUDIO) // Don't load sounds in headless mode if (_isHeadless) { return nullptr; } String fullPath; if (_pathHandler) { fullPath = _pathHandler(path); } if (fullPath.empty()) { fullPath = fs::CombinePath({ GetContentPath(), "Music"_s, path }); if (!fs::IsReadableFile(fullPath)) { // "Source" directory must be case in-sensitive fullPath = fs::FindPathCaseInsensitive(fs::CombinePath(GetSourcePath(), path)); } } if (!fs::IsReadableFile(fullPath)) { return nullptr; } return std::make_unique(fullPath); #else return nullptr; #endif } UI::Font* ContentResolver::GetFont(FontType fontType) { if (fontType >= FontType::Count) { return nullptr; } // Don't load fonts in headless mode if (_isHeadless) { return nullptr; } auto& font = _fonts[(std::int32_t)fontType]; if (font == nullptr) { switch (fontType) { case FontType::Small: font = std::make_unique(fs::CombinePath( { GetContentPath(), "Animations"_s, "UI"_s, "font_small.png"_s }), _palettes); break; case FontType::Medium: font = std::make_unique(fs::CombinePath( { GetContentPath(), "Animations"_s, "UI"_s, "font_medium.png"_s }), _palettes); break; default: return nullptr; } } return font.get(); } Shader* ContentResolver::GetShader(PrecompiledShader shader) { if (shader >= PrecompiledShader::Count) { return nullptr; } return _precompiledShaders[(std::int32_t)shader].get(); } void ContentResolver::CompileShaders() { // Don't load shaders in headless mode if (_isHeadless) { return; } ZoneScoped; _precompiledShaders[(std::int32_t)PrecompiledShader::Lighting] = CompileShader("Lighting", Shaders::LightingVs, Shaders::LightingFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedLighting] = CompileShader("BatchedLighting", Shaders::BatchedLightingVs, Shaders::LightingFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::Lighting]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedLighting]); _precompiledShaders[(std::int32_t)PrecompiledShader::Blur] = CompileShader("Blur", Shader::DefaultVertex::SPRITE, Shaders::BlurFs); _precompiledShaders[(std::int32_t)PrecompiledShader::Downsample] = CompileShader("Downsample", Shader::DefaultVertex::SPRITE, Shaders::DownsampleFs); _precompiledShaders[(std::int32_t)PrecompiledShader::Combine] = CompileShader("Combine", Shaders::CombineVs, Shaders::CombineFs); _precompiledShaders[(std::int32_t)PrecompiledShader::CombineWithWater] = CompileShader("CombineWithWater", Shaders::CombineVs, Shaders::CombineWithWaterFs); _precompiledShaders[(std::int32_t)PrecompiledShader::CombineWithWaterLow] = CompileShader("CombineWithWaterLow", Shaders::CombineVs, Shaders::CombineWithWaterLowFs); _precompiledShaders[(std::int32_t)PrecompiledShader::TexturedBackground] = CompileShader("TexturedBackground", Shader::DefaultVertex::SPRITE, Shaders::TexturedBackgroundFs); _precompiledShaders[(std::int32_t)PrecompiledShader::TexturedBackgroundDither] = CompileShader("TexturedBackgroundDither", Shader::DefaultVertex::SPRITE, Shaders::TexturedBackgroundFs, Shader::Introspection::Enabled, { "DITHER"_s }); _precompiledShaders[(std::int32_t)PrecompiledShader::TexturedBackgroundCircle] = CompileShader("TexturedBackgroundCircle", Shader::DefaultVertex::SPRITE, Shaders::TexturedBackgroundCircleFs); _precompiledShaders[(std::int32_t)PrecompiledShader::TexturedBackgroundCircleDither] = CompileShader("TexturedBackgroundCircleDither", Shader::DefaultVertex::SPRITE, Shaders::TexturedBackgroundCircleFs, Shader::Introspection::Enabled, { "DITHER"_s }); _precompiledShaders[(std::int32_t)PrecompiledShader::Colorized] = CompileShader("Colorized", Shader::DefaultVertex::SPRITE, Shaders::ColorizedFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedColorized] = CompileShader("BatchedColorized", Shader::DefaultVertex::BATCHED_SPRITES, Shaders::ColorizedFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::Colorized]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedColorized]); _precompiledShaders[(std::int32_t)PrecompiledShader::Tinted] = CompileShader("Tinted", Shader::DefaultVertex::SPRITE, Shaders::TintedFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedTinted] = CompileShader("BatchedTinted", Shader::DefaultVertex::BATCHED_SPRITES, Shaders::TintedFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::Tinted]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedTinted]); _precompiledShaders[(std::int32_t)PrecompiledShader::Outline] = CompileShader("Outline", Shader::DefaultVertex::SPRITE, Shaders::OutlineFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedOutline] = CompileShader("BatchedOutline", Shader::DefaultVertex::BATCHED_SPRITES, Shaders::OutlineFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::Outline]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedOutline]); _precompiledShaders[(std::int32_t)PrecompiledShader::WhiteMask] = CompileShader("WhiteMask", Shader::DefaultVertex::SPRITE, Shaders::WhiteMaskFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedWhiteMask] = CompileShader("BatchedWhiteMask", Shader::DefaultVertex::BATCHED_SPRITES, Shaders::WhiteMaskFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::WhiteMask]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedWhiteMask]); _precompiledShaders[(std::int32_t)PrecompiledShader::PartialWhiteMask] = CompileShader("PartialWhiteMask", Shader::DefaultVertex::SPRITE, Shaders::PartialWhiteMaskFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedPartialWhiteMask] = CompileShader("BatchedPartialWhiteMask", Shader::DefaultVertex::BATCHED_SPRITES, Shaders::PartialWhiteMaskFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::PartialWhiteMask]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedPartialWhiteMask]); _precompiledShaders[(std::int32_t)PrecompiledShader::FrozenMask] = CompileShader("FrozenMask", Shader::DefaultVertex::SPRITE, Shaders::FrozenMaskFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedFrozenMask] = CompileShader("BatchedFrozenMask", Shader::DefaultVertex::BATCHED_SPRITES, Shaders::FrozenMaskFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::FrozenMask]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedFrozenMask]); _precompiledShaders[(std::int32_t)PrecompiledShader::ShieldFire] = CompileShader("ShieldFire", Shaders::ShieldVs, Shaders::ShieldFireFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedShieldFire] = CompileShader("BatchedShieldFire", Shaders::BatchedShieldVs, Shaders::ShieldFireFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::ShieldFire]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedShieldFire]); _precompiledShaders[(std::int32_t)PrecompiledShader::ShieldLightning] = CompileShader("ShieldLightning", Shaders::ShieldVs, Shaders::ShieldLightningFs); _precompiledShaders[(std::int32_t)PrecompiledShader::BatchedShieldLightning] = CompileShader("BatchedShieldFire", Shaders::BatchedShieldVs, Shaders::ShieldLightningFs, Shader::Introspection::NoUniformsInBlocks); _precompiledShaders[(std::int32_t)PrecompiledShader::ShieldLightning]->RegisterBatchedShader(*_precompiledShaders[(int32_t)PrecompiledShader::BatchedShieldLightning]); #if !defined(DISABLE_RESCALE_SHADERS) _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeHQ2x] = CompileShader("ResizeHQ2x", Shaders::ResizeHQ2xVs, Shaders::ResizeHQ2xFs); _precompiledShaders[(std::int32_t)PrecompiledShader::Resize3xBrz] = CompileShader("Resize3xBrz", Shaders::Resize3xBrzVs, Shaders::Resize3xBrzFs); _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeCrtScanlines] = CompileShader("ResizeCrtScanlines", Shaders::ResizeCrtScanlinesVs, Shaders::ResizeCrtScanlinesFs); _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeCrtShadowMask] = CompileShader("ResizeCrtShadowMask", Shaders::ResizeCrtVs, Shaders::ResizeCrtShadowMaskFs); _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeCrtApertureGrille] = CompileShader("ResizeCrtApertureGrille", Shaders::ResizeCrtVs, Shaders::ResizeCrtApertureGrilleFs); _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeMonochrome] = CompileShader("ResizeMonochrome", Shaders::ResizeMonochromeVs, Shaders::ResizeMonochromeFs); _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeSabr] = CompileShader("ResizeSabr", Shaders::ResizeSabrVs, Shaders::ResizeSabrFs); _precompiledShaders[(std::int32_t)PrecompiledShader::ResizeCleanEdge] = CompileShader("ResizeCleanEdge", Shaders::ResizeCleanEdgeVs, Shaders::ResizeCleanEdgeFs); #endif _precompiledShaders[(std::int32_t)PrecompiledShader::Antialiasing] = CompileShader("Antialiasing", Shaders::AntialiasingVs, Shaders::AntialiasingFs); _precompiledShaders[(std::int32_t)PrecompiledShader::Transition] = CompileShader("Transition", Shaders::TransitionVs, Shaders::TransitionFs); } std::unique_ptr ContentResolver::CompileShader(const char* shaderName, Shader::DefaultVertex vertex, const char* fragment, Shader::Introspection introspection, std::initializer_list defines) { std::unique_ptr shader = std::make_unique(); if (shader->LoadFromCache(shaderName, Shaders::Version, introspection)) { return shader; } const AppConfiguration& appCfg = theApplication().GetAppConfiguration(); const IGfxCapabilities& gfxCaps = theServiceLocator().GetGfxCapabilities(); // Clamping the value as some drivers report a maximum size similar to SSBO one std::int32_t maxUniformBlockSize = std::clamp(gfxCaps.GetValue(IGfxCapabilities::GLIntValues::MAX_UNIFORM_BLOCK_SIZE), 0, 64 * 1024); // If the UBO is smaller than 64kb and fixed batch size is disabled, batched shaders need to be compiled twice to determine safe `BATCH_SIZE` define value bool compileTwice = (maxUniformBlockSize < 64 * 1024 && appCfg.fixedBatchSize <= 0 && introspection == Shader::Introspection::NoUniformsInBlocks); std::int32_t batchSize; if (appCfg.fixedBatchSize > 0 && introspection == Shader::Introspection::NoUniformsInBlocks) { batchSize = appCfg.fixedBatchSize; } else if (compileTwice) { // The first compilation of a batched shader needs a `BATCH_SIZE` defined as 1 batchSize = 1; } else { batchSize = GLShaderProgram::DefaultBatchSize; } shader->LoadFromMemory(shaderName, compileTwice ? Shader::Introspection::Enabled : introspection, vertex, fragment, batchSize, arrayView(defines)); if (compileTwice) { GLShaderUniformBlocks blocks(shader->GetHandle(), Material::InstancesBlockName, nullptr); GLUniformBlockCache* block = blocks.GetUniformBlock(Material::InstancesBlockName); DEATH_DEBUG_ASSERT(block != nullptr); if (block != nullptr) { batchSize = maxUniformBlockSize / block->GetSize(); LOGI("Shader \"{}\" - block size: {} + {} align bytes, max batch size: {}", shaderName, block->GetSize() - block->GetAlignAmount(), block->GetAlignAmount(), batchSize); bool hasLinked = false; while (batchSize > 0) { hasLinked = shader->LoadFromMemory(shaderName, introspection, vertex, fragment, batchSize, arrayView(defines)); if (hasLinked) { break; } batchSize--; LOGW("Failed to compile the shader, recompiling with batch size: {}", batchSize); } if (!hasLinked) { // Don't save to cache if it cannot be linked return shader; } } } shader->SaveToCache(shaderName, Shaders::Version); return shader; } std::unique_ptr ContentResolver::CompileShader(const char* shaderName, const char* vertex, const char* fragment, Shader::Introspection introspection, std::initializer_list defines) { std::unique_ptr shader = std::make_unique(); if (shader->LoadFromCache(shaderName, Shaders::Version, introspection)) { return shader; } const AppConfiguration& appCfg = theApplication().GetAppConfiguration(); const IGfxCapabilities& gfxCaps = theServiceLocator().GetGfxCapabilities(); // Clamping the value as some drivers report a maximum size similar to SSBO one std::int32_t maxUniformBlockSize = std::clamp(gfxCaps.GetValue(IGfxCapabilities::GLIntValues::MAX_UNIFORM_BLOCK_SIZE), 0, 64 * 1024); // If the UBO is smaller than 64kb and fixed batch size is disabled, batched shaders need to be compiled twice to determine safe `BATCH_SIZE` define value bool compileTwice = (maxUniformBlockSize < 64 * 1024 && appCfg.fixedBatchSize <= 0 && introspection == Shader::Introspection::NoUniformsInBlocks); std::int32_t batchSize; if (appCfg.fixedBatchSize > 0 && introspection == Shader::Introspection::NoUniformsInBlocks) { batchSize = appCfg.fixedBatchSize; } else if (compileTwice) { // The first compilation of a batched shader needs a `BATCH_SIZE` defined as 1 batchSize = 1; } else { batchSize = GLShaderProgram::DefaultBatchSize; } shader->LoadFromMemory(shaderName, compileTwice ? Shader::Introspection::Enabled : introspection, vertex, fragment, batchSize, arrayView(defines)); if (compileTwice) { GLShaderUniformBlocks blocks(shader->GetHandle(), Material::InstancesBlockName, nullptr); GLUniformBlockCache* block = blocks.GetUniformBlock(Material::InstancesBlockName); DEATH_DEBUG_ASSERT(block != nullptr); if (block != nullptr) { batchSize = maxUniformBlockSize / block->GetSize(); LOGI("Shader \"{}\" - block size: {} + {} align bytes, max batch size: {}", shaderName, block->GetSize() - block->GetAlignAmount(), block->GetAlignAmount(), batchSize); bool hasLinked = false; while (batchSize > 0) { hasLinked = shader->LoadFromMemory(shaderName, introspection, vertex, fragment, batchSize, arrayView(defines)); if (hasLinked) { break; } batchSize--; LOGW("Failed to compile the shader, recompiling with batch size: {}", batchSize); } if (!hasLinked) { // Don't save to cache if it cannot be linked return shader; } } } shader->SaveToCache(shaderName, Shaders::Version); return shader; } std::unique_ptr ContentResolver::GetNoiseTexture() { // Don't load textures in headless mode if (_isHeadless) { return nullptr; } std::uint32_t texels[64 * 64]; for (std::uint32_t i = 0; i < static_cast(arraySize(texels)); i++) { texels[i] = Random().Fast(0, INT32_MAX) | 0xff000000; } std::unique_ptr tex = std::make_unique("Noise", Texture::Format::RGBA8, 64, 64); tex->LoadFromTexels((std::uint8_t*)texels, 0, 0, 64, 64); tex->SetMinFiltering(SamplerFilter::Linear); tex->SetMagFiltering(SamplerFilter::Linear); tex->SetWrap(SamplerWrapping::Repeat); return tex; } void ContentResolver::RecreateGemPalettes() { constexpr std::int32_t GemColorCount = 4; constexpr std::int32_t Expansion = 32; static const std::int32_t PaletteStops[] = { 55, 52, 48, 15, 15, 87, 84, 80, 15, 15, 39, 36, 32, 15, 15, 95, 92, 88, 15, 15 }; constexpr std::int32_t StopsPerGem = (std::int32_t(arraySize(PaletteStops)) / GemColorCount) - 1; // Start to fill palette texture from the second row (right after base palette) std::int32_t src = 0, dst = ColorsPerPalette; for (std::int32_t color = 0; color < GemColorCount; color++, src++) { // Compress 2 gem color gradients to single palette row for (std::int32_t i = 0; i < StopsPerGem; i++) { // Base Palette is in first row of "palettes" array std::uint32_t from = _palettes[PaletteStops[src++]]; std::uint32_t to = _palettes[PaletteStops[src]]; std::int32_t r = (from & 0xff) * 8, dr = ((to & 0xff) * 8) - r; std::int32_t g = ((from >> 8) & 0xff) * 8, dg = (((to >> 8) & 0xff) * 8) - g; std::int32_t b = ((from >> 16) & 0xff) * 8, db = (((to >> 16) & 0xff) * 8) - b; std::int32_t a = (from & 0xff000000); r *= Expansion; g *= Expansion; b *= Expansion; for (std::int32_t j = 0; j < Expansion; j++) { _palettes[dst] = ((r / (8 * Expansion)) & 0xff) | (((g / (8 * Expansion)) & 0xff) << 8) | (((b / (8 * Expansion)) & 0xff) << 16) | a; r += dr; g += dg; b += db; dst++; } } } } #if defined(DEATH_DEBUG) void ContentResolver::MigrateGraphics(StringView path) { String auraPath = fs::CombinePath({ GetContentPath(), "Animations"_s, String(path.exceptSuffix(4) + ".aura"_s) }); if (fs::FileExists(auraPath)) { return; } auto s = fs::Open(fs::CombinePath({ GetContentPath(), "Animations"_s, String(path + ".res"_s) }), FileAccess::Read); auto fileSize = s->GetSize(); if (fileSize < 4 || fileSize > 64 * 1024 * 1024) { // 64 MB file size limit, also if not found try to use cache return; } auto buffer = std::make_unique(fileSize); s->Read(buffer.get(), fileSize); s->Dispose(); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + fileSize, &doc, &errors)) { String fullPath = fs::CombinePath({ GetContentPath(), "Animations"_s, path }); std::unique_ptr texLoader = ITextureLoader::createFromFile(fullPath); if (texLoader->hasLoaded()) { auto texFormat = texLoader->texFormat().internalFormat(); if (texFormat != GL_RGBA8 && texFormat != GL_RGB8) { return; } std::int32_t w = texLoader->width(); std::int32_t h = texLoader->height(); const std::uint32_t* palette = _palettes; bool needsMask = true; std::int64_t originalFlags; if (doc["Flags"].get(originalFlags) == Json::SUCCESS) { // Palette already applied, keep as is if ((originalFlags & 0x01) != 0x01) { palette = nullptr; } if ((originalFlags & 0x08) == 0x08) { needsMask = false; } } // TODO: Use FrameDuration instead double animDuration; if (doc["Duration"].get(animDuration) != Json::SUCCESS) { animDuration = 0.0; } std::int64_t frameCount; if (doc["FrameCount"].get(frameCount) != Json::SUCCESS || frameCount < 0) { frameCount = 0; } Vector2i frameDimensions = GetVector2iFromJson(doc["FrameSize"]); Vector2i frameConfiguration = GetVector2iFromJson(doc["FrameConfiguration"]); Vector2i hotspot = GetVector2iFromJson(doc["Hotspot"]); Vector2i coldspot = GetVector2iFromJson(doc["Coldspot"], Vector2i(InvalidValue, InvalidValue)); Vector2i gunspot = GetVector2iFromJson(doc["Gunspot"], Vector2i(InvalidValue, InvalidValue)); // Write to .aura file auto so = fs::Open(auraPath, FileAccess::Write); if (!so->IsValid()) return; std::uint8_t flags = 0x80; if (palette == nullptr) { flags |= 0x01; } if (!needsMask) { flags |= 0x02; } so->WriteValue(0xB8EF8498E2BFBBEF); so->WriteValue(0x0002208F | (flags << 24)); // Version 2 is reserved for sprites (or bigger images) so->WriteValue(4); so->WriteValue((std::uint32_t)frameDimensions.X); so->WriteValue((std::uint32_t)frameDimensions.Y); // Include Sprite extension so->WriteValue((std::uint8_t)frameConfiguration.X); so->WriteValue((std::uint8_t)frameConfiguration.Y); so->WriteValue((std::uint16_t)frameCount); so->WriteValue((std::uint16_t)(animDuration <= 0.0 ? 0 : 256 * animDuration)); if (hotspot.X != InvalidValue || hotspot.Y != InvalidValue) { so->WriteValue((std::uint16_t)hotspot.X); so->WriteValue((std::uint16_t)hotspot.Y); } else { so->WriteValue(UINT16_MAX); so->WriteValue(UINT16_MAX); } if (coldspot.X != InvalidValue || coldspot.Y != InvalidValue) { so->WriteValue((std::uint16_t)coldspot.X); so->WriteValue((std::uint16_t)coldspot.Y); } else { so->WriteValue(UINT16_MAX); so->WriteValue(UINT16_MAX); } if (gunspot.X != InvalidValue || gunspot.Y != InvalidValue) { so->WriteValue((std::uint16_t)gunspot.X); so->WriteValue((std::uint16_t)gunspot.Y); } else { so->WriteValue(UINT16_MAX); so->WriteValue(UINT16_MAX); } Compatibility::JJ2Anims::WriteImageContent(*so, texLoader->pixels(), w, h, 4); } } } #endif }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/ContentResolver.h000066400000000000000000000161431512772601700250450ustar00rootroot00000000000000#pragma once #include "../Main.h" #include "GameDifficulty.h" #include "LevelDescriptor.h" #include "Resources.h" #include "UI/Font.h" #include "../nCine/Audio/AudioBuffer.h" #include "../nCine/Audio/AudioStreamPlayer.h" #include "../nCine/Graphics/Texture.h" #include "../nCine/Base/HashMap.h" #include #include #include #include #include #include #include using namespace Death::Containers; using namespace Death::Containers::Literals; using namespace Death::IO; using namespace Jazz2::Resources; using namespace nCine; namespace Jazz2 { namespace Tiles { class TileSet; } /** @brief Manages loading of assets */ class ContentResolver { public: /** @{ @name Constants */ /** @brief Pixel size in bytes */ static constexpr std::uint32_t PixelSize = 4; /** @brief Maximum number of palettes */ static constexpr std::int32_t PaletteCount = 256; /** @brief Number of colors per palette */ static constexpr std::int32_t ColorsPerPalette = 256; /** @brief Invalid value */ static constexpr std::int32_t InvalidValue = INT_MAX; #ifndef DOXYGEN_GENERATING_OUTPUT static constexpr std::uint8_t LevelFile = 1; static constexpr std::uint8_t EpisodeFile = 2; static constexpr std::uint8_t CacheIndexFile = 3; static constexpr std::uint8_t ConfigFile = 4; static constexpr std::uint8_t StateFile = 5; static constexpr std::uint8_t SfxListFile = 6; static constexpr std::uint8_t HighscoresFile = 7; #endif /** @} */ /** @brief Returns static instance of main content resolver */ static ContentResolver& Get(); ~ContentResolver(); /** @brief Releases all cached assets */ void Release(); /** @brief Returns path to `"Content"` directory */ StringView GetContentPath() const; /** @brief Returns path to `"Cache"` directory */ StringView GetCachePath() const; /** @brief Returns path to `"Source"` directory */ StringView GetSourcePath() const; /** @brief Returns `true` if the application is running in headless mode (i.e., without any display) */ bool IsHeadless() const; /** @brief Sets whether the application is running in headless mode */ void SetHeadless(bool value); #if !defined(DEATH_TARGET_EMSCRIPTEN) /** @brief Scans the `"Content"` and `"Cache"` directories for `.pak` files and mounts them */ void RemountPaks(); #endif /** @brief Tries to find and open a file specified by the path */ std::unique_ptr OpenContentFile(StringView path, std::int32_t bufferSize = 8192); /** @brief Marks beginning of the loading assets */ void BeginLoading(); /** @brief Marks end of the loading assets */ void EndLoading(); /** @brief Overrides the default path handler */ void OverridePathHandler(Function&& callback); /** @brief Preloads specified metadata and its linked assets to cache */ void PreloadMetadataAsync(StringView path); /** @brief Loads specified metadata and its linked assets if not in cache already and returns it */ Metadata* RequestMetadata(StringView path); /** @brief Loads specified graphics asset if not in cache already and returns it */ GenericGraphicResource* RequestGraphics(StringView path, std::uint16_t paletteOffset); /** @brief Loads specified tile set and its palette */ std::unique_ptr RequestTileSet(StringView path, std::uint16_t captionTileId, bool applyPalette, const std::uint8_t* paletteRemapping = nullptr); /** @brief Returns `true` if specified level exists */ bool LevelExists(StringView levelName); /** @brief Loads specified level into a level descriptor */ bool TryLoadLevel(StringView path, GameDifficulty difficulty, LevelDescriptor& descriptor); /** @brief Loads default (sprite) palette */ void ApplyDefaultPalette(); /** @brief Returns specified episode by name */ std::optional GetEpisode(StringView name, bool withImages = false); /** @brief Returns specified episode by full path */ std::optional GetEpisodeByPath(StringView path, bool withImages = false); /** @brief Loads specified music */ std::unique_ptr GetMusic(StringView path); /** @brief Returns specified font type */ UI::Font* GetFont(FontType fontType); /** @brief Returns specified precompiled shader */ Shader* GetShader(PrecompiledShader shader); /** @brief Precompiles all required shaders */ void CompileShaders(); /** @brief Returns a noise texture with random pixels */ std::unique_ptr GetNoiseTexture(); /** @brief Returns currently loaded set of palettes */ StaticArrayView GetPalettes() const { return _palettes; } private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct StringRefEqualTo { inline bool operator()(const Reference& a, const Reference& b) const noexcept { return a.get() == b.get(); } }; #endif ContentResolver(); ContentResolver(const ContentResolver&) = delete; ContentResolver& operator=(const ContentResolver&) = delete; void InitializePaths(); GenericGraphicResource* RequestGraphicsAura(StringView path, std::uint16_t paletteOffset); static void ReadImageFromFile(std::unique_ptr& s, std::uint8_t* data, std::int32_t width, std::int32_t height, std::int32_t channelCount); static void ExpandTileDiffuse(std::uint8_t* pixelsOffset, std::uint32_t widthWithPadding); std::unique_ptr CompileShader(const char* shaderName, Shader::DefaultVertex vertex, const char* fragment, Shader::Introspection introspection = Shader::Introspection::Enabled, std::initializer_list defines = {}); std::unique_ptr CompileShader(const char* shaderName, const char* vertex, const char* fragment, Shader::Introspection introspection = Shader::Introspection::Enabled, std::initializer_list defines = {}); void RecreateGemPalettes(); #if defined(DEATH_DEBUG) void MigrateGraphics(StringView path); #endif bool _isHeadless; bool _isLoading; std::uint32_t _palettes[PaletteCount * ColorsPerPalette]; HashMap, std::unique_ptr, #if defined(DEATH_TARGET_32BIT) xxHash32Func, #else xxHash64Func, #endif StringRefEqualTo> _cachedMetadata; HashMap, std::unique_ptr> _cachedGraphics; #if defined(WITH_AUDIO) HashMap> _cachedSounds; #endif std::unique_ptr _fonts[(std::int32_t)FontType::Count]; std::unique_ptr _precompiledShaders[(std::int32_t)PrecompiledShader::Count]; #if !defined(DEATH_TARGET_EMSCRIPTEN) SmallVector> _mountedPaks; #endif Function _pathHandler; #if defined(DEATH_TARGET_UNIX) || defined(DEATH_TARGET_WINDOWS_RT) String _contentPath; #endif #if defined(DEATH_TARGET_ANDROID) || defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) || defined(DEATH_TARGET_WINDOWS_RT) String _cachePath; String _sourcePath; #endif }; } deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Direction.h000066400000000000000000000006261512772601700236300ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Direction, supports a bitwise combination of its member values */ enum class Direction { None = 0, /**< None */ Up = 0x01, /**< Up */ Down = 0x02, /**< Down */ Left = 0x04, /**< Left */ Right = 0x08, /**< Right */ Any = Up | Down | Left | Right /**< Any */ }; DEATH_ENUM_FLAGS(Direction); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/EventType.h000066400000000000000000000050341512772601700236310ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Event type */ enum class EventType : std::uint16_t { Empty = 0, // Basic LevelStart, LevelStartMultiplayer, Checkpoint, // Scenery SceneryDestruct, SceneryDestructButtstomp, SceneryDestructSpeed, SceneryCollapse, // Modifiers ModifierVine, ModifierOneWay, ModifierHook, ModifierHPole, ModifierVPole, ModifierHurt, ModifierTube, ModifierRicochet, ModifierSlide, ModifierDeath, ModifierSetWater, ModifierLimitCameraView, ModifierNoClimb, // Area AreaStopEnemy, AreaFloatUp, AreaHForce, AreaText, AreaEndOfLevel, AreaCallback, AreaActivateBoss, AreaFlyOff, AreaRevertMorph, AreaMorphToFrog, AreaNoFire, AreaWaterBlock, AreaWeather, AreaAmbientSound, AreaAmbientBubbles, // Triggers TriggerCrate, TriggerArea, TriggerZone, // Warp WarpCoinBonus, WarpOrigin, WarpTarget, // Lights LightAmbient, LightSteady, LightPulse, LightFlicker, LightIlluminate, // Environment Spring, Bridge, MovingPlatform, PushableBox, Eva, Pole, SignEOL, Moth, SteamNote, Bomb, PinballBumper, PinballPaddle, CtfBase, BirdCage, Stopwatch, SpikeBall, AirboardGenerator, Copter, RollingRock, RollingRockTrigger, SwingingVine, // Enemies EnemyTurtle, EnemyLizard, EnemySucker, EnemySuckerFloat, EnemyLabRat, EnemyHelmut, EnemyDragon, EnemyBat, EnemyFatChick, EnemyFencer, EnemyRapier, EnemySparks, EnemyMonkey, EnemyDemon, EnemyBee, EnemyBeeSwarm, EnemyCaterpillar, EnemyCrab, EnemyDoggy, EnemyDragonfly, EnemyFish, EnemyMadderHatter, EnemyRaven, EnemySkeleton, EnemyTurtleTough, EnemyTurtleTube, EnemyWitch, EnemyLizardFloat, BossTweedle, BossBilsy, BossDevan, BossQueen, BossRobot, BossUterus, BossTurtleTough, BossBubba, BossDevanRemote, BossBolly, TurtleShell, // Collectibles Coin, Gem, GemGiant, GemRing, GemStomp, Carrot, CarrotFly, CarrotInvincible, OneUp, FastFire, Food, Ammo, PowerUpWeapon, // Containers Crate, Barrel, CrateAmmo, BarrelAmmo, CrateGem, BarrelGem, PowerUpMorph, PowerUpShield, // End of enumeration Count, Generator = 0x1000, // Multiplayer-only remotable actors WeaponBlaster = 0x2001, WeaponBouncer = 0x2002, WeaponElectro = 0x2003, WeaponFreezer = 0x2004, WeaponPepper = 0x2005, WeaponRF = 0x2006, WeaponSeeker = 0x2007, WeaponThunderbolt = 0x2008, WeaponTNT = 0x2009, WeaponToaster = 0x200A, }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Events/000077500000000000000000000000001512772601700227775ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Events/EventMap.cpp000066400000000000000000000400441512772601700252240ustar00rootroot00000000000000#include "EventMap.h" #include "../Tiles/TileMap.h" #include "../WeatherType.h" #include "../../nCine/tracy.h" #include "../../nCine/Base/Random.h" #include "../../nCine/Base/FrameTimer.h" namespace Jazz2::Events { EventMap::EventMap(Vector2i layoutSize) : _levelHandler(nullptr), _layoutSize(layoutSize), _pitType(PitType::FallForever) { } void EventMap::SetLevelHandler(ILevelHandler* levelHandler) { _levelHandler = levelHandler; } Vector2i EventMap::GetSize() const { return _layoutSize; } PitType EventMap::GetPitType() const { return _pitType; } void EventMap::SetPitType(PitType value) { _pitType = value; } Vector2f EventMap::GetSpawnPosition(PlayerType type) { std::int32_t targetCount = 0; for (auto& target : _spawnPoints) { if ((target.PlayerTypeMask & (1 << ((std::int32_t)type - 1))) != 0) { targetCount++; } } if (targetCount > 0) { std::int32_t selectedTarget = nCine::Random().Next(0, targetCount); for (auto& target : _spawnPoints) { if ((target.PlayerTypeMask & (1 << ((std::int32_t)type - 1))) == 0) { continue; } if (selectedTarget == 0) { return target.Pos; } selectedTarget--; } } return Vector2f(-1, -1); } void EventMap::CreateCheckpointForRollback() { if (_eventLayoutForRollback == nullptr) { _eventLayoutForRollback = std::make_unique(_layoutSize.X * _layoutSize.Y); } std::memcpy(_eventLayoutForRollback.get(), _eventLayout.get(), _layoutSize.X * _layoutSize.Y * sizeof(EventTile)); } void EventMap::RollbackToCheckpoint() { if (_eventLayoutForRollback == nullptr) { return; } for (std::int32_t y = 0; y < _layoutSize.Y; y++) { for (std::int32_t x = 0; x < _layoutSize.X; x++) { std::int32_t tileID = y * _layoutSize.X + x; EventTile& tile = _eventLayout[tileID]; EventTile& tilePrev = _eventLayoutForRollback[tileID]; bool respawn = (tilePrev.IsEventActive && !tile.IsEventActive); // Rollback tile tile = tilePrev; if (respawn && tile.Event != EventType::Empty) { tile.IsEventActive = true; if (tile.Event == EventType::AreaWeather) { _levelHandler->SetWeather((WeatherType)tile.EventParams[0], tile.EventParams[1]); } else if (tile.Event != EventType::Generator) { Actors::ActorState flags = Actors::ActorState::IsCreatedFromEventMap | tile.EventFlags; std::shared_ptr actor = _levelHandler->EventSpawner()->SpawnEvent(tile.Event, tile.EventParams, flags, x, y, ILevelHandler::MainPlaneZ); if (actor != nullptr) { _levelHandler->AddActor(actor); } } } } } // Reset cooldown of all generators // TODO: Save also cooldown of all generators to be able to restore them for (auto& generator : _generators) { generator.TimeLeft = 0.0f; } } void EventMap::StoreTileEvent(std::int32_t x, std::int32_t y, EventType eventType, Actors::ActorState eventFlags, std::uint8_t* tileParams) { if (eventType == EventType::Empty && (x < 0 || y < 0 || x >= _layoutSize.X || y >= _layoutSize.Y)) { return; } EventTile& previousEvent = _eventLayout[x + y * _layoutSize.X]; EventTile newEvent = {}; newEvent.Event = eventType, newEvent.EventFlags = eventFlags, newEvent.IsEventActive = (previousEvent.Event == eventType && previousEvent.IsEventActive); // Store event parameters if (tileParams != nullptr) { std::memcpy(newEvent.EventParams, tileParams, sizeof(newEvent.EventParams)); } previousEvent = newEvent; } void EventMap::PreloadEventsAsync() { ZoneScopedC(0x9D5BA3); auto eventSpawner = _levelHandler->EventSpawner(); // TODO //ContentResolver::Get().SuspendAsync(); // Preload all events for (std::int32_t i = 0; i < _layoutSize.X * _layoutSize.Y; i++) { // TODO: Exclude also some modifiers here ? auto& tile = _eventLayout[i]; if (tile.Event != EventType::Empty && tile.Event != EventType::Generator && tile.Event != EventType::AreaWeather) { eventSpawner->PreloadEvent(tile.Event, tile.EventParams); } } // Preload all generators for (auto& generator : _generators) { eventSpawner->PreloadEvent(generator.Event, generator.EventParams); } // Don't wait for finalization of resources, it will be done in a few next frames //ContentResolver::Get().ResumeAsync(); } void EventMap::ProcessGenerators(float timeMult) { ZoneScopedC(0x9D5BA3); for (auto& generator : _generators) { if (!_eventLayout[generator.EventPos].IsEventActive) { // Generator is inactive (and recharging) generator.TimeLeft -= timeMult; } else if (generator.SpawnedActor == nullptr || generator.SpawnedActor->GetHealth() <= 0) { if (generator.TimeLeft <= 0.0f) { // Generator is active and is ready to spawn new actor generator.TimeLeft = generator.Delay * FrameTimer::FramesPerSecond; std::int32_t x = generator.EventPos % _layoutSize.X; std::int32_t y = generator.EventPos / _layoutSize.X; generator.SpawnedActor = _levelHandler->EventSpawner()->SpawnEvent(generator.Event, generator.EventParams, Actors::ActorState::IsFromGenerator, x, y, ILevelHandler::SpritePlaneZ); if (generator.SpawnedActor != nullptr) { _levelHandler->AddActor(generator.SpawnedActor); } } else { // Generator is active and recharging generator.TimeLeft -= timeMult; if (generator.SpawnedActor != nullptr) { generator.SpawnedActor = nullptr; } } } } } void EventMap::ActivateEvents(std::int32_t tx1, std::int32_t ty1, std::int32_t tx2, std::int32_t ty2, bool allowAsync) { ZoneScopedC(0x9D5BA3); std::int32_t x1 = std::max(0, tx1); std::int32_t x2 = std::min(_layoutSize.X - 1, tx2); std::int32_t y1 = std::max(0, ty1); std::int32_t y2 = std::min(_layoutSize.Y - 1, ty2); for (std::int32_t x = x1; x <= x2; x++) { for (std::int32_t y = y1; y <= y2; y++) { auto& tile = _eventLayout[x + y * _layoutSize.X]; if (!tile.IsEventActive && tile.Event != EventType::Empty) { tile.IsEventActive = true; if (tile.Event == EventType::AreaWeather) { _levelHandler->SetWeather((WeatherType)tile.EventParams[0], tile.EventParams[1]); } else if (tile.Event != EventType::Generator) { Actors::ActorState flags = Actors::ActorState::IsCreatedFromEventMap | tile.EventFlags; if (allowAsync) { flags |= Actors::ActorState::Async; } std::shared_ptr actor = _levelHandler->EventSpawner()->SpawnEvent(tile.Event, tile.EventParams, flags, x, y, ILevelHandler::SpritePlaneZ); if (actor != nullptr) { _levelHandler->AddActor(actor); } } } } } } void EventMap::Deactivate(std::int32_t x, std::int32_t y) { if (HasEventByPosition(x, y)) { _eventLayout[x + y * _layoutSize.X].IsEventActive = false; } } void EventMap::ResetGenerator(std::int32_t tx, std::int32_t ty) { // Linked actor was deactivated, but not destroyed // Reset its generator, so it can be respawned immediately std::uint32_t generatorIdx = *(std::uint32_t*)_eventLayout[tx + ty * _layoutSize.X].EventParams; if (generatorIdx >= _generators.size()) { // Do nothing if generator if wrongly configured return; } _generators[generatorIdx].TimeLeft = 0.0f; _generators[generatorIdx].SpawnedActor = nullptr; } const EventMap::EventTile& EventMap::GetEventTile(std::int32_t x, std::int32_t y) const { return _eventLayout[x + y * _layoutSize.X]; } EventType EventMap::GetEventByPosition(float x, float y, std::uint8_t** eventParams) { return GetEventByPosition((std::int32_t)x / Tiles::TileSet::DefaultTileSize, (std::int32_t)y / Tiles::TileSet::DefaultTileSize, eventParams); } EventType EventMap::GetEventByPosition(std::int32_t x, std::int32_t y, std::uint8_t** eventParams) { if (y > _layoutSize.Y) { return (_pitType == PitType::InstantDeathPit ? EventType::ModifierDeath : EventType::Empty); } if (x >= 0 && y >= 0 && y < _layoutSize.Y && x < _layoutSize.X) { *eventParams = _eventLayout[x + y * _layoutSize.X].EventParams; return _eventLayout[x + y * _layoutSize.X].Event; } return EventType::Empty; } bool EventMap::HasEventByPosition(std::int32_t x, std::int32_t y) const { return (x >= 0 && y >= 0 && y < _layoutSize.Y && x < _layoutSize.X && _eventLayout[x + y * _layoutSize.X].Event != EventType::Empty); } void EventMap::ForEachEvent(Function&& forEachCallback) const { for (std::int32_t y = 0; y < _layoutSize.Y; y++) { for (std::int32_t x = 0; x < _layoutSize.X; x++) { auto& event = _eventLayout[x + y * _layoutSize.X]; if (event.Event != EventType::Empty && !forEachCallback(event, x, y)) { return; } } } } bool EventMap::IsHurting(float x, float y, Direction dir) { return IsHurting((std::int32_t)x / Tiles::TileSet::DefaultTileSize, (std::int32_t)y / Tiles::TileSet::DefaultTileSize, dir); } bool EventMap::IsHurting(std::int32_t x, std::int32_t y, Direction dir) { return (x >= 0 && y >= 0 && y < _layoutSize.Y && x < _layoutSize.X && _eventLayout[x + y * _layoutSize.X].Event == EventType::ModifierHurt && (_eventLayout[x + y * _layoutSize.X].EventParams[0] & (std::uint8_t)dir) != 0); } std::int32_t EventMap::GetWarpByPosition(float x, float y) { std::int32_t tx = (std::int32_t)x / Tiles::TileSet::DefaultTileSize; std::int32_t ty = (std::int32_t)y / Tiles::TileSet::DefaultTileSize; std::uint8_t* eventParams; if (GetEventByPosition(tx, ty, &eventParams) == EventType::WarpOrigin) { return eventParams[0]; } else { return -1; } } Vector2f EventMap::GetWarpTarget(std::uint32_t id) { std::int32_t targetCount = 0; for (auto& target : _warpTargets) { if (target.Id == id) { targetCount++; } } std::int32_t selectedTarget = nCine::Random().Next(0, targetCount); for (auto& target : _warpTargets) { if (target.Id != id) { continue; } if (selectedTarget == 0) { return target.Pos; } selectedTarget--; } return Vector2f(-1.0f, -1.0f); } void EventMap::ReadEvents(Stream& s, const std::unique_ptr& tileMap, GameDifficulty difficulty) { _eventLayout = std::make_unique(_layoutSize.X * _layoutSize.Y); std::uint8_t difficultyBit; switch (difficulty) { case GameDifficulty::Easy: difficultyBit = 4; break; case GameDifficulty::Hard: difficultyBit = 6; break; case GameDifficulty::Normal: //case GameDifficulty::Default: //case GameDifficulty::Multiplayer: default: difficultyBit = 5; break; } for (std::int32_t y = 0; y < _layoutSize.Y; y++) { for (std::int32_t x = 0; x < _layoutSize.X; x++) { std::uint16_t eventType = s.ReadValueAsLE(); std::uint8_t eventFlags = s.ReadValue(); std::uint8_t eventParams[16]; // TODO: Remove inlined constants // Flag 0x02: Generator std::uint8_t generatorFlags, generatorDelay; if ((eventFlags & 0x02) != 0) { //eventFlags ^= 0x02; generatorFlags = s.ReadValue(); generatorDelay = s.ReadValue(); } else { generatorFlags = 0; generatorDelay = 0; } // Flag 0x01: No params provided if ((eventFlags & 0x01) == 0) { eventFlags ^= 0x01; s.Read(eventParams, sizeof(eventParams)); } else { std::memset(eventParams, 0, sizeof(eventParams)); } Actors::ActorState actorFlags = (Actors::ActorState)(eventFlags & /*Illuminated*/0x04); // Flag 0x02: Generator if ((eventFlags & 0x02) != 0) { if ((EventType)eventType != EventType::Empty && (eventFlags & (0x01 << difficultyBit)) != 0 && (eventFlags & 0x80) == 0) { std::uint32_t generatorIdx = (std::uint32_t)_generators.size(); float timeLeft = ((generatorFlags & 0x01) != 0 ? generatorDelay : 0.0f); GeneratorInfo& generator = _generators.emplace_back(); generator.EventPos = x + y * _layoutSize.X; generator.Event = (EventType)eventType; std::memcpy(generator.EventParams, eventParams, sizeof(eventParams)); generator.Delay = generatorDelay; generator.TimeLeft = timeLeft; *(std::uint32_t*)eventParams = generatorIdx; StoreTileEvent(x, y, EventType::Generator, actorFlags, eventParams); } continue; } // If the difficulty bytes for the event don't match the selected difficulty, don't add anything to the event map // Additionally, never show events that are multiplayer-only if (eventFlags == 0 || ((eventFlags & (0x01 << difficultyBit)) != 0 && (eventFlags & 0x80) == 0)) { switch ((EventType)eventType) { case EventType::Empty: break; case EventType::LevelStart: { AddSpawnPosition(eventParams[0], x, y); break; } case EventType::ModifierOneWay: case EventType::ModifierVine: case EventType::ModifierHook: case EventType::SceneryDestruct: case EventType::SceneryDestructButtstomp: case EventType::TriggerArea: case EventType::SceneryDestructSpeed: case EventType::SceneryCollapse: case EventType::ModifierHPole: case EventType::ModifierVPole: { StoreTileEvent(x, y, (EventType)eventType, actorFlags, eventParams); tileMap->SetTileEventFlags(x, y, (EventType)eventType, eventParams); break; } case EventType::WarpTarget: AddWarpTarget(eventParams[0], x, y); break; default: StoreTileEvent(x, y, (EventType)eventType, actorFlags, eventParams); break; } } } } std::uint32_t offGridEventCount = s.ReadVariableUint32(); for (std::uint32_t i = 0; i < offGridEventCount; i++) { std::int32_t x = s.ReadVariableUint32(); std::int32_t y = s.ReadVariableUint32(); std::uint16_t eventType = s.ReadValueAsLE(); std::uint8_t eventFlags = s.ReadValue(); std::uint8_t eventParams[16]; // TODO: Remove inlined constants // Flag 0x02: Generator std::uint8_t generatorFlags, generatorDelay; if ((eventFlags & 0x02) != 0) { //eventFlags ^= 0x02; generatorFlags = s.ReadValue(); generatorDelay = s.ReadValue(); } else { generatorFlags = 0; generatorDelay = 0; } // Flag 0x01: No params provided if ((eventFlags & 0x01) == 0) { eventFlags ^= 0x01; s.Read(eventParams, sizeof(eventParams)); } else { std::memset(eventParams, 0, sizeof(eventParams)); } // TODO: Spawn off-grid events } } void EventMap::AddWarpTarget(std::uint16_t id, std::int32_t x, std::int32_t y) { WarpTarget& target = _warpTargets.emplace_back(); target.Id = id; target.Pos = Vector2f((float)x * Tiles::TileSet::DefaultTileSize + Tiles::TileSet::DefaultTileSize / 2, (float)y * Tiles::TileSet::DefaultTileSize + Tiles::TileSet::DefaultTileSize / 2); } void EventMap::AddSpawnPosition(std::uint8_t typeMask, std::int32_t x, std::int32_t y) { if (typeMask == 0) { return; } SpawnPoint& target = _spawnPoints.emplace_back(); target.PlayerTypeMask = typeMask; target.Pos = Vector2f((float)x * Tiles::TileSet::DefaultTileSize, (float)y * Tiles::TileSet::DefaultTileSize - 8.0f); } void EventMap::InitializeFromStream(Stream& src) { std::int32_t layoutSize = src.ReadVariableInt32(); std::int32_t realLayoutSize = _layoutSize.X * _layoutSize.Y; DEATH_ASSERT(layoutSize == realLayoutSize, "Layout size mismatch", ); for (std::int32_t i = 0; i < layoutSize; i++) { EventTile& tile = _eventLayout[i]; tile.Event = (EventType)src.ReadVariableUint32(); tile.EventFlags = (Actors::ActorState)src.ReadVariableUint32(); src.Read(tile.EventParams, sizeof(tile.EventParams)); } } void EventMap::SerializeResumableToStream(Stream& dest, bool fromCheckpoint) { std::int32_t layoutSize = _layoutSize.X * _layoutSize.Y; const EventTile* source = (fromCheckpoint && _eventLayoutForRollback != nullptr ? _eventLayoutForRollback.get() : _eventLayout.get()); dest.WriteVariableInt32(layoutSize); for (std::int32_t i = 0; i < layoutSize; i++) { const EventTile& tile = source[i]; dest.WriteVariableUint32((std::uint32_t)tile.Event); dest.WriteVariableUint32((std::uint32_t)tile.EventFlags); dest.Write(tile.EventParams, sizeof(tile.EventParams)); // TODO: Optimize this } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Events/EventMap.h000066400000000000000000000111701512772601700246670ustar00rootroot00000000000000#pragma once #include "EventSpawner.h" #include "../Direction.h" #include "../GameDifficulty.h" #include "../PitType.h" #include using namespace Death::IO; namespace Jazz2::Tiles { class TileMap; } namespace Jazz2::Events { /** @brief Represents event map, spawns triggered objects */ class EventMap // : public IResumable { public: /** @brief Represents an event tile */ struct EventTile { /** @brief Event type */ EventType Event; /** @brief Event flags */ Actors::ActorState EventFlags; /** @brief Event parameters */ std::uint8_t EventParams[EventSpawner::SpawnParamsSize]; /** @brief Whether the event is active */ bool IsEventActive; }; EventMap(Vector2i layoutSize); /** @brief Sets owner of the event map */ void SetLevelHandler(ILevelHandler* levelHandler); /** @brief Returns size of event map in tiles */ Vector2i GetSize() const; /** @brief Returns pit type */ PitType GetPitType() const; /** @brief Sets pit type */ void SetPitType(PitType value); /** @brief Returns spawn position for specified player type */ Vector2f GetSpawnPosition(PlayerType type); /** @brief Creates a checkpoint for eventual rollback */ void CreateCheckpointForRollback(); /** @brief Rolls back to the last checkpoint */ void RollbackToCheckpoint(); /** @brief Stores tile event description */ void StoreTileEvent(std::int32_t x, std::int32_t y, EventType eventType, Actors::ActorState eventFlags = Actors::ActorState::None, std::uint8_t* tileParams = nullptr); /** @brief Preloads assets of all contained events */ void PreloadEventsAsync(); /** @brief Processes all generators */ void ProcessGenerators(float timeMult); /** @brief Activates all inactive events in specified tile restangle */ void ActivateEvents(std::int32_t tx1, std::int32_t ty1, std::int32_t tx2, std::int32_t ty2, bool allowAsync); /** @brief Deactivates event on specified tile position */ void Deactivate(std::int32_t x, std::int32_t y); /** @brief Resets generator on specified tile position */ void ResetGenerator(std::int32_t tx, std::int32_t ty); /** @brief Returns event description of specified tile position */ const EventTile& GetEventTile(std::int32_t x, std::int32_t y) const; /** @brief Returns event type on specified position */ EventType GetEventByPosition(float x, float y, std::uint8_t** eventParams); /** @overload */ EventType GetEventByPosition(std::int32_t x, std::int32_t y, std::uint8_t** eventParams); /** @brief Returns `true` if specified tile position contains an event */ bool HasEventByPosition(std::int32_t x, std::int32_t y) const; /** @brief Calls specified callback function for each event */ void ForEachEvent(Function&& forEachCallback) const; /** @brief Returns `true` if specified position contains hurt event */ bool IsHurting(float x, float y, Direction dir); /** @overload */ bool IsHurting(std::int32_t x, std::int32_t y, Direction dir); /** @brief Returns ID of warp on specified position */ std::int32_t GetWarpByPosition(float x, float y); /** @brief Returns target position for specified warp */ Vector2f GetWarpTarget(std::uint32_t id); /** @brief Reads event layer data from stream */ void ReadEvents(Stream& s, const std::unique_ptr& tileMap, GameDifficulty difficulty); /** @brief Adds target position for specified warp */ void AddWarpTarget(std::uint16_t id, std::int32_t x, std::int32_t y); /** @brief Adds spawn position with specified player type mask */ void AddSpawnPosition(std::uint8_t typeMask, std::int32_t x, std::int32_t y); /** @brief Initializes event map state from a stream */ void InitializeFromStream(Stream& src); /** @brief Serializes event map state to a stream */ void SerializeResumableToStream(Stream& dest, bool fromCheckpoint = false); private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct GeneratorInfo { std::int32_t EventPos; EventType Event; std::uint8_t EventParams[EventSpawner::SpawnParamsSize]; std::uint8_t Delay; float TimeLeft; std::shared_ptr SpawnedActor; }; struct SpawnPoint { std::uint8_t PlayerTypeMask; Vector2f Pos; }; struct WarpTarget { std::uint16_t Id; Vector2f Pos; }; #endif ILevelHandler* _levelHandler; Vector2i _layoutSize; PitType _pitType; std::unique_ptr _eventLayout; std::unique_ptr _eventLayoutForRollback; SmallVector _generators; SmallVector _spawnPoints; SmallVector _warpTargets; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Events/EventSpawner.cpp000066400000000000000000000261041512772601700261270ustar00rootroot00000000000000#include "EventSpawner.h" #include "../Actors/Collectibles/AmmoCollectible.h" #include "../Actors/Collectibles/CarrotCollectible.h" #include "../Actors/Collectibles/CarrotFlyCollectible.h" #include "../Actors/Collectibles/CarrotInvincibleCollectible.h" #include "../Actors/Collectibles/CoinCollectible.h" #include "../Actors/Collectibles/FastFireCollectible.h" #include "../Actors/Collectibles/FoodCollectible.h" #include "../Actors/Collectibles/GemCollectible.h" #include "../Actors/Collectibles/GemGiant.h" #include "../Actors/Collectibles/GemRing.h" #include "../Actors/Collectibles/OneUpCollectible.h" #include "../Actors/Collectibles/Stopwatch.h" #include "../Actors/Enemies/Bat.h" #include "../Actors/Enemies/Bee.h" #include "../Actors/Enemies/Caterpillar.h" #include "../Actors/Enemies/Crab.h" #include "../Actors/Enemies/Demon.h" #include "../Actors/Enemies/Doggy.h" #include "../Actors/Enemies/Dragon.h" #include "../Actors/Enemies/Dragonfly.h" #include "../Actors/Enemies/FatChick.h" #include "../Actors/Enemies/Fencer.h" #include "../Actors/Enemies/Fish.h" #include "../Actors/Enemies/Helmut.h" #include "../Actors/Enemies/LabRat.h" #include "../Actors/Enemies/Lizard.h" #include "../Actors/Enemies/LizardFloat.h" #include "../Actors/Enemies/MadderHatter.h" #include "../Actors/Enemies/Monkey.h" #include "../Actors/Enemies/Rapier.h" #include "../Actors/Enemies/Raven.h" #include "../Actors/Enemies/Skeleton.h" #include "../Actors/Enemies/Sparks.h" #include "../Actors/Enemies/Sucker.h" #include "../Actors/Enemies/SuckerFloat.h" #include "../Actors/Enemies/Turtle.h" #include "../Actors/Enemies/TurtleShell.h" #include "../Actors/Enemies/TurtleTough.h" #include "../Actors/Enemies/TurtleTube.h" #include "../Actors/Enemies/Witch.h" #include "../Actors/Enemies/Bosses/Bilsy.h" #include "../Actors/Enemies/Bosses/Bolly.h" #include "../Actors/Enemies/Bosses/Bubba.h" #include "../Actors/Enemies/Bosses/Devan.h" #include "../Actors/Enemies/Bosses/DevanRemote.h" #include "../Actors/Enemies/Bosses/Queen.h" #include "../Actors/Enemies/Bosses/Robot.h" #include "../Actors/Enemies/Bosses/TurtleBoss.h" #include "../Actors/Enemies/Bosses/Uterus.h" #include "../Actors/Environment/AirboardGenerator.h" #include "../Actors/Environment/AmbientBubbles.h" #include "../Actors/Environment/AmbientSound.h" #include "../Actors/Environment/BirdCage.h" #include "../Actors/Environment/Bomb.h" #include "../Actors/Environment/BonusWarp.h" #include "../Actors/Environment/Checkpoint.h" #include "../Actors/Environment/Copter.h" #include "../Actors/Environment/EndOfLevel.h" #include "../Actors/Environment/Eva.h" #include "../Actors/Environment/Moth.h" #include "../Actors/Environment/RollingRock.h" #include "../Actors/Environment/Spring.h" #include "../Actors/Environment/SteamNote.h" #include "../Actors/Environment/SwingingVine.h" #include "../Actors/Lighting/FlickerLight.h" #include "../Actors/Lighting/PulsatingRadialLight.h" #include "../Actors/Lighting/StaticRadialLight.h" #include "../Actors/Solid/AmmoBarrel.h" #include "../Actors/Solid/AmmoCrate.h" #include "../Actors/Solid/BarrelContainer.h" #include "../Actors/Solid/Bridge.h" #include "../Actors/Solid/CrateContainer.h" #include "../Actors/Solid/GemBarrel.h" #include "../Actors/Solid/GemCrate.h" #include "../Actors/Solid/MovingPlatform.h" #include "../Actors/Solid/PinballBumper.h" #include "../Actors/Solid/PinballPaddle.h" #include "../Actors/Solid/Pole.h" #include "../Actors/Solid/PowerUpMorphMonitor.h" #include "../Actors/Solid/PowerUpShieldMonitor.h" #include "../Actors/Solid/PowerUpWeaponMonitor.h" #include "../Actors/Solid/PushableBox.h" #include "../Actors/Solid/SpikeBall.h" #include "../Actors/Solid/TriggerCrate.h" using namespace Jazz2::Actors; namespace Jazz2::Events { EventSpawner::EventSpawner(ILevelHandler* levelHandler) : _levelHandler(levelHandler) { RegisterKnownSpawnables(); } void EventSpawner::RegisterKnownSpawnables() { // Basic RegisterSpawnable(EventType::Checkpoint); // Area RegisterSpawnable(EventType::AreaAmbientSound); RegisterSpawnable(EventType::AreaAmbientBubbles); // Triggers RegisterSpawnable(EventType::TriggerCrate); // Warp RegisterSpawnable(EventType::WarpCoinBonus); // Lights RegisterSpawnable(EventType::LightSteady); RegisterSpawnable(EventType::LightPulse); RegisterSpawnable(EventType::LightFlicker); //RegisterSpawnable(EventType::LightIlluminate, IlluminateLight.Create); // Environment RegisterSpawnable(EventType::Spring); RegisterSpawnable(EventType::Bridge); RegisterSpawnable(EventType::Pole); RegisterSpawnable(EventType::MovingPlatform); RegisterSpawnable(EventType::SpikeBall); RegisterSpawnable(EventType::PushableBox); RegisterSpawnable(EventType::Eva); RegisterSpawnable(EventType::SignEOL); RegisterSpawnable(EventType::Moth); RegisterSpawnable(EventType::SteamNote); RegisterSpawnable(EventType::Bomb); RegisterSpawnable(EventType::PinballBumper); RegisterSpawnable(EventType::PinballPaddle); RegisterSpawnable(EventType::AirboardGenerator); RegisterSpawnable(EventType::Copter); RegisterSpawnable(EventType::RollingRock); RegisterSpawnable(EventType::SwingingVine); // Enemies RegisterSpawnable(EventType::EnemyTurtle); RegisterSpawnable(EventType::EnemyLizard); RegisterSpawnable(EventType::EnemyLizardFloat); RegisterSpawnable(EventType::EnemyDragon); RegisterSpawnable(EventType::EnemySuckerFloat); RegisterSpawnable(EventType::EnemySucker); RegisterSpawnable(EventType::EnemyLabRat); RegisterSpawnable(EventType::EnemyHelmut); RegisterSpawnable(EventType::EnemyBat); RegisterSpawnable(EventType::EnemyFatChick); RegisterSpawnable(EventType::EnemyFencer); RegisterSpawnable(EventType::EnemyRapier); RegisterSpawnable(EventType::EnemySparks); RegisterSpawnable(EventType::EnemyMonkey); RegisterSpawnable(EventType::EnemyDemon); RegisterSpawnable(EventType::EnemyBee); //RegisterSpawnable(EventType::EnemyBeeSwarm, BeeSwarm.Create, BeeSwarm.Preload); RegisterSpawnable(EventType::EnemyCaterpillar); RegisterSpawnable(EventType::EnemyCrab); RegisterSpawnable(EventType::EnemyDoggy); RegisterSpawnable(EventType::EnemyDragonfly); RegisterSpawnable(EventType::EnemyFish); RegisterSpawnable(EventType::EnemyMadderHatter); RegisterSpawnable(EventType::EnemyRaven); RegisterSpawnable(EventType::EnemySkeleton); RegisterSpawnable(EventType::EnemyTurtleTough); RegisterSpawnable(EventType::EnemyTurtleTube); RegisterSpawnable(EventType::EnemyWitch); RegisterSpawnable(EventType::TurtleShell); RegisterSpawnable(EventType::BossBilsy); RegisterSpawnable(EventType::BossDevan); RegisterSpawnable(EventType::BossDevanRemote); RegisterSpawnable(EventType::BossQueen); RegisterSpawnable(EventType::BossRobot); //RegisterSpawnable(EventType::BossTweedle, Tweedle.Create, Tweedle.Preload); RegisterSpawnable(EventType::BossUterus); RegisterSpawnable(EventType::BossTurtleTough); RegisterSpawnable(EventType::BossBubba); RegisterSpawnable(EventType::BossBolly); // Collectibles RegisterSpawnable(EventType::Ammo); RegisterSpawnable(EventType::Food); RegisterSpawnable(EventType::Gem); RegisterSpawnable(EventType::GemGiant); RegisterSpawnable(EventType::GemRing); RegisterSpawnable(EventType::Coin); RegisterSpawnable(EventType::Carrot); RegisterSpawnable(EventType::CarrotFly); RegisterSpawnable(EventType::CarrotInvincible); RegisterSpawnable(EventType::OneUp); RegisterSpawnable(EventType::FastFire); RegisterSpawnable(EventType::Stopwatch); RegisterSpawnable(EventType::CrateAmmo); RegisterSpawnable(EventType::BarrelAmmo); RegisterSpawnable(EventType::Crate); RegisterSpawnable(EventType::Barrel); RegisterSpawnable(EventType::CrateGem); RegisterSpawnable(EventType::BarrelGem); RegisterSpawnable(EventType::PowerUpMorph); RegisterSpawnable(EventType::PowerUpShield); RegisterSpawnable(EventType::PowerUpWeapon); RegisterSpawnable(EventType::BirdCage); } void EventSpawner::RegisterSpawnable(EventType type, CreateDelegate create, PreloadDelegate preload) { _spawnableEvents[type] = { create, preload }; } template void EventSpawner::RegisterSpawnable(EventType type) { _spawnableEvents[type] = { [](const ActorActivationDetails& details) -> std::shared_ptr { std::shared_ptr actor = std::make_shared(); actor->OnActivated(details); return actor; }, T::Preload }; } void EventSpawner::PreloadEvent(EventType type, std::uint8_t* spawnParams) { auto it = _spawnableEvents.find(type); if (it == _spawnableEvents.end() || it->second.PreloadFunction == nullptr) { return; } it->second.PreloadFunction(ActorActivationDetails(_levelHandler, {}, spawnParams)); } std::shared_ptr EventSpawner::SpawnEvent(EventType type, const std::uint8_t* spawnParams, ActorState flags, std::int32_t x, std::int32_t y, std::int32_t z) { return SpawnEvent(type, spawnParams, flags, Vector3i(x * 32 + 16, y * 32 + 16, (std::int32_t)z)); } std::shared_ptr EventSpawner::SpawnEvent(EventType type, const std::uint8_t* spawnParams, ActorState flags, const Vector3i& pos) { auto it = _spawnableEvents.find(type); if (it == _spawnableEvents.end() || it->second.CreateFunction == nullptr) { return nullptr; } ActorActivationDetails details(_levelHandler, pos, spawnParams); details.State = flags; details.Type = type; return it->second.CreateFunction(details); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Events/EventSpawner.h000066400000000000000000000034061512772601700255740ustar00rootroot00000000000000#pragma once #include "../ILevelHandler.h" #include "../Actors/ActorBase.h" #include "../../nCine/Base/HashMap.h" namespace Jazz2::Events { /** @brief Spawns objects in a level */ class EventSpawner { public: /** @brief Delegate to create an object */ using CreateDelegate = std::shared_ptr (*)(const Actors::ActorActivationDetails& details); /** @brief Delegate to preload assets for an object */ using PreloadDelegate = void (*)(const Actors::ActorActivationDetails& details); /** @{ @name Constants */ /** @brief Size of event parameters */ static constexpr std::int32_t SpawnParamsSize = 16; /** @} */ EventSpawner(ILevelHandler* levelHandler); /** @brief Preloads assets for a given event */ void PreloadEvent(EventType type, std::uint8_t* spawnParams); /** @brief Spawns an object for a given event */ std::shared_ptr SpawnEvent(EventType type, const std::uint8_t* spawnParams, Actors::ActorState flags, std::int32_t x, std::int32_t y, std::int32_t z); /** @overload */ std::shared_ptr SpawnEvent(EventType type, const std::uint8_t* spawnParams, Actors::ActorState flags, const Vector3i& pos); /** @brief Registers a delegate to create an object from an event */ void RegisterSpawnable(EventType type, CreateDelegate create, PreloadDelegate preload = nullptr); private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct SpawnableEvent { CreateDelegate CreateFunction; PreloadDelegate PreloadFunction; }; #endif ILevelHandler* _levelHandler; HashMap _spawnableEvents; void RegisterKnownSpawnables(); template void RegisterSpawnable(EventType type); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/ExitType.h000066400000000000000000000011621512772601700234570ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Exit type, supports a bitwise combination of its member values */ enum class ExitType : std::uint8_t { None, /**< Unspecified */ Normal, /**< Standard exit */ Warp, /**< Warp exit */ Bonus, /**< Bonus exit */ Special, /**< Special exit */ Boss, /**< Exit after killing a boxx */ TypeMask = 0x0f, /**< Mask for exit type */ Frozen = 0x40, /**< Player should spawn frozen in the next level */ FastTransition = 0x80 /**< Animated fade transitions should be skipped */ }; DEATH_ENUM_FLAGS(ExitType); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/GameDifficulty.h000066400000000000000000000003731512772601700246030ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Game difficulty */ enum class GameDifficulty : std::uint8_t { Default, /**< Default/unspecified */ Easy, /**< Easy */ Normal, /**< Normal */ Hard /**< Hard */ }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/ILevelHandler.h000066400000000000000000000240401512772601700243620ustar00rootroot00000000000000#pragma once #include "LevelInitialization.h" #include "PlayerAction.h" #include "WarpFlags.h" #include "WeatherType.h" #include "Actors/ActorBase.h" #include "Actors/Enemies/Bosses/BossBase.h" #include "../nCine/Audio/AudioBufferPlayer.h" #include namespace Death::IO { class Stream; } using namespace Death::IO; namespace Jazz2 { namespace Events { class EventSpawner; class EventMap; } namespace Tiles { class TileMap; } namespace Actors { class Player; } /** @brief Base interface of a level handler */ class ILevelHandler { DEATH_RUNTIME_OBJECT(); public: /** @{ @name Constants */ /** @brief Layer of the main plane */ static constexpr std::int32_t MainPlaneZ = 500; /** @brief Layer of sprites */ static constexpr std::int32_t SpritePlaneZ = MainPlaneZ + 10; /** @brief Layer of players */ static constexpr std::int32_t PlayerZ = MainPlaneZ + 20; /** @} */ /** @brief Initializes the level handler from @ref LevelInitialization */ virtual bool Initialize(const LevelInitialization& levelInit) = 0; /** @brief Initializes the level handler from resumable state */ virtual bool Initialize(Stream& src, std::uint16_t version) = 0; /** @brief Returns event spawner for the level */ virtual Events::EventSpawner* EventSpawner() = 0; /** @brief Returns event map for the level */ virtual Events::EventMap* EventMap() = 0; /** @brief Returns tile map for the level */ virtual Tiles::TileMap* TileMap() = 0; /** @brief Return current difficulty */ virtual GameDifficulty GetDifficulty() const = 0; /** @brief Returns `true` if the level handler is on a local session */ virtual bool IsLocalSession() const = 0; /** @brief Returns `true` if the level handler is on a server or a local session */ virtual bool IsServer() const = 0; /** @brief Returns `true` if the level handler is pausable */ virtual bool IsPausable() const = 0; /** @brief Returns `true` if Reforged Gameplay is enabled */ virtual bool IsReforged() const = 0; /** @brief Returns `true` if sugar rush can be activated */ virtual bool CanActivateSugarRush() const = 0; /** @brief Returns `true` if event can be safely despawned */ virtual bool CanEventDisappear(EventType eventType) const = 0; /** @brief Returns `true` if players can collide with each other */ virtual bool CanPlayersCollide() const = 0; /** @brief Returns level bounds including camera limits */ virtual Recti GetLevelBounds() const = 0; /** @brief Returns number of elapsed frames */ virtual float GetElapsedFrames() const = 0; /** @brief Returns current gravity force */ virtual float GetGravity() const = 0; /** @brief Returns current water level */ virtual float GetWaterLevel() const = 0; /** @brief Returns default invulnerable time when a player is hurt */ virtual float GetHurtInvulnerableTime() const = 0; /** @brief Returns list of actors (objects) */ virtual ArrayView> GetActors() const = 0; /** @brief Returns list of players */ virtual ArrayView GetPlayers() const = 0; /** @brief Returns default ambient light intensity */ virtual float GetDefaultAmbientLight() const = 0; /** @brief Returns current ambient light intensity */ virtual float GetAmbientLight(Actors::Player* player) const = 0; /** @brief Sets current ambient light intensity */ virtual void SetAmbientLight(Actors::Player* player, float value) = 0; /** @brief Adds an actor (object) to the level */ virtual void AddActor(std::shared_ptr actor) = 0; /** @brief Plays a sound effect for a given actor (object) */ virtual std::shared_ptr PlaySfx(Actors::ActorBase* self, StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain = 1.0f, float pitch = 1.0f) = 0; /** @brief Plays a common sound effect */ virtual std::shared_ptr PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) = 0; /** @brief Warps a camera to its assigned target */ virtual void WarpCameraToTarget(Actors::ActorBase* actor, bool fast = false) = 0; /** @brief Returns `true` if a specified AABB is empty */ virtual bool IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, Tiles::TileCollisionParams& params, Actors::ActorBase** collider) = 0; /** @overload */ bool IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, Tiles::TileCollisionParams& params) { Actors::ActorBase* collider; return IsPositionEmpty(self, aabb, params, &collider); } /** @brief Calls the callback function for all colliding objects with specified AABB */ virtual void FindCollisionActorsByAABB(const Actors::ActorBase* self, const AABBf& aabb, Function&& callback) = 0; /** @brief Calls the callback function for all colliding objects with specified circle */ virtual void FindCollisionActorsByRadius(float x, float y, float radius, Function&& callback) = 0; /** @brief Calls the callback function for all colliding players with specified AABB */ virtual void GetCollidingPlayers(const AABBf& aabb, Function&& callback) = 0; /** @brief Broadcasts specified event to all other actors */ virtual void BroadcastTriggeredEvent(Actors::ActorBase* initiator, EventType eventType, std::uint8_t* eventParams) = 0; /** @brief Starts transition to change current level */ virtual void BeginLevelChange(Actors::ActorBase* initiator, ExitType exitType, StringView nextLevel = {}) = 0; /** @brief Sends a packet to the other side of a non-local session */ virtual void SendPacket(const Actors::ActorBase* self, ArrayView data) = 0; /** @brief Called when a boss is activated */ virtual void HandleBossActivated(Actors::Bosses::BossBase* boss, Actors::ActorBase* initiator = nullptr) = 0; /** @brief Called when the level is changed */ virtual void HandleLevelChange(LevelInitialization&& levelInit) = 0; /** @brief Called when the game is over */ virtual void HandleGameOver(Actors::Player* player) = 0; /** @brief Called when a player dies */ virtual bool HandlePlayerDied(Actors::Player* player) = 0; /** @brief Called when a player warps */ virtual void HandlePlayerWarped(Actors::Player* player, Vector2f prevPos, WarpFlags flags) = 0; /** @brief Called when a player collects or losts coins */ virtual void HandlePlayerCoins(Actors::Player* player, std::int32_t prevCount, std::int32_t newCount) = 0; /** @brief Called when a player collects or losts gems */ virtual void HandlePlayerGems(Actors::Player* player, std::uint8_t gemType, std::int32_t prevCount, std::int32_t newCount) = 0; /** @brief Sets checkpoint for a given player */ virtual void SetCheckpoint(Actors::Player* player, Vector2f pos) = 0; /** @brief Rolls back to the last checkpoint for a given player */ virtual void RollbackToCheckpoint(Actors::Player* player) = 0; /** @brief Called when a player activates sugar rush */ virtual void HandleActivateSugarRush(Actors::Player* player) = 0; /** @brief Called when an object creates a particle debris on perish */ virtual void HandleCreateParticleDebrisOnPerish(const Actors::ActorBase* self, Actors::ParticleDebrisEffect effect, Vector2f speed) = 0; /** @brief Called when an object creates a sprite debris */ virtual void HandleCreateSpriteDebris(const Actors::ActorBase* self, AnimState state, std::int32_t count) = 0; /** @brief Shows a text notification */ virtual void ShowLevelText(StringView text, Actors::ActorBase* initiator = nullptr) = 0; /** @brief Returns a level text */ virtual StringView GetLevelText(std::uint32_t textId, std::int32_t index = -1, std::uint32_t delimiter = 0) = 0; /** @brief Override specified level text */ virtual void OverrideLevelText(std::uint32_t textId, StringView value) = 0; /** @brief Returns camera position of a given player */ virtual Vector2f GetCameraPos(Actors::Player* player) const = 0; /** @brief Limits camera viewport for a given player */ virtual void LimitCameraView(Actors::Player* player, Vector2f playerPos, std::int32_t left, std::int32_t width) = 0; /** @brief Override camera viewport for a given player */ virtual void OverrideCameraView(Actors::Player* player, float x, float y, bool topLeft = false) = 0; /** @brief Shake camera for a given player */ virtual void ShakeCameraView(Actors::Player* player, float duration) = 0; /** @brief Shake camera for all players near a given position */ virtual void ShakeCameraViewNear(Vector2f pos, float duration) = 0; /** @brief Returns state of a given trigger in the tile map */ virtual bool GetTrigger(std::uint8_t triggerId) = 0; /** @brief Sets state of a given trigger in the tile map */ virtual void SetTrigger(std::uint8_t triggerId, bool newState) = 0; /** @brief Sets current level weather */ virtual void SetWeather(WeatherType type, std::uint8_t intensity) = 0; /** @brief Plays specified music */ virtual bool BeginPlayMusic(StringView path, bool setDefault = false, bool forceReload = false) = 0; /** @brief Returns `true` if player action is pressed */ virtual bool PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads = true) = 0; /** @overload */ virtual bool PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) = 0; /** @brief Returns `true` if player action is hit (newly pressed) */ virtual bool PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads = true) = 0; /** @overload */ virtual bool PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) = 0; /** @brief Returns value of desired horizontal player movement */ virtual float PlayerHorizontalMovement(Actors::Player* player) = 0; /** @brief Returns value of desired vertical player movement */ virtual float PlayerVerticalMovement(Actors::Player* player) = 0; /** @brief Executes a rumble effect */ virtual void PlayerExecuteRumble(Actors::Player* player, StringView rumbleEffect) = 0; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/IResumable.h000066400000000000000000000005101512772601700237300ustar00rootroot00000000000000#pragma once #include "../Main.h" #include using namespace Death::IO; namespace Jazz2 { /** @brief Base interface of a resumable object */ class IResumable { public: IResumable() {} /** @brief Serializes object state to a stream */ virtual bool SerializeResumableToStream(Stream& dest) = 0; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/IRootController.h000066400000000000000000000044641512772601700250140ustar00rootroot00000000000000#pragma once #include "../Main.h" #include "LevelInitialization.h" #if defined(WITH_MULTIPLAYER) # include "Multiplayer/ServerInitialization.h" #endif #include using namespace Death::Containers; namespace Jazz2 { /** @brief Base interface of a root controller */ class IRootController { public: /** @brief State flags of root controller, supports a bitwise combination of its member values */ enum class Flags { None = 0x00, IsInitialized = 0x01, IsVerified = 0x02, IsPlayable = 0x04, #if defined(DEATH_TARGET_ANDROID) HasExternalStoragePermission = 0x10, HasExternalStoragePermissionOnResume = 0x20, #endif }; DEATH_PRIVATE_ENUM_FLAGS(Flags); IRootController() { } virtual ~IRootController() { } IRootController(const IRootController&) = delete; IRootController& operator=(const IRootController&) = delete; /** @brief Invokes the specified callback asynchronously, usually at the end of current frame */ virtual void InvokeAsync(Function&& callback) = 0; /** @overload */ virtual void InvokeAsync(std::weak_ptr reference, Function&& callback) = 0; /** @brief Sets current state handler to main menu */ virtual void GoToMainMenu(bool afterIntro) = 0; /** @brief Sets current state handler to level described by @ref LevelInitialization */ virtual void ChangeLevel(LevelInitialization&& levelInit) = 0; /** @brief Returns `true` if any resumable state exists */ virtual bool HasResumableState() const = 0; /** @brief Resumes saved resumable state */ virtual void ResumeSavedState() = 0; /** @brief Saves current state if it's resumable */ virtual bool SaveCurrentStateIfAny() = 0; #if defined(WITH_MULTIPLAYER) /** @brief Connects to a multiplayer server asynchronously */ virtual void ConnectToServer(StringView endpoint, std::uint16_t defaultPort, StringView password = {}) = 0; /** @brief Creates a multiplayer server */ virtual bool CreateServer(Multiplayer::ServerInitialization&& serverInit) = 0; #endif /** @brief Returns current state flags */ virtual Flags GetFlags() const = 0; /** @brief Returns version of the latest update */ virtual StringView GetNewestVersion() const = 0; /** @brief Recreates level cache from `Source` directory */ virtual void RefreshCacheLevels(bool recreateAll) = 0; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/IStateHandler.h000066400000000000000000000027001512772601700243720ustar00rootroot00000000000000#pragma once #include "../Main.h" #include "../nCine/Input/InputEvents.h" #include "../nCine/Primitives/Vector2.h" #include #include namespace Jazz2 { /** @brief Base interface of a state handler, only one handler runs at a time */ class IStateHandler : public std::enable_shared_from_this { DEATH_RUNTIME_OBJECT(); public: IStateHandler() {} virtual ~IStateHandler() {} IStateHandler(const IStateHandler&) = delete; IStateHandler& operator=(const IStateHandler&) = delete; /** @brief Returns viewport size of the handler */ virtual nCine::Vector2i GetViewSize() const { return {}; } /** @brief Called at the beginning of each frame */ virtual void OnBeginFrame() {} /** @brief Called at the end of each frame */ virtual void OnEndFrame() {} /** @brief Called when the viewport needs to be initialized (e.g., when the resolution is changed) */ virtual void OnInitializeViewport(std::int32_t width, std::int32_t height) {} /** @brief Called when a key is pressed */ virtual void OnKeyPressed(const nCine::KeyboardEvent& event) {} /** @brief Called when a key is released */ virtual void OnKeyReleased(const nCine::KeyboardEvent& event) {} /** @brief Called when a text input is detected */ virtual void OnTextInput(const nCine::TextInputEvent& event) {} /** @brief Called when a touch event is triggered */ virtual void OnTouchEvent(const nCine::TouchEvent& event) {} }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/000077500000000000000000000000001512772601700226325ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/ControlScheme.cpp000066400000000000000000000435251512772601700261140ustar00rootroot00000000000000#include "ControlScheme.h" #include "../PreferencesCache.h" #include "../../nCine/Input/IInputManager.h" using namespace nCine; namespace Jazz2::Input { ControlSchemeMapping ControlScheme::_mappings[MaxSupportedPlayers * (std::int32_t)PlayerAction::Count] = {}; void ControlScheme::Reset() { // Clear all mappings for (auto& mapping : _mappings) { mapping.Targets.clear(); } // Set default mappings for 1st player auto first = GetMappings(0); first[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(Keys::Left)); first[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(0, ButtonName::Left)); first[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(0, AxisName::LeftX, true)); first[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(Keys::Right)); first[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(0, ButtonName::Right)); first[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(0, AxisName::LeftX)); first[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(Keys::Up)); first[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(0, ButtonName::Up)); first[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(0, AxisName::LeftY, true)); first[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(Keys::Down)); first[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(0, ButtonName::Down)); first[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(0, AxisName::LeftY)); first[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(Keys::Down)); first[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(0, ButtonName::Down)); first[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(0, AxisName::LeftY)); first[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(Keys::Space)); first[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(0, ButtonName::X)); first[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(0, AxisName::RightTrigger)); first[(std::int32_t)PlayerAction::Jump].Targets.push_back(CreateTarget(Keys::V)); first[(std::int32_t)PlayerAction::Jump].Targets.push_back(CreateTarget(0, ButtonName::A)); first[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(Keys::C)); first[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(0, ButtonName::B)); first[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(0, AxisName::LeftTrigger)); first[(std::int32_t)PlayerAction::ChangeWeapon].Targets.push_back(CreateTarget(Keys::X)); first[(std::int32_t)PlayerAction::ChangeWeapon].Targets.push_back(CreateTarget(0, ButtonName::Y)); first[(std::int32_t)PlayerAction::Menu].Targets.push_back(CreateTarget(Keys::Escape)); first[(std::int32_t)PlayerAction::Menu].Targets.push_back(CreateTarget(0, ButtonName::Start)); first[(std::int32_t)PlayerAction::Console].Targets.push_back(CreateTarget(Keys::Backquote)); first[(std::int32_t)PlayerAction::Console].Targets.push_back(CreateTarget(Keys::T)); first[(std::int32_t)PlayerAction::SwitchToBlaster].Targets.push_back(CreateTarget(Keys::D1)); first[(std::int32_t)PlayerAction::SwitchToBouncer].Targets.push_back(CreateTarget(Keys::D2)); first[(std::int32_t)PlayerAction::SwitchToFreezer].Targets.push_back(CreateTarget(Keys::D3)); first[(std::int32_t)PlayerAction::SwitchToSeeker].Targets.push_back(CreateTarget(Keys::D4)); first[(std::int32_t)PlayerAction::SwitchToRF].Targets.push_back(CreateTarget(Keys::D5)); first[(std::int32_t)PlayerAction::SwitchToToaster].Targets.push_back(CreateTarget(Keys::D6)); first[(std::int32_t)PlayerAction::SwitchToTNT].Targets.push_back(CreateTarget(Keys::D7)); first[(std::int32_t)PlayerAction::SwitchToPepper].Targets.push_back(CreateTarget(Keys::D8)); first[(std::int32_t)PlayerAction::SwitchToElectro].Targets.push_back(CreateTarget(Keys::D9)); first[(std::int32_t)PlayerAction::SwitchToThunderbolt].Targets.push_back(CreateTarget(Keys::D0)); // Set default mappings for 2nd player if (MaxSupportedPlayers >= 2) { auto second = GetMappings(1); second[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(Keys::A)); second[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(Keys::D)); second[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(Keys::W)); second[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(Keys::S)); second[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(Keys::S)); second[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(Keys::R)); second[(std::int32_t)PlayerAction::Jump].Targets.push_back(CreateTarget(Keys::F)); second[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(Keys::G)); second[(std::int32_t)PlayerAction::ChangeWeapon].Targets.push_back(CreateTarget(Keys::Q)); if (MaxSupportedPlayers >= 3) { auto third = GetMappings(2); third[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(Keys::NumPad4)); third[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(Keys::NumPad6)); third[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(Keys::NumPad8)); third[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(Keys::NumPad5)); third[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(Keys::NumPad5)); third[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(Keys::NumPadEnter)); third[(std::int32_t)PlayerAction::Jump].Targets.push_back(CreateTarget(Keys::NumPadPlus)); third[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(Keys::NumPadMinus)); third[(std::int32_t)PlayerAction::ChangeWeapon].Targets.push_back(CreateTarget(Keys::NumPad7)); if (MaxSupportedPlayers >= 4) { auto fourth = GetMappings(3); fourth[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(Keys::H)); fourth[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(Keys::K)); fourth[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(Keys::U)); fourth[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(Keys::J)); fourth[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(Keys::J)); fourth[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(Keys::O)); fourth[(std::int32_t)PlayerAction::Jump].Targets.push_back(CreateTarget(Keys::L)); fourth[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(Keys::Semicolon)); fourth[(std::int32_t)PlayerAction::ChangeWeapon].Targets.push_back(CreateTarget(Keys::Y)); } } for (std::int32_t i = 1; i < MaxSupportedPlayers; i++) { auto current = GetMappings(i); current[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(i, ButtonName::Left)); current[(std::int32_t)PlayerAction::Left].Targets.push_back(CreateTarget(i, AxisName::LeftX, true)); current[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(i, ButtonName::Right)); current[(std::int32_t)PlayerAction::Right].Targets.push_back(CreateTarget(i, AxisName::LeftX)); current[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(i, ButtonName::Up)); current[(std::int32_t)PlayerAction::Up].Targets.push_back(CreateTarget(i, AxisName::LeftY, true)); current[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(i, ButtonName::Down)); current[(std::int32_t)PlayerAction::Down].Targets.push_back(CreateTarget(i, AxisName::LeftY)); current[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(i, ButtonName::Down)); current[(std::int32_t)PlayerAction::Buttstomp].Targets.push_back(CreateTarget(i, AxisName::LeftY)); current[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(i, ButtonName::X)); current[(std::int32_t)PlayerAction::Fire].Targets.push_back(CreateTarget(i, AxisName::RightTrigger)); current[(std::int32_t)PlayerAction::Jump].Targets.push_back(CreateTarget(i, ButtonName::A)); current[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(i, ButtonName::B)); current[(std::int32_t)PlayerAction::Run].Targets.push_back(CreateTarget(i, AxisName::LeftTrigger)); current[(std::int32_t)PlayerAction::ChangeWeapon].Targets.push_back(CreateTarget(i, ButtonName::Y)); current[(std::int32_t)PlayerAction::Menu].Targets.push_back(CreateTarget(i, ButtonName::Start)); } } } ProcessedInput ControlScheme::FetchProcessedInput(std::int32_t playerIndex, const BitArray& pressedKeys, const ArrayView joyStates, std::uint64_t prevPressedActions, bool analogAsButtons) { ProcessedInput result = {}; std::size_t joyStateCount = joyStates.size(); auto mappings = GetMappings(playerIndex); for (std::size_t i = 0; i < (std::size_t)PlayerAction::Count; i++) { for (const auto& target : mappings[i].Targets) { if (target.Data & GamepadMask) { // Gamepad std::uint32_t joyIdx = (target.Data & GamepadIndexMask) >> 16; if (joyIdx < joyStateCount) { if (target.Data & GamepadAnalogMask) { // Analog axis AxisName axisName = (AxisName)(target.Data & ButtonMask); float axisValue = joyStates[joyIdx]->axisValue(axisName); if (target.Data & GamepadNegativeMask) { axisValue = -axisValue; } std::uint64_t maskedBits = (1ull << (std::uint32_t)i) | (1ull << (32 + (std::uint32_t)i)); if (analogAsButtons && axisValue >= (axisName >= AxisName::LeftTrigger ? IInputManager::TriggerButtonDeadZone : ((prevPressedActions & maskedBits) != maskedBits ? IInputManager::AnalogInButtonDeadZone : IInputManager::AnalogOutButtonDeadZone))) { result.PressedActions |= maskedBits; } if (i < 4 && axisValue > GamepadDeadZone) { switch ((PlayerAction)i) { case PlayerAction::Left: { float axisValueAdjusted = -axisValue; if (result.Movement.X < GamepadDeadZone && axisValueAdjusted < result.Movement.X) { result.Movement.X = axisValueAdjusted; } break; } case PlayerAction::Right: { if (result.Movement.X > -GamepadDeadZone && axisValue > result.Movement.X) { result.Movement.X = axisValue; } break; } case PlayerAction::Up: { float axisValueAdjusted = -axisValue; if (result.Movement.Y < GamepadDeadZone && axisValueAdjusted < result.Movement.Y) { result.Movement.Y = axisValueAdjusted; } break; } case PlayerAction::Down: { if (result.Movement.Y > -GamepadDeadZone && axisValue > result.Movement.Y) { result.Movement.Y = axisValue; } break; } } } } else { // Button if (joyStates[joyIdx]->isButtonPressed((ButtonName)(target.Data & ButtonMask))) { result.PressedActions |= (1ull << (std::uint32_t)i) | (1ull << (32 + (std::uint32_t)i)); if (analogAsButtons) { switch ((PlayerAction)i) { case PlayerAction::Left: result.Movement.X = -1.0f; break; case PlayerAction::Right: result.Movement.X = 1.0f; break; case PlayerAction::Up: result.Movement.Y = -1.0f; break; case PlayerAction::Down: result.Movement.Y = 1.0f; break; } } break; } } } } else { // Keyboard if (pressedKeys[target.Data]) { result.PressedActions |= (1ull << (std::uint32_t)i); if (analogAsButtons) { switch ((PlayerAction)i) { case PlayerAction::Left: result.Movement.X = -1.0f; break; case PlayerAction::Right: result.Movement.X = 1.0f; break; case PlayerAction::Up: result.Movement.Y = -1.0f; break; case PlayerAction::Down: result.Movement.Y = 1.0f; break; } } break; } } } } // Normalize both axes float movementLengthX = std::abs(result.Movement.X); float normalizedLength = (movementLengthX - GamepadDeadZone) / (1.0f - GamepadDeadZone * 2.0f); normalizedLength = std::clamp(normalizedLength, 0.0f, 1.0f); result.Movement.X = std::copysign(normalizedLength, result.Movement.X); float movementLengthY = std::abs(result.Movement.Y); normalizedLength = (movementLengthY - GamepadDeadZone) / (1.0f - GamepadDeadZone * 2.0f); normalizedLength = std::clamp(normalizedLength, 0.0f, 1.0f); result.Movement.Y = std::copysign(normalizedLength, result.Movement.Y); #if defined(DEATH_TARGET_ANDROID) // Allow native Android back button as menu key if (PreferencesCache::UseNativeBackButton && pressedKeys[(uint32_t)Keys::Back]) { result.PressedActions |= (1 << (int32_t)PlayerAction::Menu); } #endif return result; } std::uint32_t ControlScheme::FetchNavigation(const BitArray& pressedKeys, const ArrayView joyStates, NavigationFlags flags) { std::uint32_t pressedActions = 0; std::size_t joyStateCount = joyStates.size(); bool allowGamepads = (flags & NavigationFlags::AllowGamepads) == NavigationFlags::AllowGamepads; bool allowKeyboard = (flags & NavigationFlags::AllowKeyboard) == NavigationFlags::AllowKeyboard; for (std::int32_t j = 0; j < MaxSupportedPlayers; j++) { const auto* mappings = &_mappings[j * (std::int32_t)PlayerAction::Count]; for (std::size_t i = 0; i < (std::size_t)PlayerAction::CountInMenu; i++) { for (const auto& target : mappings[i].Targets) { if (target.Data & GamepadMask) { // Gamepad std::uint32_t joyIdx = (target.Data & GamepadIndexMask) >> 16; if (allowGamepads && joyIdx < joyStateCount) { if (target.Data & GamepadAnalogMask) { // Analog axis AxisName axisName = (AxisName)(target.Data & ButtonMask); float axisValue = joyStates[joyIdx]->axisValue(axisName); if (target.Data & GamepadNegativeMask) { axisValue = -axisValue; } if (axisValue >= (axisName >= AxisName::LeftTrigger ? IInputManager::TriggerButtonDeadZone : IInputManager::AnalogOutButtonDeadZone)) { pressedActions |= (1 << (std::uint32_t)i); } } else { // Button bool isPressed = false; switch ((PlayerAction)i) { case PlayerAction::Up: case PlayerAction::Down: case PlayerAction::Left: case PlayerAction::Right: isPressed = (joyStates[joyIdx]->isButtonPressed((ButtonName)(target.Data & ButtonMask))); break; case PlayerAction::Fire: isPressed = (joyStates[joyIdx]->isButtonPressed(ButtonName::A) || joyStates[joyIdx]->isButtonPressed(ButtonName::X)); break; case PlayerAction::Menu: isPressed = (joyStates[joyIdx]->isButtonPressed(ButtonName::B) || joyStates[joyIdx]->isButtonPressed(ButtonName::Start)); break; case PlayerAction::ChangeWeapon: isPressed = (joyStates[joyIdx]->isButtonPressed(ButtonName::Y)); break; } if (isPressed) { pressedActions |= (1 << (std::uint32_t)i); break; } } } } else if (allowKeyboard) { // Keyboard if (pressedKeys[target.Data]) { pressedActions |= (1 << (std::uint32_t)i); break; } } } } } // Allow Jump action as confirm key if (pressedActions & (1 << (std::uint32_t)PlayerAction::Jump)) { pressedActions |= (1 << (std::uint32_t)PlayerAction::Fire); } if (allowKeyboard) { // Also allow Return (Enter) as confirm key if (pressedKeys[(std::uint32_t)Keys::Return] || pressedKeys[(std::uint32_t)Keys::NumPadEnter]) { pressedActions |= (1 << (std::int32_t)PlayerAction::Fire); } // Use ChangeWeapon action as delete key if (pressedKeys[(std::uint32_t)Keys::Delete]) { pressedActions |= (1 << (std::int32_t)PlayerAction::ChangeWeapon); } } #if defined(DEATH_TARGET_ANDROID) // Allow native Android back button as menu key if (PreferencesCache::UseNativeBackButton && pressedKeys[(std::uint32_t)Keys::Back]) { pressedActions |= (1 << (std::int32_t)PlayerAction::Menu); } #endif return pressedActions; } ArrayView ControlScheme::GetAllMappings() { return _mappings; } ArrayView ControlScheme::GetMappings(std::int32_t playerIdx) { return ArrayView(&_mappings[playerIdx * (std::int32_t)PlayerAction::Count], (std::int32_t)PlayerAction::Count); } std::int32_t ControlScheme::GetGamepadForPlayer(std::int32_t playerIdx) { const auto* mappings = &_mappings[playerIdx * (std::int32_t)PlayerAction::Count]; for (std::size_t i = 0; i < (std::size_t)PlayerAction::CountInMenu; i++) { for (const auto& target : mappings[i].Targets) { if (target.Data & GamepadMask) { std::uint32_t joyIdx = (target.Data & GamepadIndexMask) >> 16; return joyIdx; } } } return -1; } bool ControlScheme::ContainsTarget(PlayerAction action, MappingTarget target) { std::int32_t index = (std::int32_t)action; if (index < (std::int32_t)PlayerAction::Count) { for (std::int32_t j = 0; j < MaxSupportedPlayers; j++) { const auto& mappings = _mappings[j * (std::int32_t)PlayerAction::Count + index]; for (const auto& t : mappings.Targets) { if (t.Data == target.Data) { return true; } } } } return false; } MappingTarget ControlScheme::CreateTarget(Keys key) { return { (std::uint32_t)key }; } MappingTarget ControlScheme::CreateTarget(std::uint32_t gamepadIndex, ButtonName button) { return { GamepadMask | ((gamepadIndex << 16) & GamepadIndexMask) | (std::uint32_t)button }; } MappingTarget ControlScheme::CreateTarget(std::uint32_t gamepadIndex, AxisName axis, bool negative) { std::uint32_t result = GamepadMask | GamepadAnalogMask | ((gamepadIndex << 16) & GamepadIndexMask) | (std::uint32_t)axis; if (negative) { result |= GamepadNegativeMask; } return { result }; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/ControlScheme.h000066400000000000000000000100661512772601700255530ustar00rootroot00000000000000#pragma once #include "../PlayerAction.h" #include "../../nCine/Base/BitArray.h" #include "../../nCine/Input/InputEvents.h" #include "../../nCine/Primitives/Vector2.h" #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::UI::Menu { class RemapControlsSection; } namespace Jazz2::Input { /** @brief Navigation flags for @ref ControlScheme::FetchNavigation(), supports a bitwise combination of its member values */ enum class NavigationFlags { /** @brief None */ None = 0, /** @brief Allow only keyboard key presses */ AllowKeyboard = 0x01, /** @brief Allow only gamepad button presses */ AllowGamepads = 0x02, /** @brief Allow both keyboard and gamepad presses */ AllowAll = AllowKeyboard | AllowGamepads }; DEATH_ENUM_FLAGS(NavigationFlags); /** @brief Control mapping target */ struct MappingTarget { /** @brief Opaque data of the target */ std::uint32_t Data; }; /** @brief Control mapping for a particular action */ struct ControlSchemeMapping { /** @brief List of mapping targets */ SmallVector Targets; }; /** @brief Result returned by @ref ControlScheme::FetchProcessedInput() */ struct ProcessedInput { /** @brief Pressed actions */ std::uint64_t PressedActions; /** @brief Movement vector */ Vector2f Movement; }; /** @brief Provides access to a user configured control scheme */ class ControlScheme { friend class UI::Menu::RemapControlsSection; public: ControlScheme() = delete; ~ControlScheme() = delete; /** @{ @name Constants */ /** @brief Maximum number of supported local players */ static constexpr std::int32_t MaxSupportedPlayers = 4; /** @brief Maximum number of supported connected gamepads */ #if defined(DEATH_TARGET_EMSCRIPTEN) || defined(DEATH_TARGET_SWITCH) || defined(DEATH_TARGET_WINDOWS_RT) static constexpr std::int32_t MaxConnectedGamepads = 4; #else static constexpr std::int32_t MaxConnectedGamepads = 6; #endif /** @} */ /** @brief Resets all bindings to default */ static void Reset(); /** @brief Fetches processed standard input for specified player according to the current bindings */ static ProcessedInput FetchProcessedInput(std::int32_t playerIndex, const BitArray& pressedKeys, const ArrayView joyStates, std::uint64_t prevPressedActions, bool analogAsButtons = true); /** @brief Fetches navigation input according to the current bindings */ static std::uint32_t FetchNavigation(const BitArray& pressedKeys, const ArrayView joyStates, NavigationFlags flags = NavigationFlags::AllowAll); /** @brief Returns the entire mapping configuration */ static ArrayView GetAllMappings(); /** @brief Returns a mapping configuration for a given player index */ static ArrayView GetMappings(std::int32_t playerIdx); /** @brief Returns a gamepad index for a given player index */ static std::int32_t GetGamepadForPlayer(std::int32_t playerIdx); /** @brief Returns `true` if the action of any player is bound the specified target */ static bool ContainsTarget(PlayerAction action, MappingTarget target); /** @brief Creates mapping target for a given key */ static MappingTarget CreateTarget(Keys key); /** @brief Creates mapping target for a given gamepad button */ static MappingTarget CreateTarget(std::uint32_t gamepadIndex, ButtonName button); /** @brief Creates mapping target for a given gamepad axis */ static MappingTarget CreateTarget(std::uint32_t gamepadIndex, AxisName axis, bool negative = false); private: static constexpr std::uint32_t GamepadMask = 0x80000000u; static constexpr std::uint32_t GamepadAnalogMask = 0x40000000u; static constexpr std::uint32_t GamepadNegativeMask = 0x20000000u; static constexpr std::uint32_t GamepadIndexMask = 0x0FFF0000u; static constexpr std::uint32_t ButtonMask = 0x0000FFFFu; static constexpr float GamepadDeadZone = 0.1f; static ControlSchemeMapping _mappings[MaxSupportedPlayers * (std::int32_t)PlayerAction::Count]; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/RgbLights.cpp000066400000000000000000000175231512772601700252330ustar00rootroot00000000000000#include "RgbLights.h" #include #include #define COLORS_LIMITED_SIZE 4 #define KEYBOARD_WIDTH 22 #define KEYBOARD_HEIGHT 6 #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) // Remapping from Razer to Aura™ indices static const std::uint8_t KeyLayout[] = { 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 81, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 83, 84, 85, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 104, 106, 107, 108, 109, 111, 112, 113, 117, 119, 121, 122, 123, 125, 126, 127, 128, 130 }; #elif defined(DEATH_TARGET_EMSCRIPTEN) // Remapping from Razer to Aura™ indices static const std::uint8_t KeyLayout[] = { 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 80, 59, 60, 61, 62, 63, 64, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 84, 85, 86, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 102, 104, 106, 107, 108, 109, 111, 112, 113, 117, 119, 121, 122, 123, 125, 126, 127, 129, 130 }; #endif namespace Jazz2::Input { RgbLights& RgbLights::Get() { static RgbLights current; return current; } RgbLights::RgbLights() { #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) # if defined(DEATH_TARGET_32BIT) _hLib = ::LoadLibrary(L"RzChromaSDK.dll"); # else _hLib = ::LoadLibrary(L"RzChromaSDK64.dll"); if (_hLib == NULL) { _hLib = ::LoadLibrary(L"RzChromaSDK.dll"); } # endif if (_hLib != NULL) { RzInit init = (RzInit)::GetProcAddress(_hLib, "Init"); _UnInit = (RzUnInit)::GetProcAddress(_hLib, "UnInit"); _CreateKeyboardEffect = (RzCreateKeyboardEffect)::GetProcAddress(_hLib, "CreateKeyboardEffect"); std::memset(_lastColors, 0, sizeof(_lastColors)); init(); } else { _UnInit = nullptr; _CreateKeyboardEffect = nullptr; } #elif defined(DEATH_TARGET_EMSCRIPTEN) _updateCount = 0; _ws = NULL; _isConnected = false; std::memset(_lastColors, 0, sizeof(_lastColors)); if (emscripten_websocket_is_supported()) { EmscriptenWebSocketCreateAttributes ws_attrs = { "wss://chromasdk.io:13339/razer/chromasdk", NULL, EM_FALSE }; _ws = emscripten_websocket_new(&ws_attrs); emscripten_websocket_set_onopen_callback(_ws, this, emscriptenOnOpen); emscripten_websocket_set_onerror_callback(_ws, this, emscriptenOnError); emscripten_websocket_set_onclose_callback(_ws, this, emscriptenOnClose); emscripten_websocket_set_onmessage_callback(_ws, this, emscriptenOnMessage); } #endif } RgbLights::~RgbLights() { #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) if (_UnInit != nullptr) { _UnInit(); } if (_hLib != NULL) { ::FreeLibrary(_hLib); _hLib = NULL; } #elif defined(DEATH_TARGET_EMSCRIPTEN) _isConnected = false; if (_ws != NULL) { emscripten_websocket_delete(_ws); _ws = NULL; } #endif } bool RgbLights::IsSupported() const { #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) return (_CreateKeyboardEffect != nullptr); #elif defined(DEATH_TARGET_EMSCRIPTEN) return _isConnected; #else return false; #endif } void RgbLights::Update(Color colors[ColorsSize]) { #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) if (_CreateKeyboardEffect == nullptr) { return; } if (std::memcmp(_lastColors, colors, sizeof(_lastColors)) == 0) { return; } std::memcpy(_lastColors, colors, sizeof(_lastColors)); ChromaSDK::Keyboard::CUSTOM_EFFECT_TYPE param = { }; for (std::int32_t i = COLORS_LIMITED_SIZE; i < ColorsSize; i++) { std::int32_t idx = KeyLayout[i - COLORS_LIMITED_SIZE]; param.Color[idx / KEYBOARD_WIDTH][idx % KEYBOARD_WIDTH] = colors[i].Abgr(); } _CreateKeyboardEffect(ChromaSDK::Keyboard::CHROMA_CUSTOM, ¶m, nullptr); #elif defined(DEATH_TARGET_EMSCRIPTEN) if (!_isConnected) { return; } if (std::memcmp(_lastColors, colors, sizeof(_lastColors)) == 0) { return; } std::memcpy(_lastColors, colors, sizeof(_lastColors)); std::uint32_t buffer[KEYBOARD_WIDTH * KEYBOARD_HEIGHT] { }; for (std::int32_t i = COLORS_LIMITED_SIZE; i < ColorsSize; i++) { buffer[KeyLayout[i - COLORS_LIMITED_SIZE]] = colors[i].Abgr(); } std::string request; request.reserve(2048); request.append(R"({"endpoint":"keyboard","effect":"CHROMA_CUSTOM","token":)"); _updateCount++; request.append(std::to_string(_updateCount)); request.append(R"(,"param":[)"); for (std::int32_t i = 0; i < KEYBOARD_HEIGHT; i++) { if (i > 0) { request.append(","); } request.append("["); for (std::int32_t j = 0; j < KEYBOARD_WIDTH; j++) { if (j > 0) { request.append(","); } request.append(std::to_string(buffer[i * KEYBOARD_WIDTH + j])); } request.append("]"); } request.append("]}"); EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(_ws, request.c_str()); if (result) { LOGE("Request failed: {}", result); } #endif } void RgbLights::Clear() { #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) if (_CreateKeyboardEffect == nullptr) { return; } bool isEmpty = true; for (std::int32_t i = 0; i < ColorsSize; i++) { if (_lastColors[i] != Color(0, 0, 0, 0)) { isEmpty = false; break; } } if (isEmpty) { return; } std::memset(_lastColors, 0, sizeof(_lastColors)); _CreateKeyboardEffect(ChromaSDK::Keyboard::CHROMA_NONE, nullptr, nullptr); #elif defined(DEATH_TARGET_EMSCRIPTEN) if (!_isConnected) { return; } bool isEmpty = true; for (std::int32_t i = 0; i < ColorsSize; i++) { if (_lastColors[i] != Color(0, 0, 0, 0)) { isEmpty = false; break; } } if (isEmpty) { return; } std::memset(_lastColors, 0, sizeof(_lastColors)); std::string request; request.reserve(80); request.append(R"({"endpoint":"keyboard","effect":"CHROMA_NONE","token":)"); _updateCount++; request.append(std::to_string(_updateCount)); request.append("}"); EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(_ws, request.c_str()); if (result) { LOGE("Request failed: {}", result); } #endif } #if defined(DEATH_TARGET_EMSCRIPTEN) EM_BOOL RgbLights::emscriptenOnOpen(int32_t eventType, const EmscriptenWebSocketOpenEvent* websocketEvent, void* userData) { static const char Response[] = R"({"title":"Jazz² Resurrection","description":"Jazz² Resurrection","author":{"name":"Dan R.","contact":"http://deat.tk/jazz2/"},"device_supported":["keyboard","mouse"],"category":"game","ext":"Aura™ Service"})"; EMSCRIPTEN_RESULT result = emscripten_websocket_send_utf8_text(websocketEvent->socket, Response); if (result) { LOGE("Response failed: {}", result); } else { RgbLights* _this = static_cast(userData); _this->_isConnected = true; } return EM_TRUE; } EM_BOOL RgbLights::emscriptenOnError(std::int32_t eventType, const EmscriptenWebSocketErrorEvent* websocketEvent, void* userData) { RgbLights* _this = static_cast(userData); _this->_isConnected = false; if (_this->_ws != NULL) { emscripten_websocket_delete(_this->_ws); _this->_ws = NULL; } return EM_TRUE; } EM_BOOL RgbLights::emscriptenOnClose(std::int32_t eventType, const EmscriptenWebSocketCloseEvent* websocketEvent, void* userData) { RgbLights* _this = static_cast(userData); _this->_isConnected = false; if (_this->_ws != NULL) { emscripten_websocket_delete(_this->_ws); _this->_ws = NULL; } return EM_TRUE; } EM_BOOL RgbLights::emscriptenOnMessage(std::int32_t eventType, const EmscriptenWebSocketMessageEvent* websocketEvent, void* userData) { // Server usually doesn't send anything back return EM_TRUE; } #endif }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/RgbLights.h000066400000000000000000000112601512772601700246700ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "../../nCine/Primitives/Color.h" #include "../../nCine/Base/FrameTimer.h" #if defined(DEATH_TARGET_EMSCRIPTEN) # include # include #endif #include using namespace nCine; #ifndef DOXYGEN_GENERATING_OUTPUT #define AURA_REFRESH_INTERVAL 40 // 40 ms #define AURA_COLORS_SIZE (4 + 105) // 4 Main colors + Keyboard mapping = 327 bytes, see AuraLight struct #define AURA_COLORS_LIMITED_SIZE 4 // Only first 4 main colors #define AURA_KEYBOARD_WIDTH 22 #define AURA_KEYBOARD_HEIGHT 6 #if defined(DEATH_TARGET_WINDOWS) namespace ChromaSDK::Keyboard { //! Chroma keyboard effect types typedef enum EFFECT_TYPE { CHROMA_NONE = 0, //!< No effect. CHROMA_BREATHING, //!< Breathing effect (This effect has deprecated and should not be used). CHROMA_CUSTOM, //!< Custom effect. CHROMA_REACTIVE, //!< Reactive effect (This effect has deprecated and should not be used). CHROMA_STATIC, //!< Static effect. CHROMA_SPECTRUMCYCLING, //!< Spectrum cycling effect (This effect has deprecated and should not be used). CHROMA_WAVE, //!< Wave effect (This effect has deprecated and should not be used). CHROMA_RESERVED, //!< Reserved. CHROMA_CUSTOM_KEY, //!< Custom effects with keys. CHROMA_CUSTOM2, CHROMA_INVALID //!< Invalid effect. } EFFECT_TYPE; //! Maximum number of rows in a keyboard. const std::size_t MAX_ROW = 6; //! Maximum number of columns in a keyboard. const std::size_t MAX_COLUMN = 22; //! Custom effect (This effect type has deprecated and should not be used). typedef struct CUSTOM_EFFECT_TYPE { COLORREF Color[MAX_ROW][MAX_COLUMN]; //!< Grid layout. 6 rows by 22 columns. } CUSTOM_EFFECT_TYPE; } #endif #endif namespace Jazz2::Input { /** @brief Individual lights of @ref RgbLights service */ enum struct AuraLight { // Primary / Logo / Center Primary, // Secondary / Scroll / Left Secondary, // Tertiary / Underglow / Right Tertiary, // Keyboard Logo KeyboardLogo, Esc, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, PauseBreak, Tilde, One, Two, Three, Four, Five, Six, Seven, Eight, Nine, Zero, Minus, Equals, Backspace, Insert, Home, PageUp, NumLock, NumSlash, NumAsterisk, NumMinus, Tab, Q, W, E, R, T, Y, U, I, O, P, OpenBracket, CloseBracket, Enter, Delete, End, PageDown, NumSeven, NumEight, NumNine, NumPlus, CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Apostrophe, Backslash, NumFour, NumFive, NumSix, LeftShift, NonUsBackslash, Z, X, C, V, B, N, M, Comma, Period, Slash, RightShift, ArrowUp, NumOne, NumTwo, NumThree, NumEnter, LeftCtrl, LeftWindows, LeftAlt, Space, RightAlt, Fn, Menu, RightCtrl, ArrowLeft, ArrowDown, ArrowRight, NumZero, NumPeriod, Unknown = -1 }; /** @brief Provides access to RGB lights from various peripherals */ class RgbLights { public: /** @{ @name Constants */ /** @brief Maximum number of individual lights */ static constexpr std::int32_t ColorsSize = AURA_COLORS_SIZE; /** @brief Refresh rate of the lighting */ static constexpr std::int32_t RefreshRate = (std::int32_t)(FrameTimer::FramesPerSecond / (1000 / AURA_REFRESH_INTERVAL)); /** @} */ RgbLights(); ~RgbLights(); /** @brief Returns `true` if the feature is supported */ bool IsSupported() const; /** @brief Updates colors of the lights */ void Update(Color colors[ColorsSize]); /** @brief Resets colors of the light to default settings */ void Clear(); /** @brief Returns static instance of @ref RgbLights */ static RgbLights& Get(); private: RgbLights(const RgbLights&) = delete; RgbLights& operator=(const RgbLights&) = delete; #if defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT) using RzInit = int (*)(); using RzUnInit = int (*)(); using RzCreateKeyboardEffect = int (*)(ChromaSDK::Keyboard::EFFECT_TYPE Effect, void* pParam, void* pEffectId); HMODULE _hLib; RzUnInit _UnInit; RzCreateKeyboardEffect _CreateKeyboardEffect; Color _lastColors[ColorsSize]; #elif defined(DEATH_TARGET_EMSCRIPTEN) std::uint32_t _updateCount; EMSCRIPTEN_WEBSOCKET_T _ws; bool _isConnected; Color _lastColors[ColorsSize]; static EM_BOOL emscriptenOnOpen(std::int32_t eventType, const EmscriptenWebSocketOpenEvent* websocketEvent, void* userData); static EM_BOOL emscriptenOnError(std::int32_t eventType, const EmscriptenWebSocketErrorEvent* websocketEvent, void* userData); static EM_BOOL emscriptenOnClose(std::int32_t eventType, const EmscriptenWebSocketCloseEvent* websocketEvent, void* userData); static EM_BOOL emscriptenOnMessage(std::int32_t eventType, const EmscriptenWebSocketMessageEvent* websocketEvent, void* userData); #endif }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/RumbleDescription.h000066400000000000000000000021151512772601700264340ustar00rootroot00000000000000#pragma once #include using namespace Death::Containers; namespace Jazz2::Input { class RumbleProcessor; /** @brief Describes a gamepad rumble effect */ class RumbleDescription { friend class RumbleProcessor; public: RumbleDescription() {} /** @brief Adds a new frame to the effect timeline */ void AddToTimeline(float endTime, float lowFreq, float highFreq, float leftTrigger = 0.0f, float rightTrigger = 0.0f) { _timeline.emplace_back(endTime, lowFreq, highFreq, leftTrigger, rightTrigger); } private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct TimelineItem { float EndTime; float LowFrequency; float HighFrequency; float LeftTrigger; float RightTrigger; TimelineItem(float endTime, float lowFreq, float highFreq, float leftTrigger, float rightTrigger) : EndTime(endTime), LowFrequency(lowFreq), HighFrequency(highFreq), LeftTrigger(leftTrigger), RightTrigger(rightTrigger) { } }; #endif SmallVector _timeline; }; } deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/RumbleProcessor.cpp000066400000000000000000000067421512772601700264750ustar00rootroot00000000000000#include "RumbleProcessor.h" #include "ControlScheme.h" #include "../PreferencesCache.h" #include "../../nCine/Application.h" #include "../../nCine/Base/FrameTimer.h" #include "../../nCine/Input/IInputManager.h" using namespace nCine; namespace Jazz2::Input { RumbleProcessor::RumbleProcessor() { } RumbleProcessor::~RumbleProcessor() { CancelAllEffects(); } void RumbleProcessor::CancelAllEffects() { if (_activeRumble.empty()) { return; } auto& inputManager = theApplication().GetInputManager(); for (std::int32_t j = 0; j < ControlScheme::MaxConnectedGamepads; j++) { inputManager.joystickRumble(j, 0.0f, 0.0f, 0); inputManager.joystickRumbleTriggers(j, 0.0f, 0.0f, 0); } } void RumbleProcessor::ExecuteEffect(std::int32_t joyId, const std::shared_ptr& desc) { if (PreferencesCache::GamepadRumble == 0) { return; } auto& inputManager = theApplication().GetInputManager(); if (!inputManager.isJoyPresent(joyId)) { return; } for (auto& rumble : _activeRumble) { if (rumble.JoyId == joyId) { // Already playing return; } } _activeRumble.emplace_back(joyId, desc); } void RumbleProcessor::OnEndFrame(float timeMult) { if (PreferencesCache::GamepadRumble == 0) { _activeRumble.clear(); return; } auto& inputManager = theApplication().GetInputManager(); if (_activeRumble.empty()) { return; } for (std::int32_t j = 0; j < ControlScheme::MaxConnectedGamepads; j++) { if (!inputManager.isJoyPresent(j)) { // Allow rumble only for connected gamepads for (std::size_t i = 0; i < _activeRumble.size(); i++) { if (_activeRumble[i].JoyId == j) { // Remove playing effects for disconnected gamepads _activeRumble.eraseUnordered(i); i--; } } continue; } float gains[4] = {}; float timeLeft = 0.0f; for (std::size_t i = 0; i < _activeRumble.size(); i++) { auto& rumble = _activeRumble[i]; if (rumble.JoyId != j) { continue; } std::size_t timelineIndex = 0; while (timelineIndex < rumble.Desc->_timeline.size() && rumble.Desc->_timeline[timelineIndex].EndTime <= rumble.Progress) { timelineIndex++; } if (timelineIndex >= rumble.Desc->_timeline.size()) { _activeRumble.eraseUnordered(i); i--; continue; } auto& t = rumble.Desc->_timeline[timelineIndex]; gains[0] = std::max(gains[0], t.LowFrequency); gains[1] = std::max(gains[1], t.HighFrequency); gains[2] = std::max(gains[2], t.LeftTrigger); gains[3] = std::max(gains[3], t.RightTrigger); auto& last = rumble.Desc->_timeline[rumble.Desc->_timeline.size() - 1]; float tl = (last.EndTime - rumble.Progress); timeLeft = std::max(timeLeft, tl); rumble.Progress += timeMult; } if (timeLeft > 0.0f) { std::uint32_t timeLeftMs = (std::uint32_t)(timeLeft * 1000 * FrameTimer::SecondsPerFrame); if (timeLeftMs > 10) { // Reduce gain if weak is selected if (PreferencesCache::GamepadRumble == 1) { for (auto& gain : gains) { gain *= 0.666f; } } if (gains[0] > 0.0f || gains[1] > 0.0f) { inputManager.joystickRumble(j, gains[0], gains[1], timeLeftMs); } if (gains[2] > 0.0f || gains[3] > 0.0f) { inputManager.joystickRumbleTriggers(j, gains[2], gains[3], timeLeftMs); } } } } } RumbleProcessor::ActiveRumble::ActiveRumble(std::int32_t joyId, std::shared_ptr desc) : Desc(std::move(desc)), JoyId(joyId), Progress(0.0f) { } } deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Input/RumbleProcessor.h000066400000000000000000000015401512772601700261310ustar00rootroot00000000000000#pragma once #include "RumbleDescription.h" namespace Jazz2::Input { /** @brief Gamepad rumble processor */ class RumbleProcessor { public: RumbleProcessor(); ~RumbleProcessor(); /** @brief Cancels all active effects */ void CancelAllEffects(); /** @brief Executes an effect on a given gamepad */ void ExecuteEffect(std::int32_t joyId, const std::shared_ptr& desc); /** @brief Called at the end of each frame */ void OnEndFrame(float timeMult); private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct ActiveRumble { std::shared_ptr Desc; std::int32_t JoyId; float Progress; ActiveRumble(std::int32_t joyId, std::shared_ptr desc); }; #endif SmallVector _activeRumble; }; } deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LevelDescriptor.h000066400000000000000000000022641512772601700250160ustar00rootroot00000000000000#pragma once #include "../Main.h" #include "WeatherType.h" #include "../nCine/Primitives/Vector4.h" #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2 { namespace Events { class EventMap; } namespace Tiles { class TileMap; } /** @brief Contains all components to fully initialize a level instance */ struct LevelDescriptor { /** @brief Full path */ String FullPath; /** @brief Display name */ String DisplayName; /** @brief Next level name */ String NextLevel; /** @brief Secret level name */ String SecretLevel; /** @brief Bonus level name */ String BonusLevel; // TODO: Unused /** @brief Tile map */ std::unique_ptr TileMap; /** @brief Event map */ std::unique_ptr EventMap; /** @brief Music file path */ String MusicPath; /** @brief Ambient color */ Vector4f AmbientColor; /** @brief Weather type */ WeatherType Weather; /** @brief Weather intensity */ std::uint8_t WeatherIntensity; /** @brief Water level */ std::uint16_t WaterLevel; /** @brief Level texts */ SmallVector LevelTexts; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LevelFlags.h000066400000000000000000000014621512772601700237330ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Level flags, supports a bitwise combination of its member values */ enum class LevelFlags : std::uint16_t { None = 0, /**< None */ HasPit = 0x01, /**< Level has a pit */ HasPitInstantDeath = 0x02, /**< Level has a pit with instant death */ UseLevelPalette = 0x04, /**< Level has custom palette */ IsHidden = 0x08, /**< Level is hidden */ IsMultiplayerLevel = 0x10, /**< Level is for multiplayer */ HasLaps = 0x20, /**< Level has laps */ HasCaptureTheFlag = 0x40, /**< Level has capture the flag */ HasVerticalSplitscreen = 0x80, /**< Vertical splitscreen should be used */ HasMultiplayerSpawnPoints = 0x100 /**< Level has multiplayer spawn points */ }; DEATH_ENUM_FLAGS(LevelFlags); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LevelHandler.cpp000066400000000000000000002243571512772601700246210ustar00rootroot00000000000000#include "LevelHandler.h" #include "ContentResolver.h" #include "PreferencesCache.h" #include "Rendering/PlayerViewport.h" #include "UI/DiscordRpcClient.h" #include "UI/HUD.h" #include "UI/InGameConsole.h" #include "UI/Menu/InGameMenu.h" #include "../Main.h" #if defined(WITH_ANGELSCRIPT) # include "Scripting/LevelScriptLoader.h" #endif #include "../nCine/I18n.h" #include "../nCine/MainApplication.h" #include "../nCine/ServiceLocator.h" #include "../nCine/tracy.h" #include "../nCine/Base/Random.h" #include "../nCine/Graphics/Camera.h" #include "../nCine/Graphics/Texture.h" #include "../nCine/Graphics/Viewport.h" #include "../nCine/Input/JoyMapping.h" #include "Actors/Player.h" #include "Actors/SolidObjectBase.h" #include "Actors/Enemies/Bosses/BossBase.h" #include "Actors/Environment/IceBlock.h" #include #include #include #include using namespace nCine; using namespace Jazz2::Tiles; namespace Jazz2 { namespace Resources { static constexpr AnimState Snow = (AnimState)0; static constexpr AnimState Rain = (AnimState)1; } using namespace Jazz2::Resources; #if defined(WITH_AUDIO) class AudioBufferPlayerForSplitscreen : public AudioBufferPlayer { DEATH_RUNTIME_OBJECT(AudioBufferPlayer); public: explicit AudioBufferPlayerForSplitscreen(AudioBuffer* audioBuffer, ArrayView> viewports); Vector3f getAdjustedPosition(IAudioDevice& device, const Vector3f& pos, bool isSourceRelative, bool isAs2D) override; void updatePosition(); void updateViewports(ArrayView> viewports); private: ArrayView> _viewports; }; AudioBufferPlayerForSplitscreen::AudioBufferPlayerForSplitscreen(AudioBuffer* audioBuffer, ArrayView> viewports) : AudioBufferPlayer(audioBuffer), _viewports(viewports) { } Vector3f AudioBufferPlayerForSplitscreen::getAdjustedPosition(IAudioDevice& device, const Vector3f& pos, bool isSourceRelative, bool isAs2D) { if (isSourceRelative || isAs2D) { return AudioBufferPlayer::getAdjustedPosition(device, pos, isSourceRelative, isAs2D); } std::size_t minIndex = 0; float minDistance = FLT_MAX; for (std::size_t i = 0; i < _viewports.size(); i++) { float distance = (pos.ToVector2() - _viewports[i]->_cameraPos).SqrLength(); if (minDistance > distance) { minDistance = distance; minIndex = i; } } Vector3f relativePos = (pos - Vector3f(_viewports[minIndex]->_cameraPos, 0.0f)); return AudioBufferPlayer::getAdjustedPosition(device, relativePos, false, false); } void AudioBufferPlayerForSplitscreen::updatePosition() { if (state_ != PlayerState::Playing || GetFlags(PlayerFlags::SourceRelative) || GetFlags(PlayerFlags::As2D)) { return; } IAudioDevice& device = theServiceLocator().GetAudioDevice(); setPositionInternal(getAdjustedPosition(device, position_, false, false)); } void AudioBufferPlayerForSplitscreen::updateViewports(ArrayView> viewports) { _viewports = viewports; } #endif LevelHandler::LevelHandler(IRootController* root) : _root(root), _lightingShader(nullptr), _blurShader(nullptr), _downsampleShader(nullptr), _combineShader(nullptr), _combineWithWaterShader(nullptr), _eventSpawner(this), _difficulty(GameDifficulty::Default), _isReforged(false), _cheatsUsed(false), _checkpointCreated(false), _nextLevelType(ExitType::None), _nextLevelTime(0.0f), _elapsedMillisecondsBegin(0), _elapsedFrames(0.0f), _checkpointFrames(0.0f), _waterLevel(FLT_MAX), _weatherType(WeatherType::None), _pressedKeys(ValueInit, (std::size_t)Keys::Count), _overrideActions(0) { } LevelHandler::~LevelHandler() { _players.clear(); // Remove nodes from UpscaleRenderPass for (auto& viewport : _assignedViewports) { viewport->_combineRenderer->setParent(nullptr); } _hud->setParent(nullptr); _console->setParent(nullptr); TracyPlot("Actors", 0LL); } bool LevelHandler::Initialize(const LevelInitialization& levelInit) { ZoneScopedC(0x4876AF); _levelName = levelInit.LevelName; _difficulty = levelInit.Difficulty; _isReforged = levelInit.IsReforged; _cheatsUsed = levelInit.CheatsUsed; _elapsedMillisecondsBegin = levelInit.ElapsedMilliseconds; auto& resolver = ContentResolver::Get(); resolver.BeginLoading(); _noiseTexture = resolver.GetNoiseTexture(); _rootNode = std::make_unique(); _rootNode->setVisitOrderState(SceneNode::VisitOrderState::Disabled); _console = std::make_unique(this); auto p = _levelName.partition('/'); // Try to search also "unknown" directory LevelDescriptor descriptor; if (!resolver.TryLoadLevel(_levelName, _difficulty, descriptor) && (p[0] == "unknown"_s || !resolver.TryLoadLevel(String("unknown/"_s + p[2]), _difficulty, descriptor))) { LOGE("Cannot load level \"{}\"", _levelName); return false; } _console->WriteLine(UI::MessageLevel::Debug, _f("Level \"{}\" initialized", descriptor.DisplayName)); AttachComponents(std::move(descriptor)); SpawnPlayers(levelInit); OnInitialized(); resolver.EndLoading(); return true; } bool LevelHandler::Initialize(Stream& src, std::uint16_t version) { ZoneScopedC(0x4876AF); std::uint8_t flags = src.ReadValue(); std::uint8_t stringSize = src.ReadValue(); String episodeName(NoInit, stringSize); src.Read(episodeName.data(), stringSize); stringSize = src.ReadValue(); String levelFileName(NoInit, stringSize); src.Read(levelFileName.data(), stringSize); _levelName = episodeName + '/' + levelFileName; _difficulty = (GameDifficulty)src.ReadValue(); _isReforged = (flags & 0x01) != 0; _cheatsUsed = (flags & 0x02) != 0; if (version >= 3) { _elapsedMillisecondsBegin = src.ReadVariableUint64(); } _checkpointFrames = src.ReadValue(); auto& resolver = ContentResolver::Get(); resolver.BeginLoading(); _noiseTexture = resolver.GetNoiseTexture(); _rootNode = std::make_unique(); _rootNode->setVisitOrderState(SceneNode::VisitOrderState::Disabled); _console = std::make_unique(this); LevelDescriptor descriptor; if (!resolver.TryLoadLevel(_levelName, _difficulty, descriptor)) { LOGE("Cannot load level \"{}\"", _levelName); return false; } _console->WriteLine(UI::MessageLevel::Debug, _f("Level \"{}\" initialized", descriptor.DisplayName)); AttachComponents(std::move(descriptor)); // All components are ready, deserialize the rest of state _waterLevel = src.ReadValueAsLE(); _weatherType = (WeatherType)src.ReadValue(); _weatherIntensity = src.ReadValue(); _tileMap->InitializeFromStream(src); _eventMap->InitializeFromStream(src); std::uint32_t playerCount = src.ReadValue(); _players.reserve(playerCount); for (std::uint32_t i = 0; i < playerCount; i++) { std::shared_ptr player = std::make_shared(); player->InitializeFromStream(this, src, version); Actors::Player* ptr = player.get(); _players.push_back(ptr); AddActor(player); AssignViewport(ptr); } _hud = CreateHUD(); _hud->BeginFadeIn(false); OnInitialized(); resolver.EndLoading(); // Set it at the end, so ambient light transition is skipped _elapsedFrames = _checkpointFrames; return true; } void LevelHandler::OnInitialized() { auto& resolver = ContentResolver::Get(); _commonResources = resolver.RequestMetadata("Common/Scenery"_s); resolver.PreloadMetadataAsync("Common/Explosions"_s); _eventMap->PreloadEventsAsync(); InitializeRumbleEffects(); UpdateRichPresence(); _console->OnInitialized(); #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { _scripts->OnLevelLoad(); } #endif } Events::EventSpawner* LevelHandler::EventSpawner() { return &_eventSpawner; } Events::EventMap* LevelHandler::EventMap() { return _eventMap.get(); } Tiles::TileMap* LevelHandler::TileMap() { return _tileMap.get(); } GameDifficulty LevelHandler::GetDifficulty() const { return _difficulty; } bool LevelHandler::IsLocalSession() const { return true; } bool LevelHandler::IsServer() const { return true; } bool LevelHandler::IsPausable() const { return true; } bool LevelHandler::IsReforged() const { return _isReforged; } bool LevelHandler::CanActivateSugarRush() const { return true; } bool LevelHandler::CanEventDisappear(EventType eventType) const { return true; } bool LevelHandler::CanPlayersCollide() const { // TODO return false; } Recti LevelHandler::GetLevelBounds() const { return _levelBounds; } float LevelHandler::GetElapsedFrames() const { return _elapsedFrames; } float LevelHandler::GetGravity() const { constexpr float DefaultGravity = 0.3f; // Higher gravity in Reforged mode return (_isReforged ? DefaultGravity : DefaultGravity * 0.8f); } float LevelHandler::GetWaterLevel() const { return _waterLevel; } float LevelHandler::GetHurtInvulnerableTime() const { return 180.0f; } ArrayView> LevelHandler::GetActors() const { return _actors; } ArrayView LevelHandler::GetPlayers() const { return _players; } float LevelHandler::GetDefaultAmbientLight() const { return _defaultAmbientLight.W; } float LevelHandler::GetAmbientLight(Actors::Player* player) const { for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { return viewport->_ambientLightTarget; } } return 0.0f; } void LevelHandler::SetAmbientLight(Actors::Player* player, float value) { for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { viewport->_ambientLightTarget = value; // Skip transition if it was changed at the beginning of level if (_elapsedFrames < FrameTimer::FramesPerSecond * 0.25f) { viewport->_ambientLight.W = value; } } } } void LevelHandler::InvokeAsync(Function&& callback) { _root->InvokeAsync(weak_from_this(), std::move(callback)); } void LevelHandler::AttachComponents(LevelDescriptor&& descriptor) { ZoneScopedC(0x4876AF); _levelDisplayName = std::move(descriptor.DisplayName); LOGI("Level \"{}\" (from \"{}.j2l\") loaded", _levelDisplayName, _levelName); if (!_levelDisplayName.empty()) { theApplication().GetGfxDevice().setWindowTitle(String(NCINE_APP_NAME " - " + _levelDisplayName)); } else { theApplication().GetGfxDevice().setWindowTitle(NCINE_APP_NAME); } _defaultNextLevel = std::move(descriptor.NextLevel); _defaultSecretLevel = std::move(descriptor.SecretLevel); _tileMap = std::move(descriptor.TileMap); _tileMap->SetOwner(this); _tileMap->setParent(_rootNode.get()); _eventMap = std::move(descriptor.EventMap); _eventMap->SetLevelHandler(this); Vector2i levelBounds = _tileMap->GetLevelBounds(); _levelBounds = Recti(0, 0, levelBounds.X, levelBounds.Y); _viewBoundsTarget = _levelBounds.As(); _defaultAmbientLight = descriptor.AmbientColor; _weatherType = descriptor.Weather; _weatherIntensity = descriptor.WeatherIntensity; _waterLevel = descriptor.WaterLevel; _musicCurrentPath = std::move(descriptor.MusicPath); _musicDefaultPath = _musicCurrentPath; #if defined(WITH_AUDIO) if (!_musicCurrentPath.empty()) { _music = ContentResolver::Get().GetMusic(_musicCurrentPath); if (_music != nullptr) { _music->setLooping(true); _music->setGain(PreferencesCache::MasterVolume * PreferencesCache::MusicVolume); _music->setSourceRelative(true); _music->play(); } } #endif _levelTexts = std::move(descriptor.LevelTexts); #if defined(WITH_ANGELSCRIPT) || defined(DEATH_TRACE) // TODO: Allow script signing if (PreferencesCache::AllowUnsignedScripts) { const StringView foundDot = descriptor.FullPath.findLastOr('.', descriptor.FullPath.end()); String scriptPath = (foundDot == descriptor.FullPath.end() ? StringView(descriptor.FullPath) : descriptor.FullPath.prefix(foundDot.begin())) + ".j2as"_s; if (auto scriptPathCaseInsensitive = fs::FindPathCaseInsensitive(scriptPath)) { if (fs::IsReadableFile(scriptPathCaseInsensitive)) { # if defined(WITH_ANGELSCRIPT) _scripts = std::make_unique(this, scriptPathCaseInsensitive); # else LOGW("Level requires scripting, but scripting support is disabled in this build"); # endif } } } #endif } std::unique_ptr LevelHandler::CreateHUD() { return std::make_unique(this); } void LevelHandler::SpawnPlayers(const LevelInitialization& levelInit) { std::int32_t playerCount = levelInit.GetPlayerCount(); for (std::int32_t i = 0; i < std::int32_t(arraySize(levelInit.PlayerCarryOvers)); i++) { if (levelInit.PlayerCarryOvers[i].Type == PlayerType::None) { continue; } Vector2 spawnPosition = _eventMap->GetSpawnPosition(levelInit.PlayerCarryOvers[i].Type); if (spawnPosition.X < 0.0f && spawnPosition.Y < 0.0f) { spawnPosition = _eventMap->GetSpawnPosition(PlayerType::Jazz); if (spawnPosition.X < 0.0f && spawnPosition.Y < 0.0f) { continue; } } std::shared_ptr player = std::make_shared(); std::uint8_t playerParams[2] = { (std::uint8_t)levelInit.PlayerCarryOvers[i].Type, (std::uint8_t)i }; player->OnActivated(Actors::ActorActivationDetails( this, Vector3i((std::int32_t)spawnPosition.X + (i * 10) - ((playerCount - 1) * 5), (std::int32_t)spawnPosition.Y - (i * 20) + ((playerCount - 1) * 5), PlayerZ - i), playerParams )); Actors::Player* ptr = player.get(); _players.push_back(ptr); AddActor(player); AssignViewport(ptr); ptr->ReceiveLevelCarryOver(levelInit.LastExitType, levelInit.PlayerCarryOvers[i]); } _hud = CreateHUD(); _hud->BeginFadeIn((levelInit.LastExitType & ExitType::FastTransition) == ExitType::FastTransition); } bool LevelHandler::IsCheatingAllowed() { return PreferencesCache::AllowCheats; } Vector2i LevelHandler::GetViewSize() const { return _viewSize; } void LevelHandler::OnBeginFrame() { ZoneScopedC(0x4876AF); float timeMult = theApplication().GetTimeMult(); if (_pauseMenu == nullptr) { UpdatePressedActions(); bool isGamepad; if (PlayerActionHit(nullptr, PlayerAction::Menu)) { if (_console->IsVisible()) { _console->Hide(); } else if (_nextLevelType == ExitType::None) { PauseGame(); } } else if (PlayerActionHit(nullptr, PlayerAction::Console, true, isGamepad)) { if (_console->IsVisible()) { if (isGamepad) { _console->Hide(); } } else { _console->Show(); } } #if defined(DEATH_DEBUG) if (IsCheatingAllowed() && PlayerActionPressed(nullptr, PlayerAction::ChangeWeapon) && PlayerActionHit(0, PlayerAction::Jump)) { _cheatsUsed = true; BeginLevelChange(nullptr, ExitType::Warp | ExitType::FastTransition); } #endif } #if defined(WITH_AUDIO) // Destroy stopped players and resume music after Sugar Rush if (_sugarRushMusic != nullptr && _sugarRushMusic->isStopped()) { _sugarRushMusic = nullptr; if (_music != nullptr) { _music->play(); } } auto it = _playingSounds.begin(); while (it != _playingSounds.end()) { if ((*it)->isStopped()) { it = _playingSounds.eraseUnordered(it); continue; } ++it; } #endif if (!IsPausable() || _pauseMenu == nullptr) { if (_nextLevelType != ExitType::None) { _nextLevelTime -= timeMult; ProcessQueuedNextLevel(); } ProcessEvents(timeMult); ProcessWeather(timeMult); // Active Boss if (_activeBoss != nullptr && _activeBoss->GetHealth() <= 0) { _activeBoss = nullptr; BeginLevelChange(nullptr, ExitType::Boss); } #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { _scripts->OnLevelUpdate(timeMult); } #endif } } void LevelHandler::OnEndFrame() { ZoneScopedC(0x4876AF); float timeMult = theApplication().GetTimeMult(); auto& resolver = ContentResolver::Get(); _tileMap->OnEndFrame(); if (!IsPausable() || _pauseMenu == nullptr) { ResolveCollisions(timeMult); if (!resolver.IsHeadless()) { #if defined(NCINE_HAS_GAMEPAD_RUMBLE) _rumble.OnEndFrame(timeMult); #endif for (auto& viewport : _assignedViewports) { viewport->UpdateCamera(timeMult); } #if defined(WITH_AUDIO) if (!_assignedViewports.empty()) { // Update audio listener position IAudioDevice& audioDevice = theServiceLocator().GetAudioDevice(); if (_assignedViewports.size() == 1) { audioDevice.updateListener(Vector3f(_assignedViewports[0]->_cameraPos, 0.0f), Vector3f(_assignedViewports[0]->_targetActor->GetSpeed(), 0.0f)); } else { audioDevice.updateListener(Vector3f::Zero, Vector3f::Zero); // All audio players must be updated to the nearest listener for (auto& current : _playingSounds) { if (auto* currentForSplitscreen = runtime_cast(current.get())) { currentForSplitscreen->updatePosition(); } } } } #endif } _elapsedFrames += timeMult; } if (!resolver.IsHeadless()) { for (auto& viewport : _assignedViewports) { viewport->OnEndFrame(); } #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) if (PreferencesCache::ShowPerformanceMetrics) { ImDrawList* drawList = ImGui::GetBackgroundDrawList(); std::size_t actorsCount = _actors.size(); for (std::size_t i = 0; i < actorsCount; i++) { auto* actor = _actors[i].get(); auto pos = WorldPosToScreenSpace(actor->_pos); auto aabbMin = WorldPosToScreenSpace({ actor->AABB.L, actor->AABB.T }); auto aabbMax = WorldPosToScreenSpace({ actor->AABB.R, actor->AABB.B }); auto aabbInnerMin = WorldPosToScreenSpace({ actor->AABBInner.L, actor->AABBInner.T }); auto aabbInnerMax = WorldPosToScreenSpace({ actor->AABBInner.R, actor->AABBInner.B }); drawList->AddRect(ImVec2(pos.x - 2.4f, pos.y - 2.4f), ImVec2(pos.x + 2.4f, pos.y + 2.4f), ImColor(0, 0, 0, 220)); drawList->AddRect(ImVec2(pos.x - 1.0f, pos.y - 1.0f), ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImColor(120, 255, 200, 220)); drawList->AddRect(aabbMin, aabbMax, ImColor(120, 200, 255, 180)); drawList->AddRect(aabbInnerMin, aabbInnerMax, ImColor(255, 255, 255)); } } #endif } TracyPlot("Actors", static_cast(_actors.size())); } void LevelHandler::OnInitializeViewport(std::int32_t width, std::int32_t height) { ZoneScopedC(0x4876AF); auto& resolver = ContentResolver::Get(); if (resolver.IsHeadless()) { // Use only the main viewport in headless mode _rootNode->setParent(&theApplication().GetRootNode()); return; } constexpr float defaultRatio = (float)DefaultWidth / DefaultHeight; float currentRatio = (float)width / height; std::int32_t w, h; if (currentRatio > defaultRatio) { w = std::min(DefaultWidth, width); h = (std::int32_t)roundf(w / currentRatio); } else if (currentRatio < defaultRatio) { h = std::min(DefaultHeight, height); w = (std::int32_t)roundf(h * currentRatio); } else { w = std::min(DefaultWidth, width); h = std::min(DefaultHeight, height); } _viewSize = Vector2i(w, h); _upscalePass.Initialize(w, h, width, height); bool notInitialized = (_combineShader == nullptr); if (notInitialized) { LOGI("Acquiring required shaders"); _lightingShader = resolver.GetShader(PrecompiledShader::Lighting); if (_lightingShader == nullptr) { LOGW("PrecompiledShader::Lighting failed"); } _blurShader = resolver.GetShader(PrecompiledShader::Blur); if (_blurShader == nullptr) { LOGW("PrecompiledShader::Blur failed"); } _downsampleShader = resolver.GetShader(PrecompiledShader::Downsample); if (_downsampleShader == nullptr) { LOGW("PrecompiledShader::Downsample failed"); } _combineShader = resolver.GetShader(PrecompiledShader::Combine); if (_combineShader == nullptr) { LOGW("PrecompiledShader::Combine failed"); } if (_hud != nullptr) { _hud->setParent(_upscalePass.GetNode()); } if (_console != nullptr) { _console->setParent(_upscalePass.GetNode()); } } _combineWithWaterShader = resolver.GetShader(PreferencesCache::LowWaterQuality ? PrecompiledShader::CombineWithWaterLow : PrecompiledShader::CombineWithWater); if (_combineWithWaterShader == nullptr) { if (PreferencesCache::LowWaterQuality) { LOGW("PrecompiledShader::CombineWithWaterLow failed"); } else { LOGW("PrecompiledShader::CombineWithWater failed"); } } bool useHalfRes = (PreferencesCache::PreferZoomOut && _assignedViewports.size() >= 3); for (std::size_t i = 0; i < _assignedViewports.size(); i++) { Rendering::PlayerViewport& viewport = *_assignedViewports[i]; Recti bounds = GetPlayerViewportBounds(w, h, (std::int32_t)i); if (viewport.Initialize(_rootNode.get(), _upscalePass.GetNode(), bounds, useHalfRes)) { InitializeCamera(viewport); } } // Viewports must be registered in reverse order _upscalePass.Register(); for (std::size_t i = 0; i < _assignedViewports.size(); i++) { Rendering::PlayerViewport& viewport = *_assignedViewports[i]; viewport.Register(); if (_pauseMenu != nullptr) { viewport.UpdateCamera(0.0f); // Force update camera if game is paused } } if (_tileMap != nullptr) { _tileMap->OnInitializeViewport(); } if (_pauseMenu != nullptr) { _pauseMenu->OnInitializeViewport(_viewSize.X, _viewSize.Y); } } bool LevelHandler::OnConsoleCommand(StringView line) { if (line == "/help"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); _console->WriteLine(UI::MessageLevel::Confirm, _("For more information, visit the official website:") + " \f[w:80]\f[c:#707070]https://deat.tk/jazz2/help\f[/c]\f[/w]"_s); return true; } else if (line == "jjk"_s || line == "jjkill"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatKill(); } else if (line == "jjgod"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatGod(); } else if (line == "jjnext"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatNext(); } else if (line == "jjguns"_s || line == "jjammo"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatGuns(); } else if (line == "jjrush"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatRush(); } else if (line == "jjgems"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatGems(); } else if (line == "jjbird"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatBird(); } else if (line == "jjlife"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatLife(); } else if (line == "jjpower"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatPower(); } else if (line == "jjcoins"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatCoins(); } else if (line == "jjmorph"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatMorph(); } else if (line == "jjshield"_s) { _console->WriteLine(UI::MessageLevel::Echo, line); return CheatShield(); } else { return false; } } void LevelHandler::OnKeyPressed(const KeyboardEvent& event) { _pressedKeys.set((std::size_t)event.sym); if (_pauseMenu != nullptr) { _pauseMenu->OnKeyPressed(event); } else if (_console->IsVisible()) { _console->OnKeyPressed(event); } } void LevelHandler::OnKeyReleased(const KeyboardEvent& event) { _pressedKeys.reset((std::size_t)event.sym); if (_pauseMenu != nullptr) { _pauseMenu->OnKeyReleased(event); } } void LevelHandler::OnTextInput(const TextInputEvent& event) { if (_console->IsVisible()) { _console->OnTextInput(event); } } void LevelHandler::OnTouchEvent(const TouchEvent& event) { if (_pauseMenu != nullptr) { _pauseMenu->OnTouchEvent(event); } else { _hud->OnTouchEvent(event, _overrideActions); } } void LevelHandler::AddActor(std::shared_ptr actor) { actor->SetParent(_rootNode.get()); if (!actor->GetState(Actors::ActorState::ForceDisableCollisions)) { actor->UpdateAABB(); actor->_collisionProxyID = _collisions.CreateProxy(actor->AABB, actor.get()); } _actors.push_back(std::move(actor)); } std::shared_ptr LevelHandler::PlaySfx(Actors::ActorBase* self, StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) { #if defined(WITH_AUDIO) if (buffer != nullptr) { auto& player = _playingSounds.emplace_back(_assignedViewports.size() > 1 ? std::make_shared(buffer, _assignedViewports) : std::make_shared(buffer)); player->setPosition(Vector3f(pos.X, pos.Y, 100.0f)); player->setGain(gain * PreferencesCache::MasterVolume * PreferencesCache::SfxVolume); player->setSourceRelative(sourceRelative); if (pos.Y >= _waterLevel) { player->setLowPass(0.05f); player->setPitch(pitch * 0.7f); } else { player->setPitch(pitch); } player->play(); return player; } #endif return nullptr; } std::shared_ptr LevelHandler::PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain, float pitch) { #if defined(WITH_AUDIO) auto it = _commonResources->Sounds.find(String::nullTerminatedView(identifier)); if (it != _commonResources->Sounds.end() && !it->second.Buffers.empty()) { std::int32_t idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (std::int32_t)it->second.Buffers.size()) : 0); auto* buffer = &it->second.Buffers[idx]->Buffer; auto& player = _playingSounds.emplace_back(_assignedViewports.size() > 1 ? std::make_shared(buffer, _assignedViewports) : std::make_shared(buffer)); player->setPosition(Vector3f(pos.X, pos.Y, 100.0f)); player->setGain(gain * PreferencesCache::MasterVolume * PreferencesCache::SfxVolume); if (pos.Y >= _waterLevel) { player->setLowPass(0.05f); player->setPitch(pitch * 0.7f); } else { player->setPitch(pitch); } player->play(); return player; } #endif return nullptr; } void LevelHandler::WarpCameraToTarget(Actors::ActorBase* actor, bool fast) { for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == actor) { viewport->WarpCameraToTarget(fast); } } } bool LevelHandler::IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, TileCollisionParams& params, Actors::ActorBase** collider) { *collider = nullptr; if (self->GetState(Actors::ActorState::CollideWithTileset)) { if (_tileMap != nullptr) { if (self->GetState(Actors::ActorState::CollideWithTilesetReduced) && aabb.B - aabb.T >= 20.0f) { // If hitbox height is larger than 20px, check bottom and top separately (and top only if going upwards) AABBf aabbTop = aabb; aabbTop.B = aabbTop.T + 6.0f; AABBf aabbBottom = aabb; aabbBottom.T = aabbBottom.B - std::max(14.0f, (aabb.B - aabb.T) - 10.0f); if (!_tileMap->IsTileEmpty(aabbBottom, params)) { return false; } if (!params.Downwards) { params.Downwards = false; if (!_tileMap->IsTileEmpty(aabbTop, params)) { return false; } } } else { if (!_tileMap->IsTileEmpty(aabb, params)) { return false; } } } } // Check for solid objects if (self->GetState(Actors::ActorState::CollideWithSolidObjects)) { Actors::ActorBase* colliderActor = nullptr; FindCollisionActorsByAABB(self, aabb, [self, &colliderActor, ¶ms](Actors::ActorBase* actor) -> bool { if ((actor->GetState() & (Actors::ActorState::IsSolidObject | Actors::ActorState::IsDestroyed)) != Actors::ActorState::IsSolidObject) { return true; } if (self->GetState(Actors::ActorState::ExcludeSimilar) && actor->GetState(Actors::ActorState::ExcludeSimilar)) { // If both objects have ExcludeSimilar, ignore it return true; } if (self->GetState(Actors::ActorState::CollideWithSolidObjectsBelow) && self->AABBInner.B > (actor->AABBInner.T + actor->AABBInner.B) * 0.5f) { return true; } auto* solidObject = runtime_cast(actor); if (solidObject == nullptr || !solidObject->IsOneWay || params.Downwards) { std::shared_ptr selfShared = self->shared_from_this(); std::shared_ptr actorShared = actor->shared_from_this(); if (!selfShared->OnHandleCollision(actorShared) && !actorShared->OnHandleCollision(selfShared)) { colliderActor = actor; return false; } } return true; }); *collider = colliderActor; } return (*collider == nullptr); } void LevelHandler::FindCollisionActorsByAABB(const Actors::ActorBase* self, const AABBf& aabb, Function&& callback) { struct QueryHelper { const LevelHandler* Handler; const Actors::ActorBase* Self; const AABBf& AABB; Function& Callback; bool OnCollisionQuery(std::int32_t nodeId) { Actors::ActorBase* actor = (Actors::ActorBase*)Handler->_collisions.GetUserData(nodeId); if (Self == actor || (actor->GetState() & (Actors::ActorState::CollideWithOtherActors | Actors::ActorState::IsDestroyed)) != Actors::ActorState::CollideWithOtherActors) { return true; } if (actor->IsCollidingWith(AABB)) { return Callback(actor); } return true; } }; QueryHelper helper = { this, self, aabb, callback }; _collisions.Query(&helper, aabb); } void LevelHandler::FindCollisionActorsByRadius(float x, float y, float radius, Function&& callback) { AABBf aabb = AABBf(x - radius, y - radius, x + radius, y + radius); float radiusSquared = (radius * radius); struct QueryHelper { const LevelHandler* Handler; const float x, y; const float RadiusSquared; Function& Callback; bool OnCollisionQuery(std::int32_t nodeId) { Actors::ActorBase* actor = (Actors::ActorBase*)Handler->_collisions.GetUserData(nodeId); if ((actor->GetState() & (Actors::ActorState::CollideWithOtherActors | Actors::ActorState::IsDestroyed)) != Actors::ActorState::CollideWithOtherActors) { return true; } // Find the closest point to the circle within the rectangle float closestX = std::clamp(x, actor->AABB.L, actor->AABB.R); float closestY = std::clamp(y, actor->AABB.T, actor->AABB.B); // Calculate the distance between the circle's center and this closest point float distanceX = (x - closestX); float distanceY = (y - closestY); // If the distance is less than the circle's radius, an intersection occurs float distanceSquared = (distanceX * distanceX) + (distanceY * distanceY); if (distanceSquared < RadiusSquared) { return Callback(actor); } return true; } }; QueryHelper helper = { this, x, y, radiusSquared, callback }; _collisions.Query(&helper, aabb); } void LevelHandler::GetCollidingPlayers(const AABBf& aabb, Function&& callback) { for (auto& player : _players) { if (aabb.Overlaps(player->AABB)) { if (!callback(player)) { break; } } } } void LevelHandler::BroadcastTriggeredEvent(Actors::ActorBase* initiator, EventType eventType, std::uint8_t* eventParams) { switch (eventType) { case EventType::AreaActivateBoss: { if (_activeBoss == nullptr && _nextLevelType == ExitType::None) { for (auto& actor : _actors) { _activeBoss = runtime_cast(actor); if (_activeBoss != nullptr) { break; } } if (_activeBoss == nullptr) { // No boss was found, it's probably a bug in the level, so go to the next level LOGW("No boss was found, skipping to the next level"); BeginLevelChange(nullptr, ExitType::Boss); return; } if (_activeBoss->OnActivatedBoss()) { HandleBossActivated(_activeBoss.get(), initiator); if (eventParams != nullptr) { size_t musicPathLength = strnlen((const char*)eventParams, 16); StringView musicPath((const char*)eventParams, musicPathLength); BeginPlayMusic(musicPath); } } } break; } case EventType::AreaCallback: { #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { _scripts->OnLevelCallback(initiator, eventParams); } #endif break; } case EventType::ModifierSetWater: { // TODO: Implement Instant (non-instant transition), Lighting _waterLevel = *(std::uint16_t*)&eventParams[0]; break; } } for (auto& actor : _actors) { actor->OnTriggeredEvent(eventType, eventParams); } } void LevelHandler::BeginLevelChange(Actors::ActorBase* initiator, ExitType exitType, StringView nextLevel) { if (_nextLevelType != ExitType::None) { return; } _nextLevelName = nextLevel; _nextLevelType = exitType; if ((exitType & ExitType::FastTransition) == ExitType::FastTransition) { ExitType exitTypeMasked = (exitType & ExitType::TypeMask); if (exitTypeMasked == ExitType::Warp || exitTypeMasked == ExitType::Bonus || exitTypeMasked == ExitType::Boss) { _nextLevelTime = 70.0f; } else { _nextLevelTime = 0.0f; } } else { _nextLevelTime = 360.0f; if (_hud != nullptr) { _hud->BeginFadeOut(_nextLevelTime - 40.0f); } #if defined(WITH_AUDIO) if (_sugarRushMusic != nullptr) { _sugarRushMusic->stop(); _sugarRushMusic = nullptr; } if (_music != nullptr) { _music->stop(); _music = nullptr; } #endif } for (auto player : _players) { player->OnLevelChanging(initiator, exitType); } } void LevelHandler::SendPacket(const Actors::ActorBase* self, ArrayView data) { // Packet cannot be sent anywhere in local sessions } void LevelHandler::HandleBossActivated(Actors::Bosses::BossBase* boss, Actors::ActorBase* initiator) { // Used only in derived classes } void LevelHandler::HandleLevelChange(LevelInitialization&& levelInit) { _root->ChangeLevel(std::move(levelInit)); } void LevelHandler::HandleGameOver(Actors::Player* player) { LevelInitialization levelInit; PrepareNextLevelInitialization(levelInit); levelInit.LevelName = ":gameover"_s; HandleLevelChange(std::move(levelInit)); } bool LevelHandler::HandlePlayerDied(Actors::Player* player) { #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { // TODO: killer _scripts->OnPlayerDied(player, nullptr); } #endif if (_activeBoss != nullptr) { if (_activeBoss->OnPlayerDied()) { _activeBoss = nullptr; } // Warp all other players to checkpoint without transition to avoid issues for (auto* otherPlayer : _players) { if (otherPlayer != player) { otherPlayer->WarpToCheckpoint(); } } } RollbackToCheckpoint(player); // Single player can respawn immediately return true; } void LevelHandler::HandlePlayerWarped(Actors::Player* player, Vector2f prevPos, WarpFlags flags) { if ((flags & WarpFlags::Fast) == WarpFlags::Fast) { WarpCameraToTarget(player, true); } else { Vector2f pos = player->GetPos(); if ((prevPos - pos).Length() > 250.0f) { WarpCameraToTarget(player); } } } void LevelHandler::HandlePlayerCoins(Actors::Player* player, std::int32_t prevCount, std::int32_t newCount) { // Coins are shared in cooperation, add it also to all other local players if (prevCount < newCount) { std::int32_t increment = (newCount - prevCount); for (auto current : _players) { if (current != player) { current->AddCoinsInternal(increment); } } } _hud->ShowCoins(newCount); } void LevelHandler::HandlePlayerGems(Actors::Player* player, std::uint8_t gemType, std::int32_t prevCount, std::int32_t newCount) { _hud->ShowGems(gemType, newCount); } void LevelHandler::SetCheckpoint(Actors::Player* player, Vector2f pos) { _checkpointFrames = GetElapsedFrames(); // All players will be respawned at the checkpoint, so also set the same ambient light float ambientLight = _defaultAmbientLight.W; for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { ambientLight = viewport->_ambientLightTarget; break; } } for (auto& player : _players) { player->SetCheckpoint(pos, ambientLight); } if (IsLocalSession()) { _eventMap->CreateCheckpointForRollback(); _tileMap->CreateCheckpointForRollback(); } } void LevelHandler::RollbackToCheckpoint(Actors::Player* player) { // Reset the camera LimitCameraView(player, player->_pos, 0, 0); WarpCameraToTarget(player); if (IsLocalSession()) { for (auto& actor : _actors) { // Despawn all actors that were created after the last checkpoint if (actor->_spawnFrames > _checkpointFrames && !actor->GetState(Actors::ActorState::PreserveOnRollback)) { if ((actor->_state & (Actors::ActorState::IsCreatedFromEventMap | Actors::ActorState::IsFromGenerator)) != Actors::ActorState::None) { Vector2i originTile = actor->_originTile; if ((actor->_state & Actors::ActorState::IsFromGenerator) == Actors::ActorState::IsFromGenerator) { _eventMap->ResetGenerator(originTile.X, originTile.Y); } _eventMap->Deactivate(originTile.X, originTile.Y); } actor->_state |= Actors::ActorState::IsDestroyed; } } _eventMap->RollbackToCheckpoint(); // Don't rollback the tilemap in local sessions for now //_tileMap->RollbackToCheckpoint(); _elapsedFrames = _checkpointFrames; } BeginPlayMusic(_musicDefaultPath); #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { _scripts->OnLevelReload(); } #endif } void LevelHandler::HandleActivateSugarRush(Actors::Player* player) { #if defined(WITH_AUDIO) if (_sugarRushMusic != nullptr) { return; } auto it = _commonResources->Sounds.find(String::nullTerminatedView("SugarRush"_s)); if (it != _commonResources->Sounds.end()) { std::int32_t idx = (it->second.Buffers.size() > 1 ? Random().Next(0, (std::int32_t)it->second.Buffers.size()) : 0); _sugarRushMusic = _playingSounds.emplace_back(std::make_shared(&it->second.Buffers[idx]->Buffer)); _sugarRushMusic->setPosition(Vector3f(0.0f, 0.0f, 100.0f)); _sugarRushMusic->setGain(PreferencesCache::MasterVolume * PreferencesCache::MusicVolume); _sugarRushMusic->setSourceRelative(true); _sugarRushMusic->play(); if (_music != nullptr) { _music->pause(); } } #endif } void LevelHandler::HandleCreateParticleDebrisOnPerish(const Actors::ActorBase* self, Actors::ParticleDebrisEffect effect, Vector2f speed) { // Used only in derived classes } void LevelHandler::HandleCreateSpriteDebris(const Actors::ActorBase* self, AnimState state, std::int32_t count) { // Used only in derived classes } void LevelHandler::ShowLevelText(StringView text, Actors::ActorBase* initiator) { _hud->ShowLevelText(text); } StringView LevelHandler::GetLevelText(std::uint32_t textId, std::int32_t index, std::uint32_t delimiter) { if (textId >= _levelTexts.size()) { return {}; } StringView text = _levelTexts[textId]; std::int32_t textSize = (std::int32_t)text.size(); if (textSize > 0 && index >= 0) { std::int32_t delimiterCount = 0; std::int32_t start = 0; std::int32_t idx = 0; do { Pair cursor = Death::Utf8::NextChar(text, idx); if (cursor.first() == delimiter) { if (delimiterCount == index - 1) { start = idx + 1; } else if (delimiterCount == index) { return StringView(text.data() + start, idx - start); } delimiterCount++; } idx = (std::int32_t)cursor.second(); } while (idx < textSize); if (delimiterCount == index) { return StringView(text.data() + start, text.size() - start); } else { return {}; } } return _x(_levelName, text.data()); } void LevelHandler::OverrideLevelText(std::uint32_t textId, StringView value) { if (textId >= _levelTexts.size()) { if (value.empty()) { return; } _levelTexts.resize(textId + 1); } _levelTexts[textId] = value; } bool LevelHandler::PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads) { bool isGamepad; return PlayerActionPressed(player, action, includeGamepads, isGamepad); } bool LevelHandler::PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) { if (_console->IsVisible() && action != PlayerAction::Menu && action != PlayerAction::Console) { return false; } isGamepad = false; std::int32_t playerIndex = (player ? player->GetPlayerIndex() : 0); auto& input = _playerInputs[playerIndex]; if ((input.PressedActions & (1ull << (std::int32_t)action)) != 0) { isGamepad = (input.PressedActions & (1ull << (32 + (std::int32_t)action))) != 0; return true; } return false; } bool LevelHandler::PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads) { bool isGamepad; return PlayerActionHit(player, action, includeGamepads, isGamepad); } bool LevelHandler::PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) { if (_console->IsVisible() && action != PlayerAction::Menu && action != PlayerAction::Console) { return false; } isGamepad = false; std::int32_t playerIndex = (player ? player->GetPlayerIndex() : 0); auto& input = _playerInputs[playerIndex]; if ((input.PressedActions & (1ull << (std::int32_t)action)) != 0 && (input.PressedActionsLast & (1ull << (std::int32_t)action)) == 0) { isGamepad = (input.PressedActions & (1ull << (32 + (std::int32_t)action))) != 0; return true; } return false; } float LevelHandler::PlayerHorizontalMovement(Actors::Player* player) { if (_console->IsVisible()) { return 0.0f; } auto& input = _playerInputs[player->GetPlayerIndex()]; return (input.Frozen ? input.FrozenMovement.X : input.RequiredMovement.X); } float LevelHandler::PlayerVerticalMovement(Actors::Player* player) { if (_console->IsVisible()) { return 0.0f; } auto& input = _playerInputs[player->GetPlayerIndex()]; return (input.Frozen ? input.FrozenMovement.Y : input.RequiredMovement.Y); } void LevelHandler::PlayerExecuteRumble(Actors::Player* player, StringView rumbleEffect) { #if defined(NCINE_HAS_GAMEPAD_RUMBLE) auto it = _rumbleEffects.find(String::nullTerminatedView(rumbleEffect)); if (it == _rumbleEffects.end()) { return; } std::int32_t joyIdx = ControlScheme::GetGamepadForPlayer(player->GetPlayerIndex()); if (joyIdx >= 0) { _rumble.ExecuteEffect(joyIdx, it->second); } #endif } bool LevelHandler::SerializeResumableToStream(Stream& dest) { std::uint8_t flags = 0; if (_isReforged) flags |= 0x01; if (_cheatsUsed) flags |= 0x02; dest.WriteValue(flags); auto p = _levelName.partition('/'); dest.WriteValue((std::uint8_t)p[0].size()); dest.Write(p[0].data(), (std::uint32_t)p[0].size()); dest.WriteValue((std::uint8_t)p[2].size()); dest.Write(p[2].data(), (std::uint32_t)p[2].size()); dest.WriteValue((std::uint8_t)_difficulty); dest.WriteVariableUint64(_elapsedMillisecondsBegin); dest.WriteValueAsLE(_checkpointFrames); dest.WriteValueAsLE(_waterLevel); dest.WriteValue((std::uint8_t)_weatherType); dest.WriteValue(_weatherIntensity); _tileMap->SerializeResumableToStream(dest, true); _eventMap->SerializeResumableToStream(dest, true); std::size_t playerCount = _players.size(); dest.WriteValue((std::uint8_t)playerCount); for (std::size_t i = 0; i < playerCount; i++) { _players[i]->SerializeResumableToStream(dest); } return true; } void LevelHandler::OnTileFrozen(std::int32_t x, std::int32_t y) { bool iceBlockFound = false; FindCollisionActorsByAABB(nullptr, AABBf(x - 1.0f, y - 1.0f, x + 1.0f, y + 1.0f), [&iceBlockFound](Actors::ActorBase* actor) -> bool { if ((actor->GetState() & Actors::ActorState::IsDestroyed) != Actors::ActorState::None) { return true; } auto* iceBlock = runtime_cast(actor); if (iceBlock != nullptr) { iceBlock->ResetTimeLeft(); iceBlockFound = true; return false; } return true; }); if (!iceBlockFound) { std::shared_ptr iceBlock = std::make_shared(); iceBlock->OnActivated(Actors::ActorActivationDetails( this, Vector3i(x - 1, y - 2, ILevelHandler::MainPlaneZ) )); AddActor(iceBlock); } } void LevelHandler::BeforeActorDestroyed(Actors::ActorBase* actor) { // Nothing to do here } void LevelHandler::ProcessEvents(float timeMult) { ZoneScopedC(0x4876AF); if (!_players.empty()) { std::size_t playerCount = _players.size(); SmallVector playerZones; playerZones.reserve(playerCount * 2); for (std::size_t i = 0; i < playerCount; i++) { auto pos = _players[i]->GetPos(); std::int32_t tx = (std::int32_t)pos.X / TileSet::DefaultTileSize; std::int32_t ty = (std::int32_t)pos.Y / TileSet::DefaultTileSize; const auto& activationRange = playerZones.emplace_back(tx - ActivateTileRange, ty - ActivateTileRange, tx + ActivateTileRange, ty + ActivateTileRange); playerZones.emplace_back(activationRange.L - 4, activationRange.T - 4, activationRange.R + 4, activationRange.B + 4); } for (auto& actor : _actors) { if ((actor->_state & (Actors::ActorState::IsCreatedFromEventMap | Actors::ActorState::IsFromGenerator)) != Actors::ActorState::None) { Vector2i originTile = actor->_originTile; bool isInside = false; for (std::size_t i = 1; i < playerZones.size(); i += 2) { if (playerZones[i].Contains(originTile)) { isInside = true; break; } } if (!isInside && actor->OnTileDeactivated()) { if ((actor->_state & Actors::ActorState::IsFromGenerator) == Actors::ActorState::IsFromGenerator) { _eventMap->ResetGenerator(originTile.X, originTile.Y); } _eventMap->Deactivate(originTile.X, originTile.Y); actor->_state |= Actors::ActorState::IsDestroyed; } } } for (std::size_t i = 0; i < playerZones.size(); i += 2) { const auto& activationZone = playerZones[i]; _eventMap->ActivateEvents(activationZone.L, activationZone.T, activationZone.R, activationZone.B, true); } if (!_checkpointCreated) { // Create checkpoint after first call to ActivateEvents() to avoid duplication of objects that are spawned near player spawn _checkpointCreated = true; _eventMap->CreateCheckpointForRollback(); _tileMap->CreateCheckpointForRollback(); #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { _scripts->OnLevelBegin(); } #endif } } _eventMap->ProcessGenerators(timeMult); } void LevelHandler::ProcessQueuedNextLevel() { bool playersReady = true; for (auto player : _players) { // Exit type was already provided in BeginLevelChange() playersReady &= player->OnLevelChanging(nullptr, ExitType::None); } if (playersReady && _nextLevelTime <= 0.0f) { LevelInitialization levelInit; PrepareNextLevelInitialization(levelInit); HandleLevelChange(std::move(levelInit)); } } void LevelHandler::PrepareNextLevelInitialization(LevelInitialization& levelInit) { StringView realNextLevel; if (!_nextLevelName.empty()) { realNextLevel = _nextLevelName; } else { realNextLevel = ((_nextLevelType & ExitType::TypeMask) == ExitType::Bonus ? _defaultSecretLevel : _defaultNextLevel); } auto p = _levelName.partition('/'); if (!realNextLevel.empty()) { if (realNextLevel.contains('/')) { levelInit.LevelName = realNextLevel; } else { levelInit.LevelName = p[0] + '/' + realNextLevel; } } levelInit.Difficulty = _difficulty; levelInit.IsReforged = _isReforged; levelInit.CheatsUsed = _cheatsUsed; levelInit.LastExitType = _nextLevelType; levelInit.LastEpisodeName = p[0]; levelInit.ElapsedMilliseconds = _elapsedMillisecondsBegin + (std::uint64_t)(_elapsedFrames * FrameTimer::SecondsPerFrame * 1000.0f); std::int32_t playerCount = (std::int32_t)std::min(_players.size(), arraySize(levelInit.PlayerCarryOvers)); for (std::int32_t i = 0; i < playerCount; i++) { levelInit.PlayerCarryOvers[i] = _players[i]->PrepareLevelCarryOver(); } } Recti LevelHandler::GetPlayerViewportBounds(std::int32_t w, std::int32_t h, std::int32_t index) { std::int32_t count = (std::int32_t)_assignedViewports.size(); switch (count) { default: case 1: { return Recti(0, 0, w, h); } case 2: { if (PreferencesCache::PreferVerticalSplitscreen) { std::int32_t halfW = w / 2; return Recti(index * halfW, 0, halfW, h); } else { std::int32_t halfH = h / 2; return Recti(0, index * halfH, w, halfH); } } case 3: case 4: { std::int32_t halfW = (w + 1) / 2; std::int32_t halfH = (h + 1) / 2; return Recti((index % 2) * halfW, (index / 2) * halfH, halfW, halfH); } } } void LevelHandler::ProcessWeather(float timeMult) { if (_weatherType == WeatherType::None) { return; } std::size_t playerCount = _assignedViewports.size(); SmallVector playerZones; playerZones.reserve(playerCount); for (std::size_t i = 0; i < playerCount; i++) { Rectf cullingRect = _assignedViewports[i]->_view->GetCullingRect(); bool found = false; for (std::size_t j = 0; j < playerZones.size(); j++) { if (playerZones[j].Overlaps(cullingRect)) { playerZones[j].Union(cullingRect); found = true; break; } } if (!found) { playerZones.push_back(std::move(cullingRect)); } } std::int32_t weatherIntensity = std::max((std::int32_t)(_weatherIntensity * timeMult), 1); for (auto& zone : playerZones) { for (std::int32_t i = 0; i < weatherIntensity; i++) { TileMap::DebrisFlags debrisFlags; if ((_weatherType & WeatherType::OutdoorsOnly) == WeatherType::OutdoorsOnly) { debrisFlags = TileMap::DebrisFlags::Disappear; } else { debrisFlags = (Random().FastFloat() > 0.7f ? TileMap::DebrisFlags::None : TileMap::DebrisFlags::Disappear); } Vector2f debrisPos = Vector2f(zone.X + Random().FastFloat(zone.W * -1.0f, zone.W * 2.0f), zone.Y + Random().NextFloat(zone.H * -1.0f, zone.H * 2.0f)); WeatherType realWeatherType = (_weatherType & ~WeatherType::OutdoorsOnly); if (realWeatherType == WeatherType::Rain) { auto* res = _commonResources->FindAnimation(Rain); if (res != nullptr) { auto& resBase = res->Base; Vector2i texSize = resBase->TextureDiffuse->GetSize(); float scale = Random().FastFloat(0.4f, 1.1f); float speedX = Random().FastFloat(2.2f, 2.7f) * scale; float speedY = Random().FastFloat(7.6f, 8.6f) * scale; TileMap::DestructibleDebris debris = { }; debris.Pos = debrisPos; debris.Depth = MainPlaneZ - 100 + (std::uint16_t)(200 * scale); debris.Size = resBase->FrameDimensions.As(); debris.Speed = Vector2f(speedX, speedY); debris.Acceleration = Vector2f(0.0f, 0.0f); debris.Scale = scale; debris.ScaleSpeed = 0.0f; debris.Angle = atan2f(speedY, speedX); debris.AngleSpeed = 0.0f; debris.Alpha = 1.0f; debris.AlphaSpeed = 0.0f; debris.Time = 180.0f; std::uint32_t curAnimFrame = res->FrameOffset + Random().Next(0, res->FrameCount); std::uint32_t col = curAnimFrame % resBase->FrameConfiguration.X; std::uint32_t row = curAnimFrame / resBase->FrameConfiguration.X; debris.TexScaleX = (float(resBase->FrameDimensions.X) / float(texSize.X)); debris.TexBiasX = (float(resBase->FrameDimensions.X * col) / float(texSize.X)); debris.TexScaleY = (float(resBase->FrameDimensions.Y) / float(texSize.Y)); debris.TexBiasY = (float(resBase->FrameDimensions.Y * row) / float(texSize.Y)); debris.DiffuseTexture = resBase->TextureDiffuse.get(); debris.Flags = debrisFlags; _tileMap->CreateDebris(debris); } } else { auto* res = _commonResources->FindAnimation(Snow); if (res != nullptr) { auto& resBase = res->Base; Vector2i texSize = resBase->TextureDiffuse->GetSize(); float scale = Random().FastFloat(0.4f, 1.1f); float speedX = Random().FastFloat(-1.6f, -1.2f) * scale; float speedY = Random().FastFloat(3.0f, 4.0f) * scale; float accel = Random().FastFloat(-0.008f, 0.008f) * scale; TileMap::DestructibleDebris debris = { }; debris.Pos = debrisPos; debris.Depth = MainPlaneZ - 100 + (std::uint16_t)(200 * scale); debris.Size = resBase->FrameDimensions.As(); debris.Speed = Vector2f(speedX, speedY); debris.Acceleration = Vector2f(accel, -std::abs(accel)); debris.Scale = scale; debris.ScaleSpeed = 0.0f; debris.Angle = Random().FastFloat(0.0f, fTwoPi); debris.AngleSpeed = speedX * 0.02f; debris.Alpha = 1.0f; debris.AlphaSpeed = 0.0f; debris.Time = 180.0f; std::uint32_t curAnimFrame = res->FrameOffset + Random().Next(0, res->FrameCount); std::uint32_t col = curAnimFrame % resBase->FrameConfiguration.X; std::uint32_t row = curAnimFrame / resBase->FrameConfiguration.X; debris.TexScaleX = (float(resBase->FrameDimensions.X) / float(texSize.X)); debris.TexBiasX = (float(resBase->FrameDimensions.X * col) / float(texSize.X)); debris.TexScaleY = (float(resBase->FrameDimensions.Y) / float(texSize.Y)); debris.TexBiasY = (float(resBase->FrameDimensions.Y * row) / float(texSize.Y)); debris.DiffuseTexture = resBase->TextureDiffuse.get(); debris.Flags = debrisFlags; _tileMap->CreateDebris(debris); } } } } } void LevelHandler::ResolveCollisions(float timeMult) { ZoneScopedC(0x4876AF); auto it = _actors.begin(); while (it != _actors.end()) { Actors::ActorBase* actor = it->get(); if (actor->GetState(Actors::ActorState::IsDestroyed)) { BeforeActorDestroyed(actor); if (actor->_collisionProxyID != Collisions::NullNode) { _collisions.DestroyProxy(actor->_collisionProxyID); actor->_collisionProxyID = Collisions::NullNode; } it = _actors.eraseUnordered(it); continue; } if (actor->GetState(Actors::ActorState::IsDirty)) { if (actor->_collisionProxyID == Collisions::NullNode) { continue; } actor->UpdateAABB(); _collisions.MoveProxy(actor->_collisionProxyID, actor->AABB, actor->_speed * timeMult); actor->SetState(Actors::ActorState::IsDirty, false); } ++it; } struct UpdatePairsHelper { void OnPairAdded(void* proxyA, void* proxyB) { Actors::ActorBase* actorA = (Actors::ActorBase*)proxyA; Actors::ActorBase* actorB = (Actors::ActorBase*)proxyB; if (((actorA->GetState() | actorB->GetState()) & (Actors::ActorState::CollideWithOtherActors | Actors::ActorState::IsDestroyed)) != Actors::ActorState::CollideWithOtherActors) { return; } if (actorA->IsCollidingWith(actorB)) { std::shared_ptr actorSharedA = actorA->shared_from_this(); std::shared_ptr actorSharedB = actorB->shared_from_this(); if (!actorSharedA->OnHandleCollision(actorSharedB->shared_from_this())) { actorSharedB->OnHandleCollision(actorSharedA->shared_from_this()); } } } }; UpdatePairsHelper helper; _collisions.UpdatePairs(&helper); } void LevelHandler::AssignViewport(Actors::Player* player) { _assignedViewports.push_back(std::make_unique(this, player)); #if defined(WITH_AUDIO) for (auto& current : _playingSounds) { if (auto* currentForSplitscreen = runtime_cast(current.get())) { currentForSplitscreen->updateViewports(_assignedViewports); } } #endif } void LevelHandler::InitializeCamera(Rendering::PlayerViewport& viewport) { if (viewport._targetActor == nullptr) { return; } viewport._viewBounds = _viewBoundsTarget; // The position to focus on Vector2f focusPos = viewport._targetActor->_pos; Vector2i halfView = viewport._view->GetSize() / 2; // Clamp camera position to level bounds if (viewport._viewBounds.W > halfView.X * 2) { viewport._cameraPos.X = std::round(std::clamp(focusPos.X, viewport._viewBounds.X + halfView.X, viewport._viewBounds.X + viewport._viewBounds.W - halfView.X)); } else { viewport._cameraPos.X = std::round(viewport._viewBounds.X + viewport._viewBounds.W * 0.5f); } if (viewport._viewBounds.H > halfView.Y * 2) { viewport._cameraPos.Y = std::round(std::clamp(focusPos.Y, viewport._viewBounds.Y + halfView.Y, viewport._viewBounds.Y + viewport._viewBounds.H - halfView.Y)); } else { viewport._cameraPos.Y = std::round(viewport._viewBounds.Y + viewport._viewBounds.H * 0.5f); } viewport._cameraLastPos = viewport._cameraPos; viewport._camera->SetView(viewport._cameraPos, 0.0f, 1.0f); } Vector2f LevelHandler::GetCameraPos(Actors::Player* player) const { for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { return viewport->_cameraPos; } } return {}; } void LevelHandler::LimitCameraView(Actors::Player* player, Vector2f playerPos, std::int32_t left, std::int32_t width) { _levelBounds.X = left; if (width > 0.0f) { _levelBounds.W = width; } else { _levelBounds.W = _tileMap->GetLevelBounds().X - left; } Rectf bounds = _levelBounds.As(); if (left == 0 && width == 0) { for (auto& viewport : _assignedViewports) { viewport->_viewBounds = bounds; } _viewBoundsTarget = bounds; } else { Rendering::PlayerViewport* currentViewport = nullptr; float maxViewWidth = 0.0f; for (auto& viewport : _assignedViewports) { auto size = viewport->GetViewportSize(); if (maxViewWidth < size.X) { maxViewWidth = (float)size.X; } if (viewport->_targetActor == player) { currentViewport = viewport.get(); } } if (bounds.W < maxViewWidth) { bounds.X -= (maxViewWidth - bounds.W); bounds.W = maxViewWidth; } if (_viewBoundsTarget != bounds) { _viewBoundsTarget = bounds; if (currentViewport != nullptr) { float limit = currentViewport->_cameraPos.X - (maxViewWidth * 0.6f); if (currentViewport->_viewBounds.X < limit) { currentViewport->_viewBounds.W += (currentViewport->_viewBounds.X - limit); currentViewport->_viewBounds.X = limit; } } // Warp all other distant players to this player for (auto& viewport : _assignedViewports) { if (viewport->_targetActor != player) { float limit = viewport->_cameraPos.X - (maxViewWidth * 0.6f); if (viewport->_viewBounds.X < limit) { viewport->_viewBounds.W += (viewport->_viewBounds.X - limit); viewport->_viewBounds.X = limit; } auto pos = viewport->_targetActor->_pos; if ((pos.X < bounds.X || pos.X >= bounds.X + bounds.W) && (pos - playerPos).Length() > 100.0f) { if (auto* otherPlayer = runtime_cast(viewport->_targetActor)) { otherPlayer->WarpToPosition(playerPos, WarpFlags::SkipWarpIn); if (currentViewport != nullptr) { viewport->_ambientLight = currentViewport->_ambientLight; viewport->_ambientLightTarget = currentViewport->_ambientLightTarget; } } } } } } } } void LevelHandler::OverrideCameraView(Actors::Player* player, float x, float y, bool topLeft) { for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { viewport->OverrideCamera(x, y, topLeft); } } } void LevelHandler::ShakeCameraView(Actors::Player* player, float duration) { for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { viewport->ShakeCameraView(duration); } } PlayerExecuteRumble(player, "Shake"_s); } void LevelHandler::ShakeCameraViewNear(Vector2f pos, float duration) { constexpr float MaxDistance = 800.0f; for (auto& viewport : _assignedViewports) { if ((viewport->_targetActor->_pos - pos).Length() <= MaxDistance) { viewport->ShakeCameraView(duration); if (auto* player = runtime_cast(viewport->_targetActor)) { PlayerExecuteRumble(player, "Shake"_s); } } } } bool LevelHandler::GetTrigger(std::uint8_t triggerId) { return _tileMap->GetTrigger(triggerId); } void LevelHandler::SetTrigger(std::uint8_t triggerId, bool newState) { _tileMap->SetTrigger(triggerId, newState); } void LevelHandler::SetWeather(WeatherType type, std::uint8_t intensity) { _weatherType = type; _weatherIntensity = intensity; } bool LevelHandler::BeginPlayMusic(StringView path, bool setDefault, bool forceReload) { bool result = false; #if defined(WITH_AUDIO) if (_sugarRushMusic != nullptr) { _sugarRushMusic->stop(); } if (!forceReload && _musicCurrentPath == path) { // Music is already playing or is paused if (_music != nullptr) { _music->play(); } if (setDefault) { _musicDefaultPath = path; } return false; } if (_music != nullptr) { _music->stop(); } if (!path.empty()) { _music = ContentResolver::Get().GetMusic(path); if (_music != nullptr) { _music->setLooping(true); _music->setGain(PreferencesCache::MasterVolume * PreferencesCache::MusicVolume); _music->setSourceRelative(true); _music->play(); result = true; } } else { _music = nullptr; } _musicCurrentPath = path; if (setDefault) { _musicDefaultPath = path; } #endif return result; } void LevelHandler::UpdatePressedActions() { ZoneScopedC(0x4876AF); auto& input = theApplication().GetInputManager(); const JoyMappedState* joyStates[ControlScheme::MaxConnectedGamepads]; std::int32_t joyStatesCount = 0; for (std::int32_t i = 0; i < JoyMapping::MaxNumJoysticks && joyStatesCount < std::int32_t(arraySize(joyStates)); i++) { if (input.isJoyMapped(i)) { joyStates[joyStatesCount++] = &input.joyMappedState(i); } } for (std::int32_t i = 0; i < ControlScheme::MaxSupportedPlayers; i++) { auto& input = _playerInputs[i]; auto processedInput = ControlScheme::FetchProcessedInput(i, _pressedKeys, ArrayView(joyStates, joyStatesCount), input.PressedActions, _hud == nullptr || !_hud->IsWeaponWheelVisible(i)); input.PressedActionsLast = input.PressedActions; input.PressedActions = processedInput.PressedActions; input.RequiredMovement = processedInput.Movement; } // Also apply overriden actions (by touch controls) { auto& input = _playerInputs[0]; input.PressedActions |= _overrideActions; if ((_overrideActions & (1 << (std::int32_t)PlayerAction::Right)) != 0) { input.RequiredMovement.X = 1.0f; } else if ((_overrideActions & (1 << (std::int32_t)PlayerAction::Left)) != 0) { input.RequiredMovement.X = -1.0f; } if ((_overrideActions & (1 << (std::int32_t)PlayerAction::Down)) != 0) { input.RequiredMovement.Y = 1.0f; } else if ((_overrideActions & (1 << (std::int32_t)PlayerAction::Up)) != 0) { input.RequiredMovement.Y = -1.0f; } } } void LevelHandler::UpdateRichPresence() { #if (defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT)) || defined(DEATH_TARGET_UNIX) if (!PreferencesCache::EnableDiscordIntegration || !UI::DiscordRpcClient::Get().IsSupported()) { return; } auto p = _levelName.partition('/'); UI::DiscordRpcClient::RichPresence richPresence; if (p[0] == "prince"_s) { if (p[2] == "01_castle1"_s || p[2] == "02_castle1n"_s) { richPresence.LargeImage = "level-prince-01"_s; } else if (p[2] == "03_carrot1"_s || p[2] == "04_carrot1n"_s) { richPresence.LargeImage = "level-prince-02"_s; } else if (p[2] == "05_labrat1"_s || p[2] == "06_labrat2"_s || p[2] == "bonus_labrat3"_s) { richPresence.LargeImage = "level-prince-03"_s; } } else if (p[0] == "rescue"_s) { if (p[2] == "01_colon1"_s || p[2] == "02_colon2"_s) { richPresence.LargeImage = "level-rescue-01"_s; } else if (p[2] == "03_psych1"_s || p[2] == "04_psych2"_s || p[2] == "bonus_psych3"_s) { richPresence.LargeImage = "level-rescue-02"_s; } else if (p[2] == "05_beach"_s || p[2] == "06_beach2"_s) { richPresence.LargeImage = "level-rescue-03"_s; } } else if (p[0] == "flash"_s) { if (p[2] == "01_diam1"_s || p[2] == "02_diam3"_s) { richPresence.LargeImage = "level-flash-01"_s; } else if (p[2] == "03_tube1"_s || p[2] == "04_tube2"_s || p[2] == "bonus_tube3"_s) { richPresence.LargeImage = "level-flash-02"_s; } else if (p[2] == "05_medivo1"_s || p[2] == "06_medivo2"_s || p[2] == "bonus_garglair"_s) { richPresence.LargeImage = "level-flash-03"_s; } } else if (p[0] == "monk"_s) { if (p[2] == "01_jung1"_s || p[2] == "02_jung2"_s) { richPresence.LargeImage = "level-monk-01"_s; } else if (p[2] == "03_hell"_s || p[2] == "04_hell2"_s) { richPresence.LargeImage = "level-monk-02"_s; } else if (p[2] == "05_damn"_s || p[2] == "06_damn2"_s) { richPresence.LargeImage = "level-monk-03"_s; } } else if (p[0] == "secretf"_s) { if (p[2] == "01_easter1"_s || p[2] == "02_easter2"_s || p[2] == "03_easter3"_s) { richPresence.LargeImage = "level-secretf-01"_s; } else if (p[2] == "04_haunted1"_s || p[2] == "05_haunted2"_s || p[2] == "06_haunted3"_s) { richPresence.LargeImage = "level-secretf-02"_s; } else if (p[2] == "07_town1"_s || p[2] == "08_town2"_s || p[2] == "09_town3"_s) { richPresence.LargeImage = "level-secretf-03"_s; } } else if (p[0] == "xmas98"_s || p[0] == "xmas99"_s) { richPresence.LargeImage = "level-xmas"_s; } else if (p[0] == "share"_s) { richPresence.LargeImage = "level-share"_s; } if (richPresence.LargeImage.empty()) { richPresence.Details = "Playing as "_s; richPresence.LargeImage = "main-transparent"_s; if (!_players.empty()) switch (_players[0]->GetPlayerType()) { default: case PlayerType::Jazz: richPresence.SmallImage = "playing-jazz"_s; break; case PlayerType::Spaz: richPresence.SmallImage = "playing-spaz"_s; break; case PlayerType::Lori: richPresence.SmallImage = "playing-lori"_s; break; } } else { richPresence.Details = "Playing episode as "_s; } if (!_players.empty()) { switch (_players[0]->GetPlayerType()) { default: case PlayerType::Jazz: richPresence.Details += "Jazz"_s; break; case PlayerType::Spaz: richPresence.Details += "Spaz"_s; break; case PlayerType::Lori: richPresence.Details += "Lori"_s; break; } } UI::DiscordRpcClient::Get().SetRichPresence(richPresence); #endif } void LevelHandler::InitializeRumbleEffects() { #if defined(NCINE_HAS_GAMEPAD_RUMBLE) if (auto* breakTile = RegisterRumbleEffect("BreakTile"_s)) { breakTile->AddToTimeline(10, 1.0f, 0.0f); } if (auto* hurt = RegisterRumbleEffect("Hurt"_s)) { hurt->AddToTimeline(4, 0.15f, 0.0f); hurt->AddToTimeline(8, 0.45f, 0.0f); hurt->AddToTimeline(12, 0.15f, 0.0f); } if (auto* die = RegisterRumbleEffect("Die"_s)) { die->AddToTimeline(4, 0.9f, 0.3f); die->AddToTimeline(8, 0.3f, 0.9f); die->AddToTimeline(12, 0.0f, 0.9f); } if (auto* land = RegisterRumbleEffect("Land"_s)) { land->AddToTimeline(4, 0.0f, 0.525f); } if (auto* spring = RegisterRumbleEffect("Spring"_s)) { spring->AddToTimeline(10, 0.0f, 0.8f); } if (auto* fire = RegisterRumbleEffect("Fire"_s)) { fire->AddToTimeline(4, 0.0f, 0.0f, 0.0f, 0.3f); } if (auto* fireWeak = RegisterRumbleEffect("FireWeak"_s)) { fireWeak->AddToTimeline(16, 0.0f, 0.0f, 0.0f, 0.04f); } if (auto* warp = RegisterRumbleEffect("Warp"_s)) { warp->AddToTimeline(2, 0.0f, 0.0f, 0.02f, 0.01f); warp->AddToTimeline(6, 0.3f, 0.0f, 0.04f, 0.02f); warp->AddToTimeline(10, 0.2f, 0.0f, 0.08f, 0.02f); warp->AddToTimeline(13, 0.1f, 0.0f, 0.04f, 0.04f); warp->AddToTimeline(16, 0.0f, 0.0f, 0.02f, 0.08f); warp->AddToTimeline(20, 0.0f, 0.0f, 0.0f, 0.04f); warp->AddToTimeline(22, 0.0f, 0.0f, 0.0f, 0.02f); } if (auto* shake = RegisterRumbleEffect("Shake"_s)) { shake->AddToTimeline(20, 1.0f, 1.0f); shake->AddToTimeline(20, 0.6f, 0.6f); shake->AddToTimeline(30, 0.2f, 0.2f); shake->AddToTimeline(40, 0.2f, 0.0f); } #endif } RumbleDescription* LevelHandler::RegisterRumbleEffect(StringView name) { #if defined(NCINE_HAS_GAMEPAD_RUMBLE) auto it = _rumbleEffects.emplace(name, std::make_shared()); return (it.second ? it.first->second.get() : nullptr); #else return nullptr; #endif } void LevelHandler::PauseGame() { // Show in-game pause menu _pauseMenu = std::make_shared(this); if (IsPausable()) { // Prevent updating of all level objects _rootNode->setUpdateEnabled(false); #if defined(NCINE_HAS_GAMEPAD_RUMBLE) _rumble.CancelAllEffects(); #endif } #if defined(WITH_AUDIO) // Use low-pass filter on music and pause all SFX if (_music != nullptr) { _music->setLowPass(0.1f); } if (IsPausable()) { for (auto& sound : _playingSounds) { if (sound->isPlaying()) { sound->pause(); } } // If Sugar Rush music is playing, pause it and play normal music instead if (_sugarRushMusic != nullptr && _music != nullptr) { _music->play(); } } #endif } void LevelHandler::ResumeGame() { // Resume all level objects _rootNode->setUpdateEnabled(true); // Hide in-game pause menu _pauseMenu = nullptr; #if defined(WITH_AUDIO) // If Sugar Rush music was playing, resume it and pause normal music again if (_sugarRushMusic != nullptr && _music != nullptr) { _music->pause(); } // Resume all SFX for (auto& sound : _playingSounds) { if (sound->isPaused()) { sound->play(); } } if (_music != nullptr) { _music->setLowPass(1.0f); } #endif // Mark Menu button as already pressed to avoid some issues for (auto& input : _playerInputs) { input.PressedActions |= (1ull << (std::int32_t)PlayerAction::Menu); input.PressedActionsLast |= (1ull << (std::int32_t)PlayerAction::Menu); } } bool LevelHandler::CheatKill() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { player->TakeDamage(INT32_MAX, 0.0f, true); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatGod() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { player->SetInvulnerability(36000.0f, Actors::Player::InvulnerableType::Shielded); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatNext() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; BeginLevelChange(nullptr, ExitType::Warp | ExitType::FastTransition); } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatGuns() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { for (std::int32_t i = 0; i < (std::int32_t)WeaponType::Count; i++) { player->AddAmmo((WeaponType)i, 99); } } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatRush() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { player->ActivateSugarRush(1300.0f); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatGems() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { player->AddGems(0, 5); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatBird() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { player->SpawnBird(0, player->GetPos()); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatLife() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { player->AddLives(5); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatPower() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { for (std::int32_t i = 0; i < (std::int32_t)WeaponType::Count; i++) { player->AddWeaponUpgrade((WeaponType)i, 0x01); } } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatCoins() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; // Coins are synchronized automatically _players[0]->AddCoins(5); } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatMorph() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; PlayerType newType; switch (_players[0]->GetPlayerType()) { case PlayerType::Jazz: newType = PlayerType::Spaz; break; case PlayerType::Spaz: newType = PlayerType::Lori; break; default: newType = PlayerType::Jazz; break; } if (!_players[0]->MorphTo(newType)) { _players[0]->MorphTo(PlayerType::Jazz); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } bool LevelHandler::CheatShield() { if (IsCheatingAllowed() && !_players.empty()) { _cheatsUsed = true; for (auto* player : _players) { ShieldType shieldType = (ShieldType)(((std::int32_t)player->GetActiveShield() + 1) % (std::int32_t)ShieldType::Count); player->SetShield(shieldType, 40.0f * FrameTimer::FramesPerSecond); } } else { _console->WriteLine(UI::MessageLevel::Error, _("Cheats are not allowed in current context")); } return true; } #if defined(WITH_IMGUI) ImVec2 LevelHandler::WorldPosToScreenSpace(const Vector2f pos) { auto& mainViewport = _assignedViewports[0]; Rectf bounds = mainViewport->GetBounds(); Vector2i originalSize = mainViewport->_view->GetSize(); Vector2f upscaledSize = _upscalePass.GetTargetSize(); Vector2f halfView = bounds.Center(); return ImVec2( (pos.X - mainViewport->_cameraPos.X + halfView.X) * upscaledSize.X / originalSize.X, (pos.Y - mainViewport->_cameraPos.Y + halfView.Y) * upscaledSize.Y / originalSize.Y ); } #endif LevelHandler::PlayerInput::PlayerInput() : PressedActions(0), PressedActionsLast(0), Frozen(false) { } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LevelHandler.h000066400000000000000000000311261512772601700242540ustar00rootroot00000000000000#pragma once #include "ILevelHandler.h" #include "IResumable.h" #include "IStateHandler.h" #include "IRootController.h" #include "LevelDescriptor.h" #include "WeatherType.h" #include "Events/EventMap.h" #include "Events/EventSpawner.h" #include "Tiles/ITileMapOwner.h" #include "Tiles/TileMap.h" #include "Collisions/DynamicTreeBroadPhase.h" #include "Input/RumbleProcessor.h" #include "Input/ControlScheme.h" #include "Rendering/UpscaleRenderPass.h" #include "../nCine/Graphics/Shader.h" #include "../nCine/Audio/AudioBufferPlayer.h" #include "../nCine/Audio/AudioStreamPlayer.h" #if defined(WITH_IMGUI) # include #endif using namespace Jazz2::Input; namespace Jazz2 { namespace Actors { class Player; } namespace Actors::Bosses { class BossBase; } namespace Rendering { class LightingRenderer; class BlurRenderPass; class CombineRenderer; class PlayerViewport; } #if defined(WITH_ANGELSCRIPT) namespace Scripting { class LevelScriptLoader; } #endif namespace UI { class HUD; class InGameConsole; } namespace UI::Menu { class InGameMenu; } /** @brief Level handler of a local game session */ class LevelHandler : public ILevelHandler, public IStateHandler, public IResumable, public Tiles::ITileMapOwner { DEATH_RUNTIME_OBJECT(ILevelHandler, IStateHandler); friend class Rendering::LightingRenderer; friend class Rendering::BlurRenderPass; friend class Rendering::CombineRenderer; friend class Rendering::PlayerViewport; #if defined(WITH_ANGELSCRIPT) friend class Scripting::LevelScriptLoader; #endif friend class UI::HUD; friend class UI::Menu::InGameMenu; public: /** @{ @name Constants */ /** @brief Default width of viewport */ static constexpr std::int32_t DefaultWidth = 720; /** @brief Default height of viewport */ static constexpr std::int32_t DefaultHeight = 405; /** @brief Range of tile activation */ static constexpr std::int32_t ActivateTileRange = 26; /** @} */ LevelHandler(IRootController* root); ~LevelHandler() override; bool Initialize(const LevelInitialization& levelInit) override; bool Initialize(Stream& src, std::uint16_t version) override; Events::EventSpawner* EventSpawner() override; Events::EventMap* EventMap() override; Tiles::TileMap* TileMap() override; GameDifficulty GetDifficulty() const override; bool IsLocalSession() const override; bool IsServer() const override; bool IsPausable() const override; bool IsReforged() const override; bool CanActivateSugarRush() const override; bool CanEventDisappear(EventType eventType) const override; bool CanPlayersCollide() const override; Recti GetLevelBounds() const override; float GetElapsedFrames() const override; float GetGravity() const override; float GetWaterLevel() const override; float GetHurtInvulnerableTime() const override; ArrayView> GetActors() const override; ArrayView GetPlayers() const override; float GetDefaultAmbientLight() const override; float GetAmbientLight(Actors::Player* player) const override; void SetAmbientLight(Actors::Player* player, float value) override; Vector2i GetViewSize() const override; void OnBeginFrame() override; void OnEndFrame() override; void OnInitializeViewport(std::int32_t width, std::int32_t height) override; /** @brief Called when a console command is entered */ virtual bool OnConsoleCommand(StringView line); void OnKeyPressed(const KeyboardEvent& event) override; void OnKeyReleased(const KeyboardEvent& event) override; void OnTextInput(const TextInputEvent& event) override; void OnTouchEvent(const TouchEvent& event) override; void AddActor(std::shared_ptr actor) override; std::shared_ptr PlaySfx(Actors::ActorBase* self, StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) override; std::shared_ptr PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) override; void WarpCameraToTarget(Actors::ActorBase* actor, bool fast = false) override; bool IsPositionEmpty(Actors::ActorBase* self, const AABBf& aabb, Tiles::TileCollisionParams& params, Actors::ActorBase** collider) override; void FindCollisionActorsByAABB(const Actors::ActorBase* self, const AABBf& aabb, Function&& callback) override; void FindCollisionActorsByRadius(float x, float y, float radius, Function&& callback) override; void GetCollidingPlayers(const AABBf& aabb, Function&& callback) override; void BroadcastTriggeredEvent(Actors::ActorBase* initiator, EventType eventType, std::uint8_t* eventParams) override; void BeginLevelChange(Actors::ActorBase* initiator, ExitType exitType, StringView nextLevel = {}) override; void SendPacket(const Actors::ActorBase* self, ArrayView data) override; void HandleBossActivated(Actors::Bosses::BossBase* boss, Actors::ActorBase* initiator = nullptr) override; void HandleLevelChange(LevelInitialization&& levelInit) override; void HandleGameOver(Actors::Player* player) override; bool HandlePlayerDied(Actors::Player* player) override; void HandlePlayerWarped(Actors::Player* player, Vector2f prevPos, WarpFlags flags) override; void HandlePlayerCoins(Actors::Player* player, std::int32_t prevCount, std::int32_t newCount) override; void HandlePlayerGems(Actors::Player* player, std::uint8_t gemType, std::int32_t prevCount, std::int32_t newCount) override; void SetCheckpoint(Actors::Player* player, Vector2f pos) override; void RollbackToCheckpoint(Actors::Player* player) override; void HandleActivateSugarRush(Actors::Player* player) override; void HandleCreateParticleDebrisOnPerish(const Actors::ActorBase* self, Actors::ParticleDebrisEffect effect, Vector2f speed) override; void HandleCreateSpriteDebris(const Actors::ActorBase* self, AnimState state, std::int32_t count) override; void ShowLevelText(StringView text, Actors::ActorBase* initiator = nullptr) override; StringView GetLevelText(std::uint32_t textId, std::int32_t index = -1, std::uint32_t delimiter = 0) override; void OverrideLevelText(std::uint32_t textId, StringView value) override; Vector2f GetCameraPos(Actors::Player* player) const override; void LimitCameraView(Actors::Player* player, Vector2f playerPos, std::int32_t left, std::int32_t width) override; void OverrideCameraView(Actors::Player* player, float x, float y, bool topLeft = false) override; void ShakeCameraView(Actors::Player* player, float duration) override; void ShakeCameraViewNear(Vector2f pos, float duration) override; bool GetTrigger(std::uint8_t triggerId) override; void SetTrigger(std::uint8_t triggerId, bool newState) override; void SetWeather(WeatherType type, std::uint8_t intensity) override; bool BeginPlayMusic(StringView path, bool setDefault = false, bool forceReload = false) override; bool PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads = true) override; bool PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) override; bool PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads = true) override; bool PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) override; float PlayerHorizontalMovement(Actors::Player* player) override; float PlayerVerticalMovement(Actors::Player* player) override; void PlayerExecuteRumble(Actors::Player* player, StringView rumbleEffect) override; bool SerializeResumableToStream(Stream& dest) override; void OnAdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount) override {} void OnTileFrozen(std::int32_t x, std::int32_t y) override; protected: /** @brief Describes current input state of a player */ struct PlayerInput { std::uint64_t PressedActions; std::uint64_t PressedActionsLast; Vector2f RequiredMovement; Vector2f FrozenMovement; bool Frozen; PlayerInput(); }; #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring IRootController* _root; Shader* _lightingShader; Shader* _blurShader; Shader* _downsampleShader; Shader* _combineShader; Shader* _combineWithWaterShader; Rendering::UpscaleRenderPassWithClipping _upscalePass; std::unique_ptr _rootNode; std::unique_ptr _noiseTexture; SmallVector, 0> _assignedViewports; #if defined(WITH_ANGELSCRIPT) std::unique_ptr _scripts; #endif SmallVector, 0> _actors; SmallVector _players; String _levelName; String _levelDisplayName; String _defaultNextLevel; String _defaultSecretLevel; GameDifficulty _difficulty; WeatherType _weatherType; std::uint8_t _weatherIntensity; ExitType _nextLevelType; float _nextLevelTime; String _nextLevelName; String _musicDefaultPath, _musicCurrentPath; Recti _levelBounds; bool _isReforged, _cheatsUsed; bool _checkpointCreated; SmallVector _levelTexts; Events::EventSpawner _eventSpawner; std::unique_ptr _eventMap; std::unique_ptr _tileMap; Collisions::DynamicTreeBroadPhase _collisions; Vector2i _viewSize; Rectf _viewBoundsTarget; std::int64_t _elapsedMillisecondsBegin; float _elapsedFrames; float _checkpointFrames; float _waterLevel; Vector4f _defaultAmbientLight; #if defined(WITH_AUDIO) std::unique_ptr _music; SmallVector> _playingSounds; std::shared_ptr _sugarRushMusic; #endif Metadata* _commonResources; std::unique_ptr _hud; std::unique_ptr _console; std::shared_ptr _pauseMenu; std::shared_ptr _activeBoss; BitArray _pressedKeys; std::uint32_t _overrideActions; PlayerInput _playerInputs[ControlScheme::MaxSupportedPlayers]; #if defined(NCINE_HAS_GAMEPAD_RUMBLE) RumbleProcessor _rumble; HashMap> _rumbleEffects; #endif #endif /** @brief Invokes the specified callback asynchronously, usually at the end of current frame */ void InvokeAsync(Function&& callback); /** @brief Attaches all required level components to the handler */ virtual void AttachComponents(LevelDescriptor&& descriptor); /** @brief Creates HUD */ virtual std::unique_ptr CreateHUD(); /** @brief Spawns all players */ virtual void SpawnPlayers(const LevelInitialization& levelInit); /** @brief Returns `true` if cheats are enabled */ virtual bool IsCheatingAllowed(); /** @brief Called after the level is loaded and all players were spawned */ virtual void OnInitialized(); /** @brief Called before an actor (object) is destroyed */ virtual void BeforeActorDestroyed(Actors::ActorBase* actor); /** @brief Processes events */ virtual void ProcessEvents(float timeMult); /** @brief Processes transition to the next level if queued */ virtual void ProcessQueuedNextLevel(); /** @brief Prepares @ref LevelInitialization for transition to the next level */ virtual void PrepareNextLevelInitialization(LevelInitialization& levelInit); /** @brief Returns player viewport bounds */ Recti GetPlayerViewportBounds(std::int32_t w, std::int32_t h, std::int32_t index); /** @brief Processes weather */ void ProcessWeather(float timeMult); /** @brief Resolves collisions */ void ResolveCollisions(float timeMult); /** @brief Assigns viewport */ void AssignViewport(Actors::Player* player); /** @brief Initializes camera for specified viewport */ void InitializeCamera(Rendering::PlayerViewport& viewport); /** @brief Updates pressed actions */ void UpdatePressedActions(); /** @brief Updates rich presence */ void UpdateRichPresence(); /** @brief Initializes common rumble effects */ void InitializeRumbleEffects(); /** @brief Registers a rumple effect */ RumbleDescription* RegisterRumbleEffect(StringView name); /** @brief Pauses the game */ void PauseGame(); /** @brief Resumes the paused game */ void ResumeGame(); #if defined(WITH_IMGUI) ImVec2 WorldPosToScreenSpace(const Vector2f pos); #endif private: bool CheatKill(); bool CheatGod(); bool CheatNext(); bool CheatGuns(); bool CheatRush(); bool CheatGems(); bool CheatBird(); bool CheatLife(); bool CheatPower(); bool CheatCoins(); bool CheatMorph(); bool CheatShield(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LevelInitialization.cpp000066400000000000000000000065121512772601700262220ustar00rootroot00000000000000#include "LevelInitialization.h" #include #include namespace Jazz2 { LevelInitialization::LevelInitialization() : IsLocalSession(true), Difficulty(GameDifficulty::Normal), IsReforged(true), CheatsUsed(false), LastExitType(ExitType::None), ElapsedMilliseconds(0), PlayerCarryOvers{} { } LevelInitialization::LevelInitialization(StringView level, GameDifficulty difficulty, bool isReforged) : LevelName(level), IsLocalSession(true), Difficulty(difficulty), IsReforged(isReforged), CheatsUsed(false), LastExitType(ExitType::None), ElapsedMilliseconds(0), PlayerCarryOvers{} { for (std::int32_t i = 0; i < MaxPlayerCount; i++) { PlayerCarryOvers[i].Type = PlayerType::None; } } LevelInitialization::LevelInitialization(StringView level, GameDifficulty difficulty, bool isReforged, bool cheatsUsed, PlayerType playerType) : LevelName(level), IsLocalSession(true), Difficulty(difficulty), IsReforged(isReforged), CheatsUsed(cheatsUsed), LastExitType(ExitType::None), ElapsedMilliseconds(0), PlayerCarryOvers{} { PlayerCarryOvers[0].Type = playerType; PlayerCarryOvers[0].Lives = DefaultLives; for (std::int32_t i = 1; i < MaxPlayerCount; i++) { PlayerCarryOvers[i].Type = PlayerType::None; } } LevelInitialization::LevelInitialization(StringView level, GameDifficulty difficulty, bool isReforged, bool cheatsUsed, ArrayView playerTypes) : LevelName(level), IsLocalSession(true), Difficulty(difficulty), IsReforged(isReforged), CheatsUsed(cheatsUsed), LastExitType(ExitType::None), ElapsedMilliseconds(0), PlayerCarryOvers{} { std::int32_t playerCount = std::min((std::int32_t)playerTypes.size(), MaxPlayerCount); for (std::int32_t i = 0; i < playerCount; i++) { PlayerCarryOvers[i].Type = playerTypes[i]; PlayerCarryOvers[i].Lives = DefaultLives; } for (std::int32_t i = playerCount; i < MaxPlayerCount; i++) { PlayerCarryOvers[i].Type = PlayerType::None; } } LevelInitialization::LevelInitialization(const LevelInitialization& copy) noexcept { LevelName = copy.LevelName; IsLocalSession = copy.IsLocalSession; Difficulty = copy.Difficulty; IsReforged = copy.IsReforged; CheatsUsed = copy.CheatsUsed; LastExitType = copy.LastExitType; LastEpisodeName = copy.LastEpisodeName; ElapsedMilliseconds = copy.ElapsedMilliseconds; std::memcpy(PlayerCarryOvers, copy.PlayerCarryOvers, sizeof(PlayerCarryOvers)); } LevelInitialization::LevelInitialization(LevelInitialization&& move) noexcept { LevelName = std::move(move.LevelName); IsLocalSession = move.IsLocalSession; Difficulty = move.Difficulty; IsReforged = move.IsReforged; CheatsUsed = move.CheatsUsed; LastExitType = move.LastExitType; LastEpisodeName = std::move(move.LastEpisodeName); ElapsedMilliseconds = move.ElapsedMilliseconds; std::memcpy(PlayerCarryOvers, move.PlayerCarryOvers, sizeof(PlayerCarryOvers)); } std::int32_t LevelInitialization::GetPlayerCount(const PlayerCarryOver** firstPlayer) const { if (firstPlayer != nullptr) { *firstPlayer = nullptr; } std::int32_t playerCount = 0; for (std::int32_t i = playerCount; i < MaxPlayerCount; i++) { if (PlayerCarryOvers[i].Type != PlayerType::None) { playerCount++; if (firstPlayer != nullptr && *firstPlayer == nullptr) { *firstPlayer = &PlayerCarryOvers[i]; } } } return playerCount; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LevelInitialization.h000066400000000000000000000050561512772601700256710ustar00rootroot00000000000000#pragma once #include "ExitType.h" #include "GameDifficulty.h" #include "PlayerType.h" #include "WeaponType.h" #include #include #include using namespace Death::Containers; namespace Jazz2 { /** @brief Player carry over between levels */ struct PlayerCarryOver { /** @{ @name Constants */ /** @brief Number of weapons */ static constexpr std::int32_t WeaponCount = (std::int32_t)WeaponType::Count; /** @} */ /** @brief Player type */ PlayerType Type; /** @brief Current weapon type */ WeaponType CurrentWeapon; /** @brief Lives */ std::uint8_t Lives; /** @brief Food eaten */ std::uint8_t FoodEaten; /** @brief Score */ std::int32_t Score; /** @brief Gems collected */ StaticArray<4, std::int32_t> Gems; /** @brief Weapon ammo */ StaticArray Ammo; /** @brief Weapon upgrades */ StaticArray WeaponUpgrades; }; /** @brief Level initialization parameters */ struct LevelInitialization { /** @{ @name Constants */ /** @brief Maximum player count */ static constexpr std::int32_t MaxPlayerCount = 4; /** @brief Default number of lives */ static constexpr std::int32_t DefaultLives = 3; /** @} */ /** @brief Level name in `/` format */ String LevelName; /** @brief Last episode name */ String LastEpisodeName; /** @brief Whether the session is local (not online) */ bool IsLocalSession; /** @brief Difficulty */ GameDifficulty Difficulty; /** @brief Whether reforged gameplay is enabled */ bool IsReforged; /** @brief Whether cheats were used */ bool CheatsUsed; /** @brief Last exit type */ ExitType LastExitType; /** @brief Elapsed milliseconds (gameplay time) */ std::uint64_t ElapsedMilliseconds; /** @brief Player carry over descriptions */ StaticArray PlayerCarryOvers; LevelInitialization(); LevelInitialization(StringView level, GameDifficulty difficulty, bool isReforged); LevelInitialization(StringView level, GameDifficulty difficulty, bool isReforged, bool cheatsUsed, PlayerType playerType); LevelInitialization(StringView level, GameDifficulty difficulty, bool isReforged, bool cheatsUsed, ArrayView playerTypes); LevelInitialization(const LevelInitialization& copy) noexcept; LevelInitialization(LevelInitialization&& move) noexcept; /** @brief Returns number of assigned players */ std::int32_t GetPlayerCount(const PlayerCarryOver** firstPlayer = nullptr) const; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/LightEmitter.h000066400000000000000000000006571512772601700243150ustar00rootroot00000000000000#pragma once #include "../nCine/Primitives/Vector2.h" using namespace nCine; namespace Jazz2 { /** @brief Allows objects to emit light */ struct LightEmitter { /** @brief Light position */ Vector2f Pos; /** @brief Light intensity */ float Intensity; /** @brief Light brightness */ float Brightness; /** @brief Light near radius */ float RadiusNear; /** @brief Light far radius */ float RadiusFar; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/000077500000000000000000000000001512772601700240425ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/Backends/000077500000000000000000000000001512772601700255545ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/Backends/enet.h000066400000000000000000007027501512772601700266730ustar00rootroot00000000000000/** * include/enet.h - a Single-Header auto-generated variant of enet.h library. * * Usage: * #define ENET_IMPLEMENTATION exactly in ONE source file right BEFORE including the library, like: * * #define ENET_IMPLEMENTATION * #include * * License: * The MIT License (MIT) * * Copyright (c) 2002-2016 Lee Salzman * Copyright (c) 2017-2021 Vladyslav Hrytsenko, Dominik Madarász * * 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 ENET_INCLUDE_H #define ENET_INCLUDE_H #include #include #include #include #include #define ENET_VERSION_MAJOR 2 #define ENET_VERSION_MINOR 6 #define ENET_VERSION_PATCH 2 #define ENET_VERSION_CREATE(major, minor, patch) (((major)<<16) | ((minor)<<8) | (patch)) #define ENET_VERSION_GET_MAJOR(version) (((version)>>16)&0xFF) #define ENET_VERSION_GET_MINOR(version) (((version)>>8)&0xFF) #define ENET_VERSION_GET_PATCH(version) ((version)&0xFF) #define ENET_VERSION ENET_VERSION_CREATE(ENET_VERSION_MAJOR, ENET_VERSION_MINOR, ENET_VERSION_PATCH) #define ENET_TIME_OVERFLOW 86400000 #define ENET_TIME_LESS(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW) #define ENET_TIME_GREATER(a, b) ((b) - (a) >= ENET_TIME_OVERFLOW) #define ENET_TIME_LESS_EQUAL(a, b) (! ENET_TIME_GREATER (a, b)) #define ENET_TIME_GREATER_EQUAL(a, b) (! ENET_TIME_LESS (a, b)) #define ENET_TIME_DIFFERENCE(a, b) ((a) - (b) >= ENET_TIME_OVERFLOW ? (b) - (a) : (a) - (b)) // =======================================================================// // ! // ! System differences // ! // =======================================================================// #if defined(_WIN32) #if defined(_MSC_VER) && defined(ENET_IMPLEMENTATION) #pragma warning (disable: 4267) // size_t to int conversion #pragma warning (disable: 4244) // 64bit to 32bit int #pragma warning (disable: 4018) // signed/unsigned mismatch #pragma warning (disable: 4146) // unary minus operator applied to unsigned type #endif #ifndef ENET_NO_PRAGMA_LINK #ifndef __GNUC__ #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "iphlpapi.lib") #endif #endif #if _MSC_VER >= 1910 /* It looks like there were changes as of Visual Studio 2017 and there are no 32/64 bit versions of _InterlockedExchange[operation], only InterlockedExchange[operation] (without leading underscore), so we have to distinguish between compiler versions */ #define NOT_UNDERSCORED_INTERLOCKED_EXCHANGE #endif #ifdef __GNUC__ #if (_WIN32_WINNT < 0x0600) #undef _WIN32_WINNT #define _WIN32_WINNT 0x0600 #endif #endif #include #include #if defined(ENET_IMPLEMENTATION) #include #include #endif #if defined(_WIN32) && defined(_MSC_VER) #if _MSC_VER < 1900 typedef struct timespec { long tv_sec; long tv_nsec; }; #endif #define CLOCK_MONOTONIC 0 #endif typedef SOCKET ENetSocket; #define ENET_SOCKET_NULL INVALID_SOCKET #define ENET_HOST_TO_NET_16(value) (htons(value)) #define ENET_HOST_TO_NET_32(value) (htonl(value)) #define ENET_NET_TO_HOST_16(value) (ntohs(value)) #define ENET_NET_TO_HOST_32(value) (ntohl(value)) typedef struct { size_t dataLength; void * data; } ENetBuffer; #define ENET_CALLBACK __cdecl #ifdef ENET_DLL #ifdef ENET_IMPLEMENTATION #define ENET_API __declspec( dllexport ) #else #define ENET_API __declspec( dllimport ) #endif // ENET_IMPLEMENTATION #else #define ENET_API extern #endif // ENET_DLL typedef fd_set ENetSocketSet; #define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO(&(sockset)) #define ENET_SOCKETSET_ADD(sockset, socket) FD_SET(socket, &(sockset)) #define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR(socket, &(sockset)) #define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET(socket, &(sockset)) #else #include #include #include #include #include #include #include #if defined(ENET_IMPLEMENTATION) #include #include #include #include #include #include #endif #ifdef __APPLE__ #include #include #include #endif #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif #ifdef MSG_MAXIOVLEN #define ENET_BUFFER_MAXIMUM MSG_MAXIOVLEN #endif typedef int ENetSocket; #define ENET_SOCKET_NULL -1 #define ENET_HOST_TO_NET_16(value) (htons(value)) /**< macro that converts host to net byte-order of a 16-bit value */ #define ENET_HOST_TO_NET_32(value) (htonl(value)) /**< macro that converts host to net byte-order of a 32-bit value */ #define ENET_NET_TO_HOST_16(value) (ntohs(value)) /**< macro that converts net to host byte-order of a 16-bit value */ #define ENET_NET_TO_HOST_32(value) (ntohl(value)) /**< macro that converts net to host byte-order of a 32-bit value */ typedef struct { void* data; size_t dataLength; } ENetBuffer; #define ENET_CALLBACK #define ENET_API extern typedef fd_set ENetSocketSet; #define ENET_SOCKETSET_EMPTY(sockset) FD_ZERO(&(sockset)) #define ENET_SOCKETSET_ADD(sockset, socket) FD_SET(socket, &(sockset)) #define ENET_SOCKETSET_REMOVE(sockset, socket) FD_CLR(socket, &(sockset)) #define ENET_SOCKETSET_CHECK(sockset, socket) FD_ISSET(socket, &(sockset)) #endif #ifdef __GNUC__ #define ENET_DEPRECATED(func) func __attribute__ ((deprecated)) #elif defined(_MSC_VER) #define ENET_DEPRECATED(func) __declspec(deprecated) func #else #pragma message("WARNING: Please ENET_DEPRECATED for this compiler") #define ENET_DEPRECATED(func) func #endif #ifndef ENET_BUFFER_MAXIMUM #define ENET_BUFFER_MAXIMUM (1 + 2 * ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS) #endif #define ENET_UNUSED(x) (void)x; #define ENET_MAX(x, y) ((x) > (y) ? (x) : (y)) #define ENET_MIN(x, y) ((x) < (y) ? (x) : (y)) #ifndef ENET_IPV6 #define ENET_IPV6 1 #endif #if ENET_IPV6 #if defined(DEATH_TARGET_SWITCH) static const struct in6_addr enet_v4_anyaddr = { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}; static const struct in6_addr enet_v4_noaddr = { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}; static const struct in6_addr enet_v4_localhost = { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01 }}; static const struct in6_addr enet_v6_anyaddr = { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}; static const struct in6_addr enet_v6_noaddr = { .s6_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}; static const struct in6_addr enet_v6_localhost = { .s6_addr = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}; #else static const struct in6_addr enet_v4_anyaddr = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00 }}}; static const struct in6_addr enet_v4_noaddr = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}; static const struct in6_addr enet_v4_localhost = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f, 0x00, 0x00, 0x01 }}}; static const struct in6_addr enet_v6_anyaddr = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}}; static const struct in6_addr enet_v6_noaddr = {{{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}}; static const struct in6_addr enet_v6_localhost = {{{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}}; #endif #define ENET_HOST_ANY in6addr_any #else #define ENET_HOST_ANY 0 #endif #define ENET_HOST_BROADCAST 0xFFFFFFFFU #define ENET_PORT_ANY 0 #ifdef __cplusplus extern "C" { #endif // =======================================================================// // ! // ! Basic stuff // ! // =======================================================================// typedef uint8_t enet_uint8; /**< unsigned 8-bit type */ typedef uint16_t enet_uint16; /**< unsigned 16-bit type */ typedef uint32_t enet_uint32; /**< unsigned 32-bit type */ typedef uint64_t enet_uint64; /**< unsigned 64-bit type */ typedef enet_uint32 ENetVersion; typedef struct _ENetPacket ENetPacket; typedef struct _ENetCallbacks { void *(ENET_CALLBACK *malloc) (size_t size); void (ENET_CALLBACK *free) (void *memory); void (ENET_CALLBACK *no_memory) (void); ENetPacket *(ENET_CALLBACK *packet_create) (const void *data, size_t dataLength, enet_uint32 flags); void (ENET_CALLBACK *packet_destroy) (ENetPacket *packet); } ENetCallbacks; extern void* enet_malloc(size_t); extern void enet_free(void*); extern ENetPacket* enet_packet_create(enet_uint8,const void*,size_t,enet_uint32); extern ENetPacket* enet_packet_create_raw(const void*,size_t,enet_uint32); extern int enet_packet_resize(ENetPacket*, size_t); extern ENetPacket* enet_packet_copy(ENetPacket*); extern void enet_packet_destroy(ENetPacket*); // =======================================================================// // ! // ! List // ! // =======================================================================// typedef struct _ENetListNode { struct _ENetListNode* next; struct _ENetListNode* previous; } ENetListNode; typedef ENetListNode *ENetListIterator; typedef struct _ENetList { ENetListNode sentinel; } ENetList; extern ENetListIterator enet_list_insert(ENetListIterator, void *); extern ENetListIterator enet_list_move(ENetListIterator, void *, void *); extern void* enet_list_remove(ENetListIterator); extern void enet_list_clear(ENetList*); extern size_t enet_list_size(ENetList*); #define enet_list_begin(list) ((list)->sentinel.next) #define enet_list_end(list) (&(list)->sentinel) #define enet_list_empty(list) (enet_list_begin(list) == enet_list_end(list)) #define enet_list_next(iterator) ((iterator)->next) #define enet_list_previous(iterator) ((iterator)->previous) #define enet_list_front(list) ((void *)(list)->sentinel.next) #define enet_list_back(list) ((void *)(list)->sentinel.previous) // =======================================================================// // ! // ! Protocol // ! // =======================================================================// enum { ENET_PROTOCOL_MINIMUM_MTU = 576, ENET_PROTOCOL_MAXIMUM_MTU = 4096, ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS = 32, ENET_PROTOCOL_MINIMUM_WINDOW_SIZE = 4096, ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE = 65536, ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT = 1, ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT = 255, ENET_PROTOCOL_MAXIMUM_PEER_ID = 0xFFF, ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT = 1024 * 1024 }; typedef enum _ENetProtocolCommand { ENET_PROTOCOL_COMMAND_NONE = 0, ENET_PROTOCOL_COMMAND_ACKNOWLEDGE = 1, ENET_PROTOCOL_COMMAND_CONNECT = 2, ENET_PROTOCOL_COMMAND_VERIFY_CONNECT = 3, ENET_PROTOCOL_COMMAND_DISCONNECT = 4, ENET_PROTOCOL_COMMAND_PING = 5, ENET_PROTOCOL_COMMAND_SEND_RELIABLE = 6, ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE = 7, ENET_PROTOCOL_COMMAND_SEND_FRAGMENT = 8, ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED = 9, ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT = 10, ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE = 11, ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT = 12, ENET_PROTOCOL_COMMAND_COUNT = 13, ENET_PROTOCOL_COMMAND_MASK = 0x0F } ENetProtocolCommand; typedef enum _ENetProtocolFlag { ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE = (1 << 7), ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED = (1 << 6), ENET_PROTOCOL_HEADER_FLAG_COMPRESSED = (1 << 14), ENET_PROTOCOL_HEADER_FLAG_SENT_TIME = (1 << 15), ENET_PROTOCOL_HEADER_FLAG_MASK = ENET_PROTOCOL_HEADER_FLAG_COMPRESSED | ENET_PROTOCOL_HEADER_FLAG_SENT_TIME, ENET_PROTOCOL_HEADER_SESSION_MASK = (3 << 12), ENET_PROTOCOL_HEADER_SESSION_SHIFT = 12 } ENetProtocolFlag; #ifdef _MSC_VER #pragma pack(push, 1) #define ENET_PACKED #elif defined(__GNUC__) || defined(__clang__) #define ENET_PACKED __attribute__ ((packed)) #else #define ENET_PACKED #endif typedef struct _ENetProtocolHeader { enet_uint16 peerID; enet_uint16 sentTime; } ENET_PACKED ENetProtocolHeader; typedef struct _ENetProtocolCommandHeader { enet_uint8 command; enet_uint8 channelID; enet_uint16 reliableSequenceNumber; } ENET_PACKED ENetProtocolCommandHeader; typedef struct _ENetProtocolAcknowledge { ENetProtocolCommandHeader header; enet_uint16 receivedReliableSequenceNumber; enet_uint16 receivedSentTime; } ENET_PACKED ENetProtocolAcknowledge; typedef struct _ENetProtocolConnect { ENetProtocolCommandHeader header; enet_uint16 outgoingPeerID; enet_uint8 incomingSessionID; enet_uint8 outgoingSessionID; enet_uint32 mtu; enet_uint32 windowSize; enet_uint32 channelCount; enet_uint32 incomingBandwidth; enet_uint32 outgoingBandwidth; enet_uint32 packetThrottleInterval; enet_uint32 packetThrottleAcceleration; enet_uint32 packetThrottleDeceleration; enet_uint32 connectID; enet_uint32 data; } ENET_PACKED ENetProtocolConnect; typedef struct _ENetProtocolVerifyConnect { ENetProtocolCommandHeader header; enet_uint16 outgoingPeerID; enet_uint8 incomingSessionID; enet_uint8 outgoingSessionID; enet_uint32 mtu; enet_uint32 windowSize; enet_uint32 channelCount; enet_uint32 incomingBandwidth; enet_uint32 outgoingBandwidth; enet_uint32 packetThrottleInterval; enet_uint32 packetThrottleAcceleration; enet_uint32 packetThrottleDeceleration; enet_uint32 connectID; } ENET_PACKED ENetProtocolVerifyConnect; typedef struct _ENetProtocolBandwidthLimit { ENetProtocolCommandHeader header; enet_uint32 incomingBandwidth; enet_uint32 outgoingBandwidth; } ENET_PACKED ENetProtocolBandwidthLimit; typedef struct _ENetProtocolThrottleConfigure { ENetProtocolCommandHeader header; enet_uint32 packetThrottleInterval; enet_uint32 packetThrottleAcceleration; enet_uint32 packetThrottleDeceleration; } ENET_PACKED ENetProtocolThrottleConfigure; typedef struct _ENetProtocolDisconnect { ENetProtocolCommandHeader header; enet_uint32 data; } ENET_PACKED ENetProtocolDisconnect; typedef struct _ENetProtocolPing { ENetProtocolCommandHeader header; } ENET_PACKED ENetProtocolPing; typedef struct _ENetProtocolSendReliable { ENetProtocolCommandHeader header; enet_uint16 dataLength; } ENET_PACKED ENetProtocolSendReliable; typedef struct _ENetProtocolSendUnreliable { ENetProtocolCommandHeader header; enet_uint16 unreliableSequenceNumber; enet_uint16 dataLength; } ENET_PACKED ENetProtocolSendUnreliable; typedef struct _ENetProtocolSendUnsequenced { ENetProtocolCommandHeader header; enet_uint16 unsequencedGroup; enet_uint16 dataLength; } ENET_PACKED ENetProtocolSendUnsequenced; typedef struct _ENetProtocolSendFragment { ENetProtocolCommandHeader header; enet_uint16 startSequenceNumber; enet_uint16 dataLength; enet_uint32 fragmentCount; enet_uint32 fragmentNumber; enet_uint32 totalLength; enet_uint32 fragmentOffset; } ENET_PACKED ENetProtocolSendFragment; typedef union _ENetProtocol { ENetProtocolCommandHeader header; ENetProtocolAcknowledge acknowledge; ENetProtocolConnect connect; ENetProtocolVerifyConnect verifyConnect; ENetProtocolDisconnect disconnect; ENetProtocolPing ping; ENetProtocolSendReliable sendReliable; ENetProtocolSendUnreliable sendUnreliable; ENetProtocolSendUnsequenced sendUnsequenced; ENetProtocolSendFragment sendFragment; ENetProtocolBandwidthLimit bandwidthLimit; ENetProtocolThrottleConfigure throttleConfigure; } ENET_PACKED ENetProtocol; #ifdef _MSC_VER #pragma pack(pop) #endif // =======================================================================// // ! // ! General ENet structs/enums // ! // =======================================================================// typedef enum _ENetSocketType { ENET_SOCKET_TYPE_STREAM = 1, ENET_SOCKET_TYPE_DATAGRAM = 2 } ENetSocketType; typedef enum _ENetSocketWait { ENET_SOCKET_WAIT_NONE = 0, ENET_SOCKET_WAIT_SEND = (1 << 0), ENET_SOCKET_WAIT_RECEIVE = (1 << 1), ENET_SOCKET_WAIT_INTERRUPT = (1 << 2) } ENetSocketWait; typedef enum _ENetSocketOption { ENET_SOCKOPT_NONBLOCK = 1, ENET_SOCKOPT_BROADCAST = 2, ENET_SOCKOPT_RCVBUF = 3, ENET_SOCKOPT_SNDBUF = 4, ENET_SOCKOPT_REUSEADDR = 5, ENET_SOCKOPT_RCVTIMEO = 6, ENET_SOCKOPT_SNDTIMEO = 7, ENET_SOCKOPT_ERROR = 8, ENET_SOCKOPT_NODELAY = 9, ENET_SOCKOPT_IPV6_V6ONLY = 10, } ENetSocketOption; typedef enum _ENetSocketShutdown { ENET_SOCKET_SHUTDOWN_READ = 0, ENET_SOCKET_SHUTDOWN_WRITE = 1, ENET_SOCKET_SHUTDOWN_READ_WRITE = 2 } ENetSocketShutdown; /** * Portable internet address structure. * * The host must be specified in network byte-order, and the port must be in host * byte-order. The constant ENET_HOST_ANY may be used to specify the default * server host. The constant ENET_HOST_BROADCAST may be used to specify the * broadcast address (255.255.255.255). This makes sense for enet_host_connect, * but not for enet_host_create. Once a server responds to a broadcast, the * address is updated from ENET_HOST_BROADCAST to the server's actual IP address. */ typedef struct _ENetAddress { #if ENET_IPV6 struct in6_addr host; enet_uint16 port; enet_uint16 sin6_scope_id; #else enet_uint32 host; enet_uint16 port; #endif } ENetAddress; #if ENET_IPV6 #define enet_host_equal(in6_addr_a, in6_addr_b) (memcmp(&in6_addr_a, &in6_addr_b, sizeof(struct in6_addr)) == 0) #else #define enet_host_equal(in4_addr_a, in4_addr_b) (memcmp(&in4_addr_a, &in4_addr_b, sizeof(enet_uint32)) == 0) #endif /** * Packet flag bit constants. * * The host must be specified in network byte-order, and the port must be in * host byte-order. The constant ENET_HOST_ANY may be used to specify the * default server host. * * @sa ENetPacket */ typedef enum _ENetPacketFlag { ENET_PACKET_FLAG_RELIABLE = (1 << 0), /** packet must be received by the target peer and resend attempts should be made until the packet is delivered */ ENET_PACKET_FLAG_UNSEQUENCED = (1 << 1), /** packet will not be sequenced with other packets not supported for reliable packets */ ENET_PACKET_FLAG_NO_ALLOCATE = (1 << 2), /** packet will not allocate data, and user must supply it instead */ ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT = (1 << 3), /** packet will be fragmented using unreliable (instead of reliable) sends if it exceeds the MTU */ ENET_PACKET_FLAG_SENT = (1 << 8), /** whether the packet has been sent from all queues it has been entered into */ } ENetPacketFlag; typedef void (ENET_CALLBACK *ENetPacketFreeCallback)(void*); /** * ENet packet structure. * * An ENet data packet that may be sent to or received from a peer. The shown * fields should only be read and never modified. The data field contains the * allocated data for the packet. The dataLength fields specifies the length * of the allocated data. The flags field is either 0 (specifying no flags), * or a bitwise-or of any combination of the following flags: * * ENET_PACKET_FLAG_RELIABLE - packet must be received by the target peer and resend attempts should be made until the packet is delivered * ENET_PACKET_FLAG_UNSEQUENCED - packet will not be sequenced with other packets (not supported for reliable packets) * ENET_PACKET_FLAG_NO_ALLOCATE - packet will not allocate data, and user must supply it instead * ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT - packet will be fragmented using unreliable (instead of reliable) sends if it exceeds the MTU * ENET_PACKET_FLAG_SENT - whether the packet has been sent from all queues it has been entered into * @sa ENetPacketFlag */ typedef struct _ENetPacket { size_t referenceCount; /**< internal use only */ enet_uint32 flags; /**< bitwise-or of ENetPacketFlag constants */ enet_uint8* data; /**< allocated data for packet */ size_t dataLength; /**< length of data */ ENetPacketFreeCallback freeCallback; /**< function to be called when the packet is no longer in use */ void* userData; /**< application private data, may be freely modified */ } ENetPacket; typedef struct _ENetAcknowledgement { ENetListNode acknowledgementList; enet_uint32 sentTime; ENetProtocol command; } ENetAcknowledgement; typedef struct _ENetOutgoingCommand { ENetListNode outgoingCommandList; enet_uint16 reliableSequenceNumber; enet_uint16 unreliableSequenceNumber; enet_uint32 sentTime; enet_uint32 roundTripTimeout; enet_uint32 roundTripTimeoutLimit; enet_uint32 fragmentOffset; enet_uint16 fragmentLength; enet_uint16 sendAttempts; ENetProtocol command; ENetPacket * packet; } ENetOutgoingCommand; typedef struct _ENetIncomingCommand { ENetListNode incomingCommandList; enet_uint16 reliableSequenceNumber; enet_uint16 unreliableSequenceNumber; ENetProtocol command; enet_uint32 fragmentCount; enet_uint32 fragmentsRemaining; enet_uint32 *fragments; ENetPacket * packet; } ENetIncomingCommand; typedef enum _ENetPeerState { ENET_PEER_STATE_DISCONNECTED = 0, ENET_PEER_STATE_CONNECTING = 1, ENET_PEER_STATE_ACKNOWLEDGING_CONNECT = 2, ENET_PEER_STATE_CONNECTION_PENDING = 3, ENET_PEER_STATE_CONNECTION_SUCCEEDED = 4, ENET_PEER_STATE_CONNECTED = 5, ENET_PEER_STATE_DISCONNECT_LATER = 6, ENET_PEER_STATE_DISCONNECTING = 7, ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT = 8, ENET_PEER_STATE_ZOMBIE = 9 } ENetPeerState; enum { ENET_HOST_RECEIVE_BUFFER_SIZE = 256 * 1024, ENET_HOST_SEND_BUFFER_SIZE = 256 * 1024, ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL = 1000, ENET_HOST_DEFAULT_MTU = 1400, ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE = 32 * 1024 * 1024, ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024, ENET_PEER_DEFAULT_ROUND_TRIP_TIME = 500, ENET_PEER_DEFAULT_PACKET_THROTTLE = 32, ENET_PEER_PACKET_THROTTLE_SCALE = 32, ENET_PEER_PACKET_THROTTLE_COUNTER = 7, ENET_PEER_PACKET_THROTTLE_ACCELERATION = 2, ENET_PEER_PACKET_THROTTLE_DECELERATION = 2, ENET_PEER_PACKET_THROTTLE_INTERVAL = 5000, ENET_PEER_PACKET_LOSS_SCALE = (1 << 16), ENET_PEER_PACKET_LOSS_INTERVAL = 10000, ENET_PEER_WINDOW_SIZE_SCALE = 64 * 1024, ENET_PEER_TIMEOUT_LIMIT = 32, ENET_PEER_TIMEOUT_MINIMUM = 5000, ENET_PEER_TIMEOUT_MAXIMUM = 30000, ENET_PEER_PING_INTERVAL = 500, ENET_PEER_UNSEQUENCED_WINDOWS = 64, ENET_PEER_UNSEQUENCED_WINDOW_SIZE = 1024, ENET_PEER_FREE_UNSEQUENCED_WINDOWS = 32, ENET_PEER_RELIABLE_WINDOWS = 16, ENET_PEER_RELIABLE_WINDOW_SIZE = 0x1000, ENET_PEER_FREE_RELIABLE_WINDOWS = 8 }; typedef struct _ENetChannel { enet_uint16 outgoingReliableSequenceNumber; enet_uint16 outgoingUnreliableSequenceNumber; enet_uint16 usedReliableWindows; enet_uint16 reliableWindows[ENET_PEER_RELIABLE_WINDOWS]; enet_uint16 incomingReliableSequenceNumber; enet_uint16 incomingUnreliableSequenceNumber; ENetList incomingReliableCommands; ENetList incomingUnreliableCommands; } ENetChannel; /** * An ENet peer which data packets may be sent or received from. * * No fields should be modified unless otherwise specified. */ typedef struct _ENetPeer { ENetListNode dispatchList; struct _ENetHost* host; enet_uint16 outgoingPeerID; enet_uint16 incomingPeerID; enet_uint32 connectID; enet_uint8 outgoingSessionID; enet_uint8 incomingSessionID; ENetAddress address; /**< Internet address of the peer */ void* data; /**< Application private data, may be freely modified */ ENetPeerState state; ENetChannel* channels; size_t channelCount; /**< Number of channels allocated for communication with peer */ enet_uint32 incomingBandwidth; /**< Downstream bandwidth of the client in bytes/second */ enet_uint32 outgoingBandwidth; /**< Upstream bandwidth of the client in bytes/second */ enet_uint32 incomingBandwidthThrottleEpoch; enet_uint32 outgoingBandwidthThrottleEpoch; enet_uint32 incomingDataTotal; enet_uint64 totalDataReceived; enet_uint32 outgoingDataTotal; enet_uint64 totalDataSent; enet_uint32 lastSendTime; enet_uint32 lastReceiveTime; enet_uint32 nextTimeout; enet_uint32 earliestTimeout; enet_uint32 packetLossEpoch; enet_uint32 packetsSent; enet_uint64 totalPacketsSent; /**< total number of packets sent during a session */ enet_uint32 packetsLost; enet_uint32 totalPacketsLost; /**< total number of packets lost during a session */ enet_uint32 packetLoss; /**< mean packet loss of reliable packets as a ratio with respect to the constant ENET_PEER_PACKET_LOSS_SCALE */ enet_uint32 packetLossVariance; enet_uint32 packetThrottle; enet_uint32 packetThrottleLimit; enet_uint32 packetThrottleCounter; enet_uint32 packetThrottleEpoch; enet_uint32 packetThrottleAcceleration; enet_uint32 packetThrottleDeceleration; enet_uint32 packetThrottleInterval; enet_uint32 pingInterval; enet_uint32 timeoutLimit; enet_uint32 timeoutMinimum; enet_uint32 timeoutMaximum; enet_uint32 lastRoundTripTime; enet_uint32 lowestRoundTripTime; enet_uint32 lastRoundTripTimeVariance; enet_uint32 highestRoundTripTimeVariance; enet_uint32 roundTripTime; /**< mean round trip time (RTT), in milliseconds, between sending a reliable packet and receiving its acknowledgement */ enet_uint32 roundTripTimeVariance; enet_uint32 mtu; enet_uint32 windowSize; enet_uint32 reliableDataInTransit; enet_uint16 outgoingReliableSequenceNumber; ENetList acknowledgements; ENetList sentReliableCommands; ENetList sentUnreliableCommands; ENetList outgoingReliableCommands; ENetList outgoingUnreliableCommands; ENetList dispatchedCommands; int needsDispatch; enet_uint16 incomingUnsequencedGroup; enet_uint16 outgoingUnsequencedGroup; enet_uint32 unsequencedWindow[ENET_PEER_UNSEQUENCED_WINDOW_SIZE / 32]; enet_uint32 eventData; size_t totalWaitingData; } ENetPeer; /** An ENet packet compressor for compressing UDP packets before socket sends or receives. */ typedef struct _ENetCompressor { /** Context data for the compressor. Must be non-NULL. */ void *context; /** Compresses from inBuffers[0:inBufferCount-1], containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ size_t(ENET_CALLBACK * compress) (void *context, const ENetBuffer * inBuffers, size_t inBufferCount, size_t inLimit, enet_uint8 * outData, size_t outLimit); /** Decompresses from inData, containing inLimit bytes, to outData, outputting at most outLimit bytes. Should return 0 on failure. */ size_t(ENET_CALLBACK * decompress) (void *context, const enet_uint8 * inData, size_t inLimit, enet_uint8 * outData, size_t outLimit); /** Destroys the context when compression is disabled or the host is destroyed. May be NULL. */ void (ENET_CALLBACK * destroy)(void *context); } ENetCompressor; /** Callback that computes the checksum of the data held in buffers[0:bufferCount-1] */ typedef enet_uint32 (ENET_CALLBACK * ENetChecksumCallback)(const ENetBuffer *buffers, size_t bufferCount); /** Callback for intercepting received raw UDP packets. Should return 1 to intercept, 0 to ignore, or -1 to propagate an error. */ typedef int (ENET_CALLBACK * ENetInterceptCallback)(struct _ENetHost *host, void *event); /** An ENet host for communicating with peers. * * No fields should be modified unless otherwise stated. * * @sa enet_host_create() * @sa enet_host_destroy() * @sa enet_host_connect() * @sa enet_host_service() * @sa enet_host_flush() * @sa enet_host_broadcast() * @sa enet_host_compress() * @sa enet_host_channel_limit() * @sa enet_host_bandwidth_limit() * @sa enet_host_bandwidth_throttle() */ typedef struct _ENetHost { ENetSocket socket; ENetAddress address; /**< Internet address of the host */ enet_uint32 incomingBandwidth; /**< downstream bandwidth of the host */ enet_uint32 outgoingBandwidth; /**< upstream bandwidth of the host */ enet_uint32 bandwidthThrottleEpoch; enet_uint32 mtu; enet_uint32 randomSeed; int recalculateBandwidthLimits; ENetPeer* peers; /**< array of peers allocated for this host */ size_t peerCount; /**< number of peers allocated for this host */ size_t channelLimit; /**< maximum number of channels allowed for connected peers */ enet_uint32 serviceTime; ENetList dispatchQueue; int continueSending; size_t packetSize; enet_uint16 headerFlags; ENetProtocol commands[ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS]; size_t commandCount; ENetBuffer buffers[ENET_BUFFER_MAXIMUM]; size_t bufferCount; ENetChecksumCallback checksum; /**< callback the user can set to enable packet checksums for this host */ ENetCompressor compressor; enet_uint8 packetData[2][ENET_PROTOCOL_MAXIMUM_MTU]; ENetAddress receivedAddress; enet_uint8* receivedData; size_t receivedDataLength; enet_uint32 totalSentData; /**< total data sent, user should reset to 0 as needed to prevent overflow */ enet_uint32 totalSentPackets; /**< total UDP packets sent, user should reset to 0 as needed to prevent overflow */ enet_uint32 totalReceivedData; /**< total data received, user should reset to 0 as needed to prevent overflow */ enet_uint32 totalReceivedPackets; /**< total UDP packets received, user should reset to 0 as needed to prevent overflow */ ENetInterceptCallback intercept; /**< callback the user can set to intercept received raw UDP packets */ size_t connectedPeers; size_t bandwidthLimitedPeers; size_t duplicatePeers; /**< optional number of allowed peers from duplicate IPs, defaults to ENET_PROTOCOL_MAXIMUM_PEER_ID */ size_t maximumPacketSize; /**< the maximum allowable packet size that may be sent or received on a peer */ size_t maximumWaitingData; /**< the maximum aggregate amount of buffer space a peer may use waiting for packets to be delivered */ } ENetHost; /** * An ENet event type, as specified in @ref ENetEvent. */ typedef enum _ENetEventType { /** no event occurred within the specified time limit */ ENET_EVENT_TYPE_NONE = 0, /** a connection request initiated by enet_host_connect has completed. * The peer field contains the peer which successfully connected. */ ENET_EVENT_TYPE_CONNECT = 1, /** a peer has disconnected. This event is generated on a successful * completion of a disconnect initiated by enet_peer_disconnect, if * a peer has timed out. The peer field contains the peer * which disconnected. The data field contains user supplied data * describing the disconnection, or 0, if none is available. */ ENET_EVENT_TYPE_DISCONNECT = 2, /** a packet has been received from a peer. The peer field specifies the * peer which sent the packet. The channelID field specifies the channel * number upon which the packet was received. The packet field contains * the packet that was received; this packet must be destroyed with * enet_packet_destroy after use. */ ENET_EVENT_TYPE_RECEIVE = 3, /** a peer is disconnected because the host didn't receive the acknowledgment * packet within certain maximum time out. The reason could be because of bad * network connection or host crashed. */ ENET_EVENT_TYPE_DISCONNECT_TIMEOUT = 4, } ENetEventType; /** * An ENet event as returned by enet_host_service(). * * @sa enet_host_service */ typedef struct _ENetEvent { ENetEventType type; /**< type of the event */ ENetPeer* peer; /**< peer that generated a connect, disconnect or receive event */ enet_uint8 channelID; /**< channel on the peer that generated the event, if appropriate */ enet_uint32 data; /**< data associated with the event, if appropriate */ ENetPacket* packet; /**< packet associated with the event, if appropriate */ } ENetEvent; // =======================================================================// // ! // ! Public API // ! // =======================================================================// /** * Initializes ENet globally. Must be called prior to using any functions in ENet. * @returns 0 on success, < 0 on failure */ ENET_API int enet_initialize(void); /** * Initializes ENet globally and supplies user-overridden callbacks. Must be called prior to using any functions in ENet. Do not use enet_initialize() if you use this variant. Make sure the ENetCallbacks structure is zeroed out so that any additional callbacks added in future versions will be properly ignored. * * @param version the constant ENET_VERSION should be supplied so ENet knows which version of ENetCallbacks struct to use * @param inits user-overridden callbacks where any NULL callbacks will use ENet's defaults * @returns 0 on success, < 0 on failure */ ENET_API int enet_initialize_with_callbacks(ENetVersion version, const ENetCallbacks* inits); /** * Shuts down ENet globally. Should be called when a program that has initialized ENet exits. */ ENET_API void enet_deinitialize(void); /** * Gives the linked version of the ENet library. * @returns the version number */ ENET_API ENetVersion enet_linked_version(void); /** Returns the monotonic time in milliseconds. Its initial value is unspecified unless otherwise set. */ ENET_API enet_uint32 enet_time_get(void); /** ENet socket functions */ ENET_API ENetSocket enet_socket_create(ENetSocketType); ENET_API int enet_socket_bind(ENetSocket, const ENetAddress*); ENET_API int enet_socket_get_address(ENetSocket, ENetAddress*); ENET_API int enet_socket_listen(ENetSocket, int); ENET_API ENetSocket enet_socket_accept(ENetSocket, ENetAddress*); ENET_API int enet_socket_connect(ENetSocket, const ENetAddress*); ENET_API int enet_socket_send(ENetSocket, const ENetAddress*, const ENetBuffer*, size_t); ENET_API int enet_socket_receive(ENetSocket, ENetAddress*, ENetBuffer*, size_t); ENET_API int enet_socket_wait(ENetSocket, enet_uint32*, enet_uint64); ENET_API int enet_socket_set_option(ENetSocket, ENetSocketOption, int); ENET_API int enet_socket_get_option(ENetSocket, ENetSocketOption, int*); ENET_API int enet_socket_shutdown(ENetSocket, ENetSocketShutdown); ENET_API void enet_socket_destroy(ENetSocket); ENET_API int enet_socketset_select(ENetSocket, ENetSocketSet*, ENetSocketSet*, enet_uint32); #if !defined(ENET_FEATURE_ADDRESS_MAPPING) || !ENET_IPV6 /** Attempts to parse the printable form of the IP address in the parameter hostName and sets the host field in the address parameter if successful. @param address destination to store the parsed IP address @param hostName IP address to parse @retval 0 on success @retval < 0 on failure @returns the address of the given hostName in address on success */ ENET_API int enet_address_set_host_ip_old(ENetAddress* address, const char* hostName); /** Attempts to resolve the host named by the parameter hostName and sets the host field in the address parameter if successful. @param address destination to store resolved address @param hostName host name to lookup @retval 0 on success @retval < 0 on failure @returns the address of the given hostName in address on success */ ENET_API int enet_address_set_host_old(ENetAddress* address, const char* hostName); /** Gives the printable form of the IP address specified in the address parameter. @param address address printed @param hostName destination for name, must not be NULL @param nameLength maximum length of hostName. @returns the null-terminated name of the host in hostName on success @retval 0 on success @retval < 0 on failure */ ENET_API int enet_address_get_host_ip_old(const ENetAddress* address, char* hostName, size_t nameLength); /** Attempts to do a reverse lookup of the host field in the address parameter. @param address address used for reverse lookup @param hostName destination for name, must not be NULL @param nameLength maximum length of hostName. @returns the null-terminated name of the host in hostName on success @retval 0 on success @retval < 0 on failure */ ENET_API int enet_address_get_host_old(const ENetAddress* address, char* hostName, size_t nameLength); #endif ENET_API int enet_address_set_host_ip_new(ENetAddress* address, const char* hostName); ENET_API int enet_address_set_host_new(ENetAddress* address, const char* hostName); ENET_API int enet_address_get_host_ip_new(const ENetAddress* address, char* hostName, size_t nameLength); ENET_API int enet_address_get_host_new(const ENetAddress* address, char* hostName, size_t nameLength); #if defined(ENET_FEATURE_ADDRESS_MAPPING) && ENET_IPV6 #define enet_address_set_host_ip enet_address_set_host_ip_new #define enet_address_set_host enet_address_set_host_new #define enet_address_get_host_ip enet_address_get_host_ip_new #define enet_address_get_host enet_address_get_host_new #else #define enet_address_set_host_ip enet_address_set_host_ip_old #define enet_address_set_host enet_address_set_host_old #define enet_address_get_host_ip enet_address_get_host_ip_old #define enet_address_get_host enet_address_get_host_old #endif ENET_API enet_uint32 enet_host_get_peers_count(ENetHost*); ENET_API enet_uint32 enet_host_get_packets_sent(ENetHost*); ENET_API enet_uint32 enet_host_get_packets_received(ENetHost*); ENET_API enet_uint32 enet_host_get_bytes_sent(ENetHost*); ENET_API enet_uint32 enet_host_get_bytes_received(ENetHost*); ENET_API enet_uint32 enet_host_get_received_data(ENetHost*, enet_uint8** data); ENET_API enet_uint32 enet_host_get_mtu(ENetHost*); ENET_API enet_uint32 enet_peer_get_id(ENetPeer*); ENET_API enet_uint32 enet_peer_get_ip(ENetPeer*, char* ip, size_t ipLength); ENET_API enet_uint16 enet_peer_get_port(ENetPeer*); ENET_API enet_uint32 enet_peer_get_rtt(ENetPeer*); ENET_API enet_uint64 enet_peer_get_packets_sent(ENetPeer*); ENET_API enet_uint32 enet_peer_get_packets_lost(ENetPeer*); ENET_API enet_uint64 enet_peer_get_bytes_sent(ENetPeer*); ENET_API enet_uint64 enet_peer_get_bytes_received(ENetPeer*); ENET_API ENetPeerState enet_peer_get_state(ENetPeer*); ENET_API void* enet_peer_get_data(ENetPeer*); ENET_API void enet_peer_set_data(ENetPeer*, const void*); ENET_API void* enet_packet_get_data(ENetPacket*); ENET_API enet_uint32 enet_packet_get_length(ENetPacket*); ENET_API void enet_packet_set_free_callback(ENetPacket*, void*); ENET_API ENetPacket* enet_packet_create_offset(const void*, size_t, size_t, enet_uint32); ENET_API enet_uint32 enet_crc32(const ENetBuffer*, size_t); ENET_API ENetHost* enet_host_create(const ENetAddress*, size_t, size_t, enet_uint32, enet_uint32); ENET_API void enet_host_destroy(ENetHost*); ENET_API ENetPeer* enet_host_connect(ENetHost*, const ENetAddress*, size_t, enet_uint32); ENET_API int enet_host_check_events(ENetHost*, ENetEvent*); ENET_API int enet_host_service(ENetHost*, ENetEvent*, enet_uint32); ENET_API int enet_host_send_raw(ENetHost*, const ENetAddress*, enet_uint8*, size_t); ENET_API int enet_host_send_raw_ex(ENetHost* host, const ENetAddress* address, enet_uint8* data, size_t skipBytes, size_t bytesToSend); ENET_API void enet_host_set_intercept(ENetHost*, const ENetInterceptCallback); ENET_API void enet_host_flush(ENetHost*); ENET_API void enet_host_broadcast(ENetHost*, enet_uint8, ENetPacket*); ENET_API void enet_host_compress(ENetHost*, const ENetCompressor*); ENET_API void enet_host_channel_limit(ENetHost*, size_t); ENET_API void enet_host_bandwidth_limit(ENetHost*, enet_uint32, enet_uint32); extern void enet_host_bandwidth_throttle(ENetHost*); extern enet_uint64 enet_host_random_seed(void); ENET_API int enet_peer_send(ENetPeer*, enet_uint8, ENetPacket*); ENET_API ENetPacket* enet_peer_receive(ENetPeer*, enet_uint8 * channelID); ENET_API void enet_peer_ping(ENetPeer*); ENET_API void enet_peer_ping_interval(ENetPeer*, enet_uint32); ENET_API void enet_peer_timeout(ENetPeer*, enet_uint32, enet_uint32, enet_uint32); ENET_API void enet_peer_reset(ENetPeer*); ENET_API void enet_peer_disconnect(ENetPeer*, enet_uint32); ENET_API void enet_peer_disconnect_now(ENetPeer*, enet_uint32); ENET_API void enet_peer_disconnect_later(ENetPeer*, enet_uint32); ENET_API void enet_peer_throttle_configure(ENetPeer*, enet_uint32, enet_uint32, enet_uint32); extern int enet_peer_throttle(ENetPeer*, enet_uint32); extern void enet_peer_reset_queues(ENetPeer*); extern void enet_peer_setup_outgoing_command(ENetPeer*, ENetOutgoingCommand*); extern ENetOutgoingCommand* enet_peer_queue_outgoing_command(ENetPeer*, const ENetProtocol*, ENetPacket*, enet_uint32, enet_uint16); extern ENetIncomingCommand* enet_peer_queue_incoming_command(ENetPeer*, const ENetProtocol*, const void*, size_t, enet_uint32, enet_uint32); extern ENetAcknowledgement* enet_peer_queue_acknowledgement(ENetPeer*, const ENetProtocol*, enet_uint16); extern void enet_peer_dispatch_incoming_unreliable_commands(ENetPeer*, ENetChannel*); extern void enet_peer_dispatch_incoming_reliable_commands(ENetPeer*, ENetChannel*); extern void enet_peer_on_connect(ENetPeer *); extern void enet_peer_on_disconnect(ENetPeer *); extern size_t enet_protocol_command_size(enet_uint8); #ifdef __cplusplus } #endif #if defined(ENET_IMPLEMENTATION) && !defined(ENET_IMPLEMENTATION_DONE) #define ENET_IMPLEMENTATION_DONE 1 #ifdef __cplusplus extern "C" { #endif // =======================================================================// // ! // ! Atomics // ! // =======================================================================// #if defined(_MSC_VER) #define ENET_AT_CASSERT_PRED(predicate) sizeof(char[2 * !!(predicate)-1]) #define ENET_IS_SUPPORTED_ATOMIC(size) ENET_AT_CASSERT_PRED(size == 1 || size == 2 || size == 4 || size == 8) #define ENET_ATOMIC_SIZEOF(variable) (ENET_IS_SUPPORTED_ATOMIC(sizeof(*(variable))), sizeof(*(variable))) __inline int64_t enet_at_atomic_read(char* ptr, size_t size) { switch (size) { case 1: return _InterlockedExchangeAdd8((volatile char*)ptr, 0); case 2: return _InterlockedExchangeAdd16((volatile SHORT*)ptr, 0); case 4: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedExchangeAdd((volatile LONG*)ptr, 0); #else return _InterlockedExchangeAdd((volatile LONG*)ptr, 0); #endif case 8: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedExchangeAdd64((volatile LONGLONG*)ptr, 0); #else return _InterlockedExchangeAdd64((volatile LONGLONG*)ptr, 0); #endif default: return 0xbad13bad; /* never reached */ } } __inline int64_t enet_at_atomic_write(char* ptr, int64_t value, size_t size) { switch (size) { case 1: return _InterlockedExchange8((volatile char*)ptr, (char)value); case 2: return _InterlockedExchange16((volatile SHORT*)ptr, (SHORT)value); case 4: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedExchange((volatile LONG*)ptr, (LONG)value); #else return _InterlockedExchange((volatile LONG*)ptr, (LONG)value); #endif case 8: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedExchange64((volatile LONGLONG*)ptr, (LONGLONG)value); #else return _InterlockedExchange64((volatile LONGLONG*)ptr, (LONGLONG)value); #endif default: return 0xbad13bad; /* never reached */ } } __inline int64_t enet_at_atomic_cas(char* ptr, int64_t new_val, int64_t old_val, size_t size) { switch (size) { case 1: return _InterlockedCompareExchange8((volatile char*)ptr, (char)new_val, (char)old_val); case 2: return _InterlockedCompareExchange16((volatile SHORT*)ptr, (SHORT)new_val, (SHORT)old_val); case 4: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedCompareExchange((volatile LONG*)ptr, (LONG)new_val, (LONG)old_val); #else return _InterlockedCompareExchange((volatile LONG*)ptr, (LONG)new_val, (LONG)old_val); #endif case 8: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedCompareExchange64((volatile LONGLONG*)ptr, (LONGLONG)new_val, (LONGLONG)old_val); #else return _InterlockedCompareExchange64((volatile LONGLONG*)ptr, (LONGLONG)new_val, (LONGLONG)old_val); #endif default: return 0xbad13bad; /* never reached */ } } __inline int64_t enet_at_atomic_inc(char* ptr, int64_t delta, size_t data_size) { switch (data_size) { case 1: return _InterlockedExchangeAdd8((volatile char*)ptr, (char)delta); case 2: return _InterlockedExchangeAdd16((volatile SHORT*)ptr, (SHORT)delta); case 4: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedExchangeAdd((volatile LONG*)ptr, (LONG)delta); #else return _InterlockedExchangeAdd((volatile LONG*)ptr, (LONG)delta); #endif case 8: #ifdef NOT_UNDERSCORED_INTERLOCKED_EXCHANGE return InterlockedExchangeAdd64((volatile LONGLONG*)ptr, (LONGLONG)delta); #else return _InterlockedExchangeAdd64((volatile LONGLONG*)ptr, (LONGLONG)delta); #endif default: return 0xbad13bad; /* never reached */ } } #define ENET_ATOMIC_READ(variable) enet_at_atomic_read((char*)(variable), ENET_ATOMIC_SIZEOF(variable)) #define ENET_ATOMIC_WRITE(variable, new_val) \ enet_at_atomic_write((char*)(variable), (int64_t)(new_val), ENET_ATOMIC_SIZEOF(variable)) #define ENET_ATOMIC_CAS(variable, old_value, new_val) \ enet_at_atomic_cas((char*)(variable), (int64_t)(new_val), (int64_t)(old_value), \ ENET_ATOMIC_SIZEOF(variable)) #define ENET_ATOMIC_INC(variable) enet_at_atomic_inc((char*)(variable), 1, ENET_ATOMIC_SIZEOF(variable)) #define ENET_ATOMIC_DEC(variable) enet_at_atomic_inc((char*)(variable), -1, ENET_ATOMIC_SIZEOF(variable)) #define ENET_ATOMIC_INC_BY(variable, delta) \ enet_at_atomic_inc((char*)(variable), (delta), ENET_ATOMIC_SIZEOF(variable)) #define ENET_ATOMIC_DEC_BY(variable, delta) \ enet_at_atomic_inc((char*)(variable), -(delta), ENET_ATOMIC_SIZEOF(variable)) #elif defined(__GNUC__) || defined(__clang__) #if defined(__clang__) || (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) #define AT_HAVE_ATOMICS #endif /* We want to use __atomic built-ins if possible because the __sync primitives are deprecated, because the __atomic build-ins allow us to use ENET_ATOMIC_WRITE on uninitialized memory without running into undefined behavior, and because the __atomic versions generate more efficient code since we don't need to rely on CAS when we don't actually want it. Note that we use acquire-release memory order (like mutexes do). We could use sequentially consistent memory order but that has lower performance and is almost always unneeded. */ #ifdef AT_HAVE_ATOMICS #define ENET_ATOMIC_READ(ptr) __atomic_load_n((ptr), __ATOMIC_ACQUIRE) #define ENET_ATOMIC_WRITE(ptr, value) __atomic_store_n((ptr), (value), __ATOMIC_RELEASE) #ifndef typeof #define typeof __typeof__ #endif /* clang_analyzer doesn't know that CAS writes to memory so it complains about potentially lost data. Replace the code with the equivalent non-sync code. */ #ifdef __clang_analyzer__ #define ENET_ATOMIC_CAS(ptr, old_value, new_value) \ ({ \ typeof(*(ptr)) ENET_ATOMIC_CAS_old_actual_ = (*(ptr)); \ if (ATOMIC_CAS_old_actual_ == (old_value)) { \ *(ptr) = new_value; \ } \ ENET_ATOMIC_CAS_old_actual_; \ }) #else /* Could use __auto_type instead of typeof but that shouldn't work in C++. The ({ }) syntax is a GCC extension called statement expression. It lets us return a value out of the macro. TODO We should return bool here instead of the old value to avoid the ABA problem. */ #define ENET_ATOMIC_CAS(ptr, old_value, new_value) \ ({ \ typeof(*(ptr)) ENET_ATOMIC_CAS_expected_ = (old_value); \ __atomic_compare_exchange_n((ptr), &ENET_ATOMIC_CAS_expected_, (new_value), false, \ __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE); \ ENET_ATOMIC_CAS_expected_; \ }) #endif /* __clang_analyzer__ */ #define ENET_ATOMIC_INC(ptr) __atomic_fetch_add((ptr), 1, __ATOMIC_ACQ_REL) #define ENET_ATOMIC_DEC(ptr) __atomic_fetch_sub((ptr), 1, __ATOMIC_ACQ_REL) #define ENET_ATOMIC_INC_BY(ptr, delta) __atomic_fetch_add((ptr), (delta), __ATOMIC_ACQ_REL) #define ENET_ATOMIC_DEC_BY(ptr, delta) __atomic_fetch_sub((ptr), (delta), __ATOMIC_ACQ_REL) #else #define ENET_ATOMIC_READ(variable) __sync_fetch_and_add(variable, 0) #define ENET_ATOMIC_WRITE(variable, new_val) \ (void) __sync_val_compare_and_swap((variable), *(variable), (new_val)) #define ENET_ATOMIC_CAS(variable, old_value, new_val) \ __sync_val_compare_and_swap((variable), (old_value), (new_val)) #define ENET_ATOMIC_INC(variable) __sync_fetch_and_add((variable), 1) #define ENET_ATOMIC_DEC(variable) __sync_fetch_and_sub((variable), 1) #define ENET_ATOMIC_INC_BY(variable, delta) __sync_fetch_and_add((variable), (delta), 1) #define ENET_ATOMIC_DEC_BY(variable, delta) __sync_fetch_and_sub((variable), (delta), 1) #endif /* AT_HAVE_ATOMICS */ #undef AT_HAVE_ATOMICS #endif /* defined(_MSC_VER) */ // =======================================================================// // ! // ! Callbacks // ! // =======================================================================// ENetCallbacks callbacks = { malloc, free, abort, enet_packet_create_raw, enet_packet_destroy }; int enet_initialize_with_callbacks(ENetVersion version, const ENetCallbacks* inits) { if (version < ENET_VERSION_CREATE(1, 3, 0)) { return -1; } if (inits->malloc != NULL || inits->free != NULL) { if (inits->malloc == NULL || inits->free == NULL) { return -1; } callbacks.malloc = inits->malloc; callbacks.free = inits->free; } if (inits->no_memory != NULL) { callbacks.no_memory = inits->no_memory; } if (inits->packet_create != NULL || inits->packet_destroy != NULL) { if (inits->packet_create == NULL || inits->packet_destroy == NULL) { return -1; } callbacks.packet_create = inits->packet_create; callbacks.packet_destroy = inits->packet_destroy; } return enet_initialize(); } ENetVersion enet_linked_version(void) { return ENET_VERSION; } void* enet_malloc(size_t size) { void* memory = callbacks.malloc(size); if (memory == NULL) { callbacks.no_memory(); } return memory; } void enet_free(void* memory) { callbacks.free(memory); } // =======================================================================// // ! // ! List // ! // =======================================================================// void enet_list_clear(ENetList* list) { list->sentinel.next = &list->sentinel; list->sentinel.previous = &list->sentinel; } ENetListIterator enet_list_insert(ENetListIterator position, void* data) { ENetListIterator result = (ENetListIterator)data; result->previous = position->previous; result->next = position; result->previous->next = result; position->previous = result; return result; } void* enet_list_remove(ENetListIterator position) { position->previous->next = position->next; position->next->previous = position->previous; return position; } ENetListIterator enet_list_move(ENetListIterator position, void* dataFirst, void* dataLast) { ENetListIterator first = (ENetListIterator)dataFirst; ENetListIterator last = (ENetListIterator)dataLast; first->previous->next = last->next; last->next->previous = first->previous; first->previous = position->previous; last->next = position; first->previous->next = first; position->previous = last; return first; } size_t enet_list_size(ENetList* list) { size_t size = 0; ENetListIterator position; for (position = enet_list_begin(list); position != enet_list_end(list); position = enet_list_next(position)) { ++size; } return size; } // =======================================================================// // ! // ! Packet // ! // =======================================================================// /** * Creates a packet that may be sent to a peer. * @param type packet type sent as the first byte * @param data initial contents of the packet's data * @param dataLength size of the data allocated for this packet * @param flags flags for this packet as described for the ENetPacket structure. * @returns the packet on success, NULL on failure */ ENetPacket* enet_packet_create(enet_uint8 type, const void* data, size_t dataLength, enet_uint32 flags) { if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { return NULL; } ENetPacket* packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket) + 1 + dataLength); if (packet == NULL) { return NULL; } packet->data = (enet_uint8*)packet + sizeof(ENetPacket); packet->data[0] = type; if (dataLength > 0) { memcpy(&packet->data[1], data, dataLength); } packet->referenceCount = 0; packet->flags = flags; packet->dataLength = 1 + dataLength; packet->freeCallback = NULL; packet->userData = NULL; return packet; } /** * Creates a raw packet that may be sent to a peer. * @param type initial contents of the packet's data; the packet's data will remain uninitialized if data is NULL. * @param data initial contents of the packet's data; the packet's data will remain uninitialized if data is NULL. * @param dataLength size of the data allocated for this packet * @param flags flags for this packet as described for the ENetPacket structure. * @returns the packet on success, NULL on failure */ ENetPacket* enet_packet_create_raw(const void* data, size_t dataLength, enet_uint32 flags) { ENetPacket* packet; if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket)); if (packet == NULL) { return NULL; } packet->data = (enet_uint8*)data; } else { packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket) + dataLength); if (packet == NULL) { return NULL; } packet->data = (enet_uint8*)packet + sizeof(ENetPacket); if (data != NULL) { memcpy(packet->data, data, dataLength); } } packet->referenceCount = 0; packet->flags = flags; packet->dataLength = dataLength; packet->freeCallback = NULL; packet->userData = NULL; return packet; } /** Attempts to resize the data in the packet to length specified in the dataLength parameter @param packet packet to resize @param dataLength new size for the packet data @returns 0 on success, < 0 on failure */ int enet_packet_resize(ENetPacket* packet, size_t dataLength) { enet_uint8* newData = 0; if (dataLength <= packet->dataLength || (packet->flags & ENET_PACKET_FLAG_NO_ALLOCATE)) { packet->dataLength = dataLength; return 0; } newData = (enet_uint8*)enet_malloc(dataLength); if (newData == NULL) return -1; if (packet->data != NULL) { if (packet->dataLength > 0) memcpy(newData, packet->data, packet->dataLength); enet_free(packet->data); } packet->data = newData; packet->dataLength = dataLength; return 0; } ENetPacket *enet_packet_create_offset(const void* data, size_t dataLength, size_t dataOffset, enet_uint32 flags) { ENetPacket* packet; if (flags & ENET_PACKET_FLAG_NO_ALLOCATE) { packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket)); if (packet == NULL) { return NULL; } packet->data = (enet_uint8 *)data; } else { packet = (ENetPacket*)enet_malloc(sizeof(ENetPacket) + dataLength + dataOffset); if (packet == NULL) { return NULL; } packet->data = (enet_uint8*)packet + sizeof(ENetPacket); if (data != NULL) { memcpy(packet->data + dataOffset, data, dataLength); } } packet->referenceCount = 0; packet->flags = flags; packet->dataLength = dataLength + dataOffset; packet->freeCallback = NULL; packet->userData = NULL; return packet; } ENetPacket* enet_packet_copy(ENetPacket* packet) { return enet_packet_create_raw(packet->data, packet->dataLength, packet->flags); } /** * Destroys the packet and deallocates its data. * @param packet packet to be destroyed */ void enet_packet_destroy(ENetPacket* packet) { if (packet == NULL) { return; } if (packet->freeCallback != NULL) { (*packet->freeCallback)((void*)packet); } enet_free(packet); } static int initializedCRC32 = 0; static enet_uint32 crcTable[256]; static enet_uint32 reflect_crc(int val, int bits) { int result = 0, bit; for (bit = 0; bit < bits; bit++) { if (val & 1) { result |= 1 << (bits - 1 - bit); } val >>= 1; } return result; } static void initialize_crc32(void) { int byte; for (byte = 0; byte < 256; ++byte) { enet_uint32 crc = reflect_crc(byte, 8) << 24; int offset; for (offset = 0; offset < 8; ++offset) { if (crc & 0x80000000) { crc = (crc << 1) ^ 0x04c11db7; } else { crc <<= 1; } } crcTable[byte] = reflect_crc(crc, 32); } initializedCRC32 = 1; } enet_uint32 enet_crc32(const ENetBuffer* buffers, size_t bufferCount) { enet_uint32 crc = 0xFFFFFFFF; if (!initializedCRC32) { initialize_crc32(); } while (bufferCount-- > 0) { const enet_uint8* data = (const enet_uint8*)buffers->data; const enet_uint8* dataEnd = &data[buffers->dataLength]; while (data < dataEnd) { crc = (crc >> 8) ^ crcTable[(crc & 0xFF) ^ *data++]; } ++buffers; } return ENET_HOST_TO_NET_32(~crc); } // =======================================================================// // ! // ! Protocol // ! // =======================================================================// static size_t commandSizes[ENET_PROTOCOL_COMMAND_COUNT] = { 0, sizeof(ENetProtocolAcknowledge), sizeof(ENetProtocolConnect), sizeof(ENetProtocolVerifyConnect), sizeof(ENetProtocolDisconnect), sizeof(ENetProtocolPing), sizeof(ENetProtocolSendReliable), sizeof(ENetProtocolSendUnreliable), sizeof(ENetProtocolSendFragment), sizeof(ENetProtocolSendUnsequenced), sizeof(ENetProtocolBandwidthLimit), sizeof(ENetProtocolThrottleConfigure), sizeof(ENetProtocolSendFragment) }; size_t enet_protocol_command_size(enet_uint8 commandNumber) { return commandSizes[commandNumber & ENET_PROTOCOL_COMMAND_MASK]; } static void enet_protocol_change_state(ENetHost* host, ENetPeer* peer, ENetPeerState state) { ENET_UNUSED(host) if (state == ENET_PEER_STATE_CONNECTED || state == ENET_PEER_STATE_DISCONNECT_LATER) { enet_peer_on_connect(peer); } else { enet_peer_on_disconnect(peer); } peer->state = state; } static void enet_protocol_dispatch_state(ENetHost* host, ENetPeer* peer, ENetPeerState state) { enet_protocol_change_state(host, peer, state); if (!peer->needsDispatch) { enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList); peer->needsDispatch = 1; } } static int enet_protocol_dispatch_incoming_commands(ENetHost* host, ENetEvent* event) { while (!enet_list_empty(&host->dispatchQueue)) { ENetPeer* peer = (ENetPeer*)enet_list_remove(enet_list_begin(&host->dispatchQueue)); peer->needsDispatch = 0; switch (peer->state) { case ENET_PEER_STATE_CONNECTION_PENDING: case ENET_PEER_STATE_CONNECTION_SUCCEEDED: enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED); event->type = ENET_EVENT_TYPE_CONNECT; event->peer = peer; event->data = peer->eventData; return 1; case ENET_PEER_STATE_ZOMBIE: host->recalculateBandwidthLimits = 1; event->type = ENET_EVENT_TYPE_DISCONNECT; event->peer = peer; event->data = peer->eventData; enet_peer_reset(peer); return 1; case ENET_PEER_STATE_CONNECTED: if (enet_list_empty(&peer->dispatchedCommands)) { continue; } event->packet = enet_peer_receive(peer, &event->channelID); if (event->packet == NULL) { continue; } event->type = ENET_EVENT_TYPE_RECEIVE; event->peer = peer; if (!enet_list_empty(&peer->dispatchedCommands)) { peer->needsDispatch = 1; enet_list_insert(enet_list_end(&host->dispatchQueue), &peer->dispatchList); } return 1; default: break; } } return 0; } /* enet_protocol_dispatch_incoming_commands */ static void enet_protocol_notify_connect(ENetHost* host, ENetPeer* peer, ENetEvent* event) { host->recalculateBandwidthLimits = 1; if (event != NULL) { enet_protocol_change_state(host, peer, ENET_PEER_STATE_CONNECTED); peer->totalDataSent = 0; peer->totalDataReceived = 0; peer->totalPacketsSent = 0; peer->totalPacketsLost = 0; event->type = ENET_EVENT_TYPE_CONNECT; event->peer = peer; event->data = peer->eventData; } else { enet_protocol_dispatch_state(host, peer, peer->state == ENET_PEER_STATE_CONNECTING ? ENET_PEER_STATE_CONNECTION_SUCCEEDED : ENET_PEER_STATE_CONNECTION_PENDING); } } static void enet_protocol_notify_disconnect(ENetHost* host, ENetPeer* peer, ENetEvent* event) { if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING) { host->recalculateBandwidthLimits = 1; } if (peer->state != ENET_PEER_STATE_CONNECTING && peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) { enet_peer_reset(peer); } else if (event != NULL) { event->type = ENET_EVENT_TYPE_DISCONNECT; event->peer = peer; event->data = 0; enet_peer_reset(peer); } else { peer->eventData = 0; enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); } } static void enet_protocol_notify_disconnect_timeout (ENetHost* host, ENetPeer* peer, ENetEvent* event) { if (peer->state >= ENET_PEER_STATE_CONNECTION_PENDING) { host->recalculateBandwidthLimits = 1; } if (peer->state != ENET_PEER_STATE_CONNECTING && peer->state < ENET_PEER_STATE_CONNECTION_SUCCEEDED) { enet_peer_reset (peer); } else if (event != NULL) { event->type = ENET_EVENT_TYPE_DISCONNECT_TIMEOUT; event->peer = peer; event->data = 0; enet_peer_reset(peer); } else { peer->eventData = 0; enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); } } static void enet_protocol_remove_sent_unreliable_commands(ENetPeer* peer) { ENetOutgoingCommand* outgoingCommand; while (!enet_list_empty(&peer->sentUnreliableCommands)) { outgoingCommand = (ENetOutgoingCommand*)enet_list_front(&peer->sentUnreliableCommands); enet_list_remove(&outgoingCommand->outgoingCommandList); if (outgoingCommand->packet != NULL) { --outgoingCommand->packet->referenceCount; if (outgoingCommand->packet->referenceCount == 0) { outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT; callbacks.packet_destroy(outgoingCommand->packet); } } enet_free(outgoingCommand); } } static ENetProtocolCommand enet_protocol_remove_sent_reliable_command(ENetPeer* peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID) { ENetOutgoingCommand* outgoingCommand = NULL; ENetListIterator currentCommand; ENetProtocolCommand commandNumber; int wasSent = 1; for (currentCommand = enet_list_begin(&peer->sentReliableCommands); currentCommand != enet_list_end(&peer->sentReliableCommands); currentCommand = enet_list_next(currentCommand) ) { outgoingCommand = (ENetOutgoingCommand*)currentCommand; if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber && outgoingCommand->command.header.channelID == channelID) { break; } } if (currentCommand == enet_list_end(&peer->sentReliableCommands)) { for (currentCommand = enet_list_begin(&peer->outgoingReliableCommands); currentCommand != enet_list_end(&peer->outgoingReliableCommands); currentCommand = enet_list_next(currentCommand) ) { outgoingCommand = (ENetOutgoingCommand*)currentCommand; if (outgoingCommand->sendAttempts < 1) { return ENET_PROTOCOL_COMMAND_NONE; } if (outgoingCommand->reliableSequenceNumber == reliableSequenceNumber && outgoingCommand->command.header.channelID == channelID) { break; } } if (currentCommand == enet_list_end(&peer->outgoingReliableCommands)) { return ENET_PROTOCOL_COMMAND_NONE; } wasSent = 0; } if (outgoingCommand == NULL) { return ENET_PROTOCOL_COMMAND_NONE; } if (channelID < peer->channelCount) { ENetChannel *channel = &peer->channels[channelID]; enet_uint16 reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (channel->reliableWindows[reliableWindow] > 0) { --channel->reliableWindows[reliableWindow]; if (!channel->reliableWindows[reliableWindow]) { channel->usedReliableWindows &= ~(1 << reliableWindow); } } } commandNumber = (ENetProtocolCommand)(outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK); enet_list_remove(&outgoingCommand->outgoingCommandList); if (outgoingCommand->packet != NULL) { if (wasSent) { peer->reliableDataInTransit -= outgoingCommand->fragmentLength; } --outgoingCommand->packet->referenceCount; if (outgoingCommand->packet->referenceCount == 0) { outgoingCommand->packet->flags |= ENET_PACKET_FLAG_SENT; callbacks.packet_destroy(outgoingCommand->packet); } } enet_free(outgoingCommand); if (enet_list_empty(&peer->sentReliableCommands)) { return commandNumber; } outgoingCommand = (ENetOutgoingCommand*)enet_list_front(&peer->sentReliableCommands); peer->nextTimeout = outgoingCommand->sentTime + outgoingCommand->roundTripTimeout; return commandNumber; } /* enet_protocol_remove_sent_reliable_command */ static ENetPeer* enet_protocol_handle_connect(ENetHost* host, ENetProtocolHeader* header, ENetProtocol* command) { ENET_UNUSED(header) enet_uint8 incomingSessionID, outgoingSessionID; enet_uint32 mtu, windowSize; ENetChannel* channel; size_t channelCount, duplicatePeers = 0; ENetPeer* currentPeer, *peer = NULL; ENetProtocol verifyCommand; channelCount = ENET_NET_TO_HOST_32(command->connect.channelCount); if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { return NULL; } for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) { if (peer == NULL) { peer = currentPeer; } } else if (currentPeer->state != ENET_PEER_STATE_CONNECTING && enet_host_equal(currentPeer->address.host, host->receivedAddress.host)) { if (currentPeer->address.port == host->receivedAddress.port && currentPeer->connectID == command->connect.connectID) { return NULL; } ++duplicatePeers; } } if (peer == NULL || duplicatePeers >= host->duplicatePeers) { return NULL; } if (channelCount > host->channelLimit) { channelCount = host->channelLimit; } peer->channels = (ENetChannel*)enet_malloc(channelCount * sizeof(ENetChannel)); if (peer->channels == NULL) { return NULL; } peer->channelCount = channelCount; peer->state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT; peer->connectID = command->connect.connectID; peer->address = host->receivedAddress; peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->connect.outgoingPeerID); peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->connect.incomingBandwidth); peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->connect.outgoingBandwidth); peer->packetThrottleInterval = ENET_NET_TO_HOST_32(command->connect.packetThrottleInterval); peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(command->connect.packetThrottleAcceleration); peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(command->connect.packetThrottleDeceleration); peer->eventData = ENET_NET_TO_HOST_32(command->connect.data); incomingSessionID = command->connect.incomingSessionID == 0xFF ? peer->outgoingSessionID : command->connect.incomingSessionID; incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); if (incomingSessionID == peer->outgoingSessionID) { incomingSessionID = (incomingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); } peer->outgoingSessionID = incomingSessionID; outgoingSessionID = command->connect.outgoingSessionID == 0xFF ? peer->incomingSessionID : command->connect.outgoingSessionID; outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); if (outgoingSessionID == peer->incomingSessionID) { outgoingSessionID = (outgoingSessionID + 1) & (ENET_PROTOCOL_HEADER_SESSION_MASK >> ENET_PROTOCOL_HEADER_SESSION_SHIFT); } peer->incomingSessionID = outgoingSessionID; for (channel = peer->channels; channel < &peer->channels[channelCount]; ++channel) { channel->outgoingReliableSequenceNumber = 0; channel->outgoingUnreliableSequenceNumber = 0; channel->incomingReliableSequenceNumber = 0; channel->incomingUnreliableSequenceNumber = 0; enet_list_clear(&channel->incomingReliableCommands); enet_list_clear(&channel->incomingUnreliableCommands); channel->usedReliableWindows = 0; memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows)); } mtu = ENET_NET_TO_HOST_32(command->connect.mtu); if (mtu < ENET_PROTOCOL_MINIMUM_MTU) { mtu = ENET_PROTOCOL_MINIMUM_MTU; } else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) { mtu = ENET_PROTOCOL_MAXIMUM_MTU; } peer->mtu = mtu; if (host->outgoingBandwidth == 0 && peer->incomingBandwidth == 0) { peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } else if (host->outgoingBandwidth == 0 || peer->incomingBandwidth == 0) { peer->windowSize = (ENET_MAX(host->outgoingBandwidth, peer->incomingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } else { peer->windowSize = (ENET_MIN(host->outgoingBandwidth, peer->incomingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } if (host->incomingBandwidth == 0) { windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } else { windowSize = (host->incomingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } if (windowSize > ENET_NET_TO_HOST_32(command->connect.windowSize)) { windowSize = ENET_NET_TO_HOST_32(command->connect.windowSize); } if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } else if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } verifyCommand.header.command = ENET_PROTOCOL_COMMAND_VERIFY_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; verifyCommand.header.channelID = 0xFF; verifyCommand.verifyConnect.outgoingPeerID = ENET_HOST_TO_NET_16(peer->incomingPeerID); verifyCommand.verifyConnect.incomingSessionID = incomingSessionID; verifyCommand.verifyConnect.outgoingSessionID = outgoingSessionID; verifyCommand.verifyConnect.mtu = ENET_HOST_TO_NET_32(peer->mtu); verifyCommand.verifyConnect.windowSize = ENET_HOST_TO_NET_32(windowSize); verifyCommand.verifyConnect.channelCount = ENET_HOST_TO_NET_32(channelCount); verifyCommand.verifyConnect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth); verifyCommand.verifyConnect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); verifyCommand.verifyConnect.packetThrottleInterval = ENET_HOST_TO_NET_32(peer->packetThrottleInterval); verifyCommand.verifyConnect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(peer->packetThrottleAcceleration); verifyCommand.verifyConnect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(peer->packetThrottleDeceleration); verifyCommand.verifyConnect.connectID = peer->connectID; enet_peer_queue_outgoing_command(peer, &verifyCommand, NULL, 0, 0); return peer; } /* enet_protocol_handle_connect */ static int enet_protocol_handle_send_reliable(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, enet_uint8** currentData) { size_t dataLength; if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { return -1; } dataLength = ENET_NET_TO_HOST_16(command->sendReliable.dataLength); *currentData += dataLength; if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { return -1; } if (enet_peer_queue_incoming_command(peer, command, (const enet_uint8 *) command + sizeof(ENetProtocolSendReliable), dataLength, ENET_PACKET_FLAG_RELIABLE, 0) == NULL) { return -1; } return 0; } static int enet_protocol_handle_send_unsequenced(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, enet_uint8** currentData) { enet_uint32 unsequencedGroup, index; size_t dataLength; if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { return -1; } dataLength = ENET_NET_TO_HOST_16(command->sendUnsequenced.dataLength); *currentData += dataLength; if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { return -1; } unsequencedGroup = ENET_NET_TO_HOST_16(command->sendUnsequenced.unsequencedGroup); index = unsequencedGroup % ENET_PEER_UNSEQUENCED_WINDOW_SIZE; if (unsequencedGroup < peer->incomingUnsequencedGroup) { unsequencedGroup += 0x10000; } if (unsequencedGroup >= (enet_uint32) peer->incomingUnsequencedGroup + ENET_PEER_FREE_UNSEQUENCED_WINDOWS * ENET_PEER_UNSEQUENCED_WINDOW_SIZE) { return 0; } unsequencedGroup &= 0xFFFF; if (unsequencedGroup - index != peer->incomingUnsequencedGroup) { peer->incomingUnsequencedGroup = unsequencedGroup - index; memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow)); } else if (peer->unsequencedWindow[index / 32] & (1 << (index % 32))) { return 0; } if (enet_peer_queue_incoming_command(peer, command, (const enet_uint8 *) command + sizeof(ENetProtocolSendUnsequenced), dataLength, ENET_PACKET_FLAG_UNSEQUENCED,0) == NULL) { return -1; } peer->unsequencedWindow[index / 32] |= 1 << (index % 32); return 0; } /* enet_protocol_handle_send_unsequenced */ static int enet_protocol_handle_send_unreliable(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, enet_uint8** currentData) { size_t dataLength; if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { return -1; } dataLength = ENET_NET_TO_HOST_16(command->sendUnreliable.dataLength); *currentData += dataLength; if (dataLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { return -1; } if (enet_peer_queue_incoming_command(peer, command, (const enet_uint8 *) command + sizeof(ENetProtocolSendUnreliable), dataLength, 0, 0) == NULL) { return -1; } return 0; } static int enet_protocol_handle_send_fragment(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, enet_uint8** currentData) { enet_uint32 fragmentNumber, fragmentCount, fragmentOffset, fragmentLength, startSequenceNumber, totalLength; ENetChannel* channel; enet_uint16 startWindow, currentWindow; ENetListIterator currentCommand; ENetIncomingCommand* startCommand = NULL; if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { return -1; } fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength); *currentData += fragmentLength; if (fragmentLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { return -1; } channel = &peer->channels[command->header.channelID]; startSequenceNumber = ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber); startWindow = startSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (startSequenceNumber < channel->incomingReliableSequenceNumber) { startWindow += ENET_PEER_RELIABLE_WINDOWS; } if (startWindow < currentWindow || startWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { return 0; } fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber); fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount); fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset); totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength); if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || fragmentNumber >= fragmentCount || totalLength > host->maximumPacketSize || fragmentOffset >= totalLength || fragmentLength > totalLength - fragmentOffset ) { return -1; } for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingReliableCommands)); currentCommand != enet_list_end(&channel->incomingReliableCommands); currentCommand = enet_list_previous(currentCommand) ) { ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; if (startSequenceNumber >= channel->incomingReliableSequenceNumber) { if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { continue; } } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { break; } if (incomingCommand->reliableSequenceNumber <= startSequenceNumber) { if (incomingCommand->reliableSequenceNumber < startSequenceNumber) { break; } if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_FRAGMENT || totalLength != incomingCommand->packet->dataLength || fragmentCount != incomingCommand->fragmentCount ) { return -1; } startCommand = incomingCommand; break; } } if (startCommand == NULL) { ENetProtocol hostCommand = *command; hostCommand.header.reliableSequenceNumber = startSequenceNumber; startCommand = enet_peer_queue_incoming_command(peer, &hostCommand, NULL, totalLength, ENET_PACKET_FLAG_RELIABLE, fragmentCount); if (startCommand == NULL) { return -1; } } if ((startCommand->fragments[fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) { --startCommand->fragmentsRemaining; startCommand->fragments[fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); if (fragmentOffset + fragmentLength > startCommand->packet->dataLength) { fragmentLength = startCommand->packet->dataLength - fragmentOffset; } memcpy(startCommand->packet->data + fragmentOffset, (enet_uint8 *) command + sizeof(ENetProtocolSendFragment), fragmentLength); if (startCommand->fragmentsRemaining <= 0) { enet_peer_dispatch_incoming_reliable_commands(peer, channel); } } return 0; } /* enet_protocol_handle_send_fragment */ static int enet_protocol_handle_send_unreliable_fragment(ENetHost* host, ENetPeer* peer, const ENetProtocol* command, enet_uint8** currentData) { enet_uint32 fragmentNumber, fragmentCount, fragmentOffset, fragmentLength, reliableSequenceNumber, startSequenceNumber, totalLength; enet_uint16 reliableWindow, currentWindow; ENetChannel* channel; ENetListIterator currentCommand; ENetIncomingCommand *startCommand = NULL; if (command->header.channelID >= peer->channelCount || (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER)) { return -1; } fragmentLength = ENET_NET_TO_HOST_16(command->sendFragment.dataLength); *currentData += fragmentLength; if (fragmentLength <= 0 || fragmentLength > host->maximumPacketSize || *currentData < host->receivedData || *currentData > &host->receivedData[host->receivedDataLength]) { return -1; } channel = &peer->channels[command->header.channelID]; reliableSequenceNumber = command->header.reliableSequenceNumber; startSequenceNumber = ENET_NET_TO_HOST_16(command->sendFragment.startSequenceNumber); reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (reliableSequenceNumber < channel->incomingReliableSequenceNumber) { reliableWindow += ENET_PEER_RELIABLE_WINDOWS; } if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { return 0; } if (reliableSequenceNumber == channel->incomingReliableSequenceNumber && startSequenceNumber <= channel->incomingUnreliableSequenceNumber) { return 0; } fragmentNumber = ENET_NET_TO_HOST_32(command->sendFragment.fragmentNumber); fragmentCount = ENET_NET_TO_HOST_32(command->sendFragment.fragmentCount); fragmentOffset = ENET_NET_TO_HOST_32(command->sendFragment.fragmentOffset); totalLength = ENET_NET_TO_HOST_32(command->sendFragment.totalLength); if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT || fragmentNumber >= fragmentCount || totalLength > host->maximumPacketSize || totalLength < fragmentCount || fragmentOffset >= totalLength || fragmentLength > totalLength - fragmentOffset ) { return -1; } for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingUnreliableCommands)); currentCommand != enet_list_end(&channel->incomingUnreliableCommands); currentCommand = enet_list_previous(currentCommand) ) { ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { continue; } } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { break; } if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) { break; } if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber) { continue; } if (incomingCommand->unreliableSequenceNumber <= startSequenceNumber) { if (incomingCommand->unreliableSequenceNumber < startSequenceNumber) { break; } if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT || totalLength != incomingCommand->packet->dataLength || fragmentCount != incomingCommand->fragmentCount ) { return -1; } startCommand = incomingCommand; break; } } if (startCommand == NULL) { startCommand = enet_peer_queue_incoming_command(peer, command, NULL, totalLength, ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT, fragmentCount); if (startCommand == NULL) { return -1; } } if ((startCommand->fragments[fragmentNumber / 32] & (1 << (fragmentNumber % 32))) == 0) { --startCommand->fragmentsRemaining; startCommand->fragments[fragmentNumber / 32] |= (1 << (fragmentNumber % 32)); if (fragmentOffset + fragmentLength > startCommand->packet->dataLength) { fragmentLength = startCommand->packet->dataLength - fragmentOffset; } memcpy(startCommand->packet->data + fragmentOffset, (enet_uint8 *) command + sizeof(ENetProtocolSendFragment), fragmentLength); if (startCommand->fragmentsRemaining <= 0) { enet_peer_dispatch_incoming_unreliable_commands(peer, channel); } } return 0; } /* enet_protocol_handle_send_unreliable_fragment */ static int enet_protocol_handle_ping(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { ENET_UNUSED(host) ENET_UNUSED(command) if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { return -1; } return 0; } static int enet_protocol_handle_bandwidth_limit(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { return -1; } if (peer->incomingBandwidth != 0) { --host->bandwidthLimitedPeers; } peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->bandwidthLimit.incomingBandwidth); if (peer->incomingBandwidth != 0) { ++host->bandwidthLimitedPeers; } peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->bandwidthLimit.outgoingBandwidth); if (peer->incomingBandwidth == 0 && host->outgoingBandwidth == 0) { peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } else if (peer->incomingBandwidth == 0 || host->outgoingBandwidth == 0) { peer->windowSize = (ENET_MAX(peer->incomingBandwidth, host->outgoingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } else { peer->windowSize = (ENET_MIN(peer->incomingBandwidth, host->outgoingBandwidth) / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } if (peer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { peer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } else if (peer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } return 0; } /* enet_protocol_handle_bandwidth_limit */ static int enet_protocol_handle_throttle_configure(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { ENET_UNUSED(host) if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { return -1; } peer->packetThrottleInterval = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleInterval); peer->packetThrottleAcceleration = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleAcceleration); peer->packetThrottleDeceleration = ENET_NET_TO_HOST_32(command->throttleConfigure.packetThrottleDeceleration); return 0; } static int enet_protocol_handle_disconnect(ENetHost* host, ENetPeer* peer, const ENetProtocol* command) { if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE || peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT ) { return 0; } enet_peer_reset_queues(peer); if (peer->state == ENET_PEER_STATE_CONNECTION_SUCCEEDED || peer->state == ENET_PEER_STATE_DISCONNECTING || peer->state == ENET_PEER_STATE_CONNECTING) { enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); } else if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { if (peer->state == ENET_PEER_STATE_CONNECTION_PENDING) { host->recalculateBandwidthLimits = 1; } enet_peer_reset(peer); } else if (command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { enet_protocol_change_state(host, peer, ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT); } else { enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); } if (peer->state != ENET_PEER_STATE_DISCONNECTED) { peer->eventData = ENET_NET_TO_HOST_32(command->disconnect.data); } return 0; } static int enet_protocol_handle_acknowledge(ENetHost* host, ENetEvent* event, ENetPeer* peer, const ENetProtocol* command) { enet_uint32 roundTripTime, receivedSentTime, receivedReliableSequenceNumber; ENetProtocolCommand commandNumber; if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE) { return 0; } receivedSentTime = ENET_NET_TO_HOST_16(command->acknowledge.receivedSentTime); receivedSentTime |= host->serviceTime & 0xFFFF0000; if ((receivedSentTime & 0x8000) > (host->serviceTime & 0x8000)) { receivedSentTime -= 0x10000; } if (ENET_TIME_LESS(host->serviceTime, receivedSentTime)) { return 0; } peer->lastReceiveTime = host->serviceTime; peer->earliestTimeout = 0; roundTripTime = ENET_TIME_DIFFERENCE(host->serviceTime, receivedSentTime); enet_peer_throttle(peer, roundTripTime); peer->roundTripTimeVariance -= peer->roundTripTimeVariance / 4; if (roundTripTime >= peer->roundTripTime) { peer->roundTripTime += (roundTripTime - peer->roundTripTime) / 8; peer->roundTripTimeVariance += (roundTripTime - peer->roundTripTime) / 4; } else { peer->roundTripTime -= (peer->roundTripTime - roundTripTime) / 8; peer->roundTripTimeVariance += (peer->roundTripTime - roundTripTime) / 4; } if (peer->roundTripTime < peer->lowestRoundTripTime) { peer->lowestRoundTripTime = peer->roundTripTime; } if (peer->roundTripTimeVariance > peer->highestRoundTripTimeVariance) { peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance; } if (peer->packetThrottleEpoch == 0 || ENET_TIME_DIFFERENCE(host->serviceTime, peer->packetThrottleEpoch) >= peer->packetThrottleInterval ) { peer->lastRoundTripTime = peer->lowestRoundTripTime; peer->lastRoundTripTimeVariance = peer->highestRoundTripTimeVariance; peer->lowestRoundTripTime = peer->roundTripTime; peer->highestRoundTripTimeVariance = peer->roundTripTimeVariance; peer->packetThrottleEpoch = host->serviceTime; } receivedReliableSequenceNumber = ENET_NET_TO_HOST_16(command->acknowledge.receivedReliableSequenceNumber); commandNumber = enet_protocol_remove_sent_reliable_command(peer, receivedReliableSequenceNumber, command->header.channelID); switch (peer->state) { case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: if (commandNumber != ENET_PROTOCOL_COMMAND_VERIFY_CONNECT) { return -1; } enet_protocol_notify_connect(host, peer, event); break; case ENET_PEER_STATE_DISCONNECTING: if (commandNumber != ENET_PROTOCOL_COMMAND_DISCONNECT) { return -1; } enet_protocol_notify_disconnect(host, peer, event); break; case ENET_PEER_STATE_DISCONNECT_LATER: if (enet_list_empty(&peer->outgoingReliableCommands) && enet_list_empty(&peer->outgoingUnreliableCommands) && enet_list_empty(&peer->sentReliableCommands)) { enet_peer_disconnect(peer, peer->eventData); } break; default: break; } return 0; } /* enet_protocol_handle_acknowledge */ static int enet_protocol_handle_verify_connect(ENetHost* host, ENetEvent* event, ENetPeer* peer, const ENetProtocol* command) { enet_uint32 mtu, windowSize; size_t channelCount; if (peer->state != ENET_PEER_STATE_CONNECTING) { return 0; } channelCount = ENET_NET_TO_HOST_32(command->verifyConnect.channelCount); if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT || channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT || ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleInterval) != peer->packetThrottleInterval || ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleAcceleration) != peer->packetThrottleAcceleration || ENET_NET_TO_HOST_32(command->verifyConnect.packetThrottleDeceleration) != peer->packetThrottleDeceleration || command->verifyConnect.connectID != peer->connectID ) { peer->eventData = 0; enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); return -1; } enet_protocol_remove_sent_reliable_command(peer, 1, 0xFF); if (channelCount < peer->channelCount) { peer->channelCount = channelCount; } peer->outgoingPeerID = ENET_NET_TO_HOST_16(command->verifyConnect.outgoingPeerID); peer->incomingSessionID = command->verifyConnect.incomingSessionID; peer->outgoingSessionID = command->verifyConnect.outgoingSessionID; mtu = ENET_NET_TO_HOST_32(command->verifyConnect.mtu); if (mtu < ENET_PROTOCOL_MINIMUM_MTU) { mtu = ENET_PROTOCOL_MINIMUM_MTU; } else if (mtu > ENET_PROTOCOL_MAXIMUM_MTU) { mtu = ENET_PROTOCOL_MAXIMUM_MTU; } if (mtu < peer->mtu) { peer->mtu = mtu; } windowSize = ENET_NET_TO_HOST_32(command->verifyConnect.windowSize); if (windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } if (windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } if (windowSize < peer->windowSize) { peer->windowSize = windowSize; } peer->incomingBandwidth = ENET_NET_TO_HOST_32(command->verifyConnect.incomingBandwidth); peer->outgoingBandwidth = ENET_NET_TO_HOST_32(command->verifyConnect.outgoingBandwidth); enet_protocol_notify_connect(host, peer, event); return 0; } /* enet_protocol_handle_verify_connect */ static int enet_protocol_handle_incoming_commands(ENetHost* host, ENetEvent* event) { ENetProtocolHeader* header; ENetProtocol* command; ENetPeer* peer; enet_uint8* currentData; size_t headerSize; enet_uint16 peerID, flags; enet_uint8 sessionID; if (host->receivedDataLength < (size_t) &((ENetProtocolHeader *) 0)->sentTime) { return 0; } header = (ENetProtocolHeader *) host->receivedData; peerID = ENET_NET_TO_HOST_16(header->peerID); sessionID = (peerID & ENET_PROTOCOL_HEADER_SESSION_MASK) >> ENET_PROTOCOL_HEADER_SESSION_SHIFT; flags = peerID & ENET_PROTOCOL_HEADER_FLAG_MASK; peerID &= ~(ENET_PROTOCOL_HEADER_FLAG_MASK | ENET_PROTOCOL_HEADER_SESSION_MASK); headerSize = (flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME ? sizeof(ENetProtocolHeader) : (size_t) &((ENetProtocolHeader *) 0)->sentTime); if (host->checksum != NULL) { headerSize += sizeof(enet_uint32); } if (peerID == ENET_PROTOCOL_MAXIMUM_PEER_ID) { peer = NULL; } else if (peerID >= host->peerCount) { return 0; } else { peer = &host->peers[peerID]; if (peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ZOMBIE || ((!enet_host_equal(host->receivedAddress.host , peer->address.host) || host->receivedAddress.port != peer->address.port) && #ifdef ENET_IPV6 1 /* no broadcast in ipv6 !enet_host_equal(peer->address.host, ENET_HOST_BROADCAST)*/ #else !enet_host_equal(peer->address.host, ENET_HOST_BROADCAST) #endif ) || (peer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID && sessionID != peer->incomingSessionID) ) { return 0; } } if (flags & ENET_PROTOCOL_HEADER_FLAG_COMPRESSED) { size_t originalSize; if (host->compressor.context == NULL || host->compressor.decompress == NULL) { return 0; } originalSize = host->compressor.decompress(host->compressor.context, host->receivedData + headerSize, host->receivedDataLength - headerSize, host->packetData[1] + headerSize, sizeof(host->packetData[1]) - headerSize ); if (originalSize <= 0 || originalSize > sizeof(host->packetData[1]) - headerSize) { return 0; } memcpy(host->packetData[1], header, headerSize); host->receivedData = host->packetData[1]; host->receivedDataLength = headerSize + originalSize; } if (host->checksum != NULL) { enet_uint32 *checksum = (enet_uint32 *) &host->receivedData[headerSize - sizeof(enet_uint32)]; enet_uint32 desiredChecksum = *checksum; ENetBuffer buffer; *checksum = peer != NULL ? peer->connectID : 0; buffer.data = host->receivedData; buffer.dataLength = host->receivedDataLength; if (host->checksum(&buffer, 1) != desiredChecksum) { return 0; } } if (peer != NULL) { peer->address.host = host->receivedAddress.host; peer->address.port = host->receivedAddress.port; peer->incomingDataTotal += host->receivedDataLength; peer->totalDataReceived += host->receivedDataLength; } currentData = host->receivedData + headerSize; while (currentData < &host->receivedData[host->receivedDataLength]) { enet_uint8 commandNumber; size_t commandSize; command = (ENetProtocol *) currentData; if (currentData + sizeof(ENetProtocolCommandHeader) > &host->receivedData[host->receivedDataLength]) { break; } commandNumber = command->header.command & ENET_PROTOCOL_COMMAND_MASK; if (commandNumber >= ENET_PROTOCOL_COMMAND_COUNT) { break; } commandSize = commandSizes[commandNumber]; if (commandSize == 0 || currentData + commandSize > &host->receivedData[host->receivedDataLength]) { break; } currentData += commandSize; if (peer == NULL && (commandNumber != ENET_PROTOCOL_COMMAND_CONNECT || currentData < &host->receivedData[host->receivedDataLength])) { break; } command->header.reliableSequenceNumber = ENET_NET_TO_HOST_16(command->header.reliableSequenceNumber); switch (commandNumber) { case ENET_PROTOCOL_COMMAND_ACKNOWLEDGE: if (enet_protocol_handle_acknowledge(host, event, peer, command)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_CONNECT: if (peer != NULL) { goto commandError; } peer = enet_protocol_handle_connect(host, header, command); if (peer == NULL) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_VERIFY_CONNECT: if (enet_protocol_handle_verify_connect(host, event, peer, command)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_DISCONNECT: if (enet_protocol_handle_disconnect(host, peer, command)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_PING: if (enet_protocol_handle_ping(host, peer, command)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: if (enet_protocol_handle_send_reliable(host, peer, command, ¤tData)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: if (enet_protocol_handle_send_unreliable(host, peer, command, ¤tData)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: if (enet_protocol_handle_send_unsequenced(host, peer, command, ¤tData)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: if (enet_protocol_handle_send_fragment(host, peer, command, ¤tData)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT: if (enet_protocol_handle_bandwidth_limit(host, peer, command)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE: if (enet_protocol_handle_throttle_configure(host, peer, command)) { goto commandError; } break; case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: if (enet_protocol_handle_send_unreliable_fragment(host, peer, command, ¤tData)) { goto commandError; } break; default: goto commandError; } assert(peer); if ((command->header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0) { enet_uint16 sentTime; if (!(flags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME)) { break; } sentTime = ENET_NET_TO_HOST_16(header->sentTime); switch (peer->state) { case ENET_PEER_STATE_DISCONNECTING: case ENET_PEER_STATE_ACKNOWLEDGING_CONNECT: case ENET_PEER_STATE_DISCONNECTED: case ENET_PEER_STATE_ZOMBIE: break; case ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT: if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) { enet_peer_queue_acknowledgement(peer, command, sentTime); } break; default: enet_peer_queue_acknowledgement(peer, command, sentTime); break; } } } commandError: if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) { return 1; } return 0; } /* enet_protocol_handle_incoming_commands */ static int enet_protocol_receive_incoming_commands(ENetHost* host, ENetEvent* event) { int packets; for (packets = 0; packets < 256; ++packets) { int receivedLength; ENetBuffer buffer; buffer.data = host->packetData[0]; // buffer.dataLength = sizeof (host->packetData[0]); buffer.dataLength = host->mtu; receivedLength = enet_socket_receive(host->socket, &host->receivedAddress, &buffer, 1); if (receivedLength == -2) continue; if (receivedLength < 0) { return -1; } if (receivedLength == 0) { return 0; } host->receivedData = host->packetData[0]; host->receivedDataLength = receivedLength; host->totalReceivedData += receivedLength; host->totalReceivedPackets++; if (host->intercept != NULL) { switch (host->intercept(host, (void *)event)) { case 1: if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) { return 1; } continue; case -1: return -1; default: break; } } switch (enet_protocol_handle_incoming_commands(host, event)) { case 1: return 1; case -1: return -1; default: break; } } return -1; } /* enet_protocol_receive_incoming_commands */ static void enet_protocol_send_acknowledgements(ENetHost* host, ENetPeer* peer) { ENetProtocol* command = &host->commands[host->commandCount]; ENetBuffer* buffer = &host->buffers[host->bufferCount]; ENetAcknowledgement* acknowledgement; ENetListIterator currentAcknowledgement; enet_uint16 reliableSequenceNumber; currentAcknowledgement = enet_list_begin(&peer->acknowledgements); while (currentAcknowledgement != enet_list_end(&peer->acknowledgements)) { if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || buffer >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || peer->mtu - host->packetSize < sizeof(ENetProtocolAcknowledge) ) { host->continueSending = 1; break; } acknowledgement = (ENetAcknowledgement *) currentAcknowledgement; currentAcknowledgement = enet_list_next(currentAcknowledgement); buffer->data = command; buffer->dataLength = sizeof(ENetProtocolAcknowledge); host->packetSize += buffer->dataLength; reliableSequenceNumber = ENET_HOST_TO_NET_16(acknowledgement->command.header.reliableSequenceNumber); command->header.command = ENET_PROTOCOL_COMMAND_ACKNOWLEDGE; command->header.channelID = acknowledgement->command.header.channelID; command->header.reliableSequenceNumber = reliableSequenceNumber; command->acknowledge.receivedReliableSequenceNumber = reliableSequenceNumber; command->acknowledge.receivedSentTime = ENET_HOST_TO_NET_16(acknowledgement->sentTime); if ((acknowledgement->command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_DISCONNECT) { enet_protocol_dispatch_state(host, peer, ENET_PEER_STATE_ZOMBIE); } enet_list_remove(&acknowledgement->acknowledgementList); enet_free(acknowledgement); ++command; ++buffer; } host->commandCount = command - host->commands; host->bufferCount = buffer - host->buffers; } /* enet_protocol_send_acknowledgements */ static void enet_protocol_send_unreliable_outgoing_commands(ENetHost* host, ENetPeer* peer) { ENetProtocol* command = &host->commands[host->commandCount]; ENetBuffer* buffer = &host->buffers[host->bufferCount]; ENetOutgoingCommand* outgoingCommand; ENetListIterator currentCommand; currentCommand = enet_list_begin(&peer->outgoingUnreliableCommands); while (currentCommand != enet_list_end(&peer->outgoingUnreliableCommands)) { size_t commandSize; outgoingCommand = (ENetOutgoingCommand *) currentCommand; commandSize = commandSizes[outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK]; if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || buffer + 1 >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || peer->mtu - host->packetSize < commandSize || (outgoingCommand->packet != NULL && peer->mtu - host->packetSize < commandSize + outgoingCommand->fragmentLength) ) { host->continueSending = 1; break; } currentCommand = enet_list_next(currentCommand); if (outgoingCommand->packet != NULL && outgoingCommand->fragmentOffset == 0) { peer->packetThrottleCounter += ENET_PEER_PACKET_THROTTLE_COUNTER; peer->packetThrottleCounter %= ENET_PEER_PACKET_THROTTLE_SCALE; if (peer->packetThrottleCounter > peer->packetThrottle) { enet_uint16 reliableSequenceNumber = outgoingCommand->reliableSequenceNumber; enet_uint16 unreliableSequenceNumber = outgoingCommand->unreliableSequenceNumber; for (;;) { --outgoingCommand->packet->referenceCount; if (outgoingCommand->packet->referenceCount == 0) { callbacks.packet_destroy(outgoingCommand->packet); } enet_list_remove(&outgoingCommand->outgoingCommandList); enet_free(outgoingCommand); if (currentCommand == enet_list_end(&peer->outgoingUnreliableCommands)) { break; } outgoingCommand = (ENetOutgoingCommand *) currentCommand; if (outgoingCommand->reliableSequenceNumber != reliableSequenceNumber || outgoingCommand->unreliableSequenceNumber != unreliableSequenceNumber) { break; } currentCommand = enet_list_next(currentCommand); } continue; } } buffer->data = command; buffer->dataLength = commandSize; host->packetSize += buffer->dataLength; *command = outgoingCommand->command; enet_list_remove(&outgoingCommand->outgoingCommandList); if (outgoingCommand->packet != NULL) { ++buffer; buffer->data = outgoingCommand->packet->data + outgoingCommand->fragmentOffset; buffer->dataLength = outgoingCommand->fragmentLength; host->packetSize += buffer->dataLength; enet_list_insert(enet_list_end(&peer->sentUnreliableCommands), outgoingCommand); } else { enet_free(outgoingCommand); } ++command; ++buffer; } host->commandCount = command - host->commands; host->bufferCount = buffer - host->buffers; if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER && enet_list_empty(&peer->outgoingReliableCommands) && enet_list_empty(&peer->outgoingUnreliableCommands) && enet_list_empty(&peer->sentReliableCommands)) { enet_peer_disconnect(peer, peer->eventData); } } /* enet_protocol_send_unreliable_outgoing_commands */ static int enet_protocol_check_timeouts(ENetHost* host, ENetPeer* peer, ENetEvent* event) { ENetOutgoingCommand* outgoingCommand; ENetListIterator currentCommand, insertPosition; currentCommand = enet_list_begin(&peer->sentReliableCommands); insertPosition = enet_list_begin(&peer->outgoingReliableCommands); while (currentCommand != enet_list_end(&peer->sentReliableCommands)) { outgoingCommand = (ENetOutgoingCommand *) currentCommand; currentCommand = enet_list_next(currentCommand); if (ENET_TIME_DIFFERENCE(host->serviceTime, outgoingCommand->sentTime) < outgoingCommand->roundTripTimeout) { continue; } if (peer->earliestTimeout == 0 || ENET_TIME_LESS(outgoingCommand->sentTime, peer->earliestTimeout)) { peer->earliestTimeout = outgoingCommand->sentTime; } if (peer->earliestTimeout != 0 && (ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >= peer->timeoutMaximum || (outgoingCommand->roundTripTimeout >= outgoingCommand->roundTripTimeoutLimit && ENET_TIME_DIFFERENCE(host->serviceTime, peer->earliestTimeout) >= peer->timeoutMinimum)) ) { enet_protocol_notify_disconnect_timeout(host, peer, event); return 1; } if (outgoingCommand->packet != NULL) { peer->reliableDataInTransit -= outgoingCommand->fragmentLength; } ++peer->packetsLost; ++peer->totalPacketsLost; /* Replaced exponential backoff time with something more linear */ /* Source: http://lists.cubik.org/pipermail/enet-discuss/2014-May/002308.html */ outgoingCommand->roundTripTimeout = peer->roundTripTime + 4 * peer->roundTripTimeVariance; outgoingCommand->roundTripTimeoutLimit = peer->timeoutLimit * outgoingCommand->roundTripTimeout; enet_list_insert(insertPosition, enet_list_remove(&outgoingCommand->outgoingCommandList)); if (currentCommand == enet_list_begin(&peer->sentReliableCommands) && !enet_list_empty(&peer->sentReliableCommands)) { outgoingCommand = (ENetOutgoingCommand *) currentCommand; peer->nextTimeout = outgoingCommand->sentTime + outgoingCommand->roundTripTimeout; } } return 0; } /* enet_protocol_check_timeouts */ static int enet_protocol_send_reliable_outgoing_commands(ENetHost* host, ENetPeer* peer) { ENetProtocol* command = &host->commands[host->commandCount]; ENetBuffer* buffer = &host->buffers[host->bufferCount]; ENetOutgoingCommand* outgoingCommand; ENetListIterator currentCommand; ENetChannel* channel; enet_uint16 reliableWindow; size_t commandSize; int windowExceeded = 0, windowWrap = 0, canPing = 1; currentCommand = enet_list_begin(&peer->outgoingReliableCommands); while (currentCommand != enet_list_end(&peer->outgoingReliableCommands)) { outgoingCommand = (ENetOutgoingCommand *) currentCommand; channel = outgoingCommand->command.header.channelID < peer->channelCount ? &peer->channels[outgoingCommand->command.header.channelID] : NULL; reliableWindow = outgoingCommand->reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (channel != NULL) { if (!windowWrap && outgoingCommand->sendAttempts < 1 && !(outgoingCommand->reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) && (channel->reliableWindows[(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE || channel->usedReliableWindows & ((((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) << reliableWindow) | (((1 << ENET_PEER_FREE_RELIABLE_WINDOWS) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow)))) ) { windowWrap = 1; } if (windowWrap) { currentCommand = enet_list_next(currentCommand); continue; } } if (outgoingCommand->packet != NULL) { if (!windowExceeded) { enet_uint32 windowSize = (peer->packetThrottle * peer->windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE; if (peer->reliableDataInTransit + outgoingCommand->fragmentLength > ENET_MAX(windowSize, peer->mtu)) { windowExceeded = 1; } } if (windowExceeded) { currentCommand = enet_list_next(currentCommand); continue; } } canPing = 0; commandSize = commandSizes[outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK]; if (command >= &host->commands[sizeof(host->commands) / sizeof(ENetProtocol)] || buffer + 1 >= &host->buffers[sizeof(host->buffers) / sizeof(ENetBuffer)] || peer->mtu - host->packetSize < commandSize || (outgoingCommand->packet != NULL && (enet_uint16) (peer->mtu - host->packetSize) < (enet_uint16) (commandSize + outgoingCommand->fragmentLength)) ) { host->continueSending = 1; break; } currentCommand = enet_list_next(currentCommand); if (channel != NULL && outgoingCommand->sendAttempts < 1) { channel->usedReliableWindows |= 1 << reliableWindow; ++channel->reliableWindows[reliableWindow]; } ++outgoingCommand->sendAttempts; if (outgoingCommand->roundTripTimeout == 0) { outgoingCommand->roundTripTimeout = peer->roundTripTime + 4 * peer->roundTripTimeVariance; outgoingCommand->roundTripTimeoutLimit = peer->timeoutLimit * outgoingCommand->roundTripTimeout; } if (enet_list_empty(&peer->sentReliableCommands)) { peer->nextTimeout = host->serviceTime + outgoingCommand->roundTripTimeout; } enet_list_insert(enet_list_end(&peer->sentReliableCommands), enet_list_remove(&outgoingCommand->outgoingCommandList)); outgoingCommand->sentTime = host->serviceTime; buffer->data = command; buffer->dataLength = commandSize; host->packetSize += buffer->dataLength; host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_SENT_TIME; *command = outgoingCommand->command; if (outgoingCommand->packet != NULL) { ++buffer; buffer->data = outgoingCommand->packet->data + outgoingCommand->fragmentOffset; buffer->dataLength = outgoingCommand->fragmentLength; host->packetSize += outgoingCommand->fragmentLength; peer->reliableDataInTransit += outgoingCommand->fragmentLength; } ++peer->packetsSent; ++peer->totalPacketsSent; ++command; ++buffer; } host->commandCount = command - host->commands; host->bufferCount = buffer - host->buffers; return canPing; } /* enet_protocol_send_reliable_outgoing_commands */ static int enet_protocol_send_outgoing_commands(ENetHost* host, ENetEvent* event, int checkForTimeouts) { enet_uint8 headerData[sizeof(ENetProtocolHeader) + sizeof(enet_uint32)]; ENetProtocolHeader* header = (ENetProtocolHeader *) headerData; ENetPeer* currentPeer; int sentLength; size_t shouldCompress = 0; host->continueSending = 1; while (host->continueSending) for (host->continueSending = 0, currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED || currentPeer->state == ENET_PEER_STATE_ZOMBIE) { continue; } host->headerFlags = 0; host->commandCount = 0; host->bufferCount = 1; host->packetSize = sizeof(ENetProtocolHeader); if (!enet_list_empty(¤tPeer->acknowledgements)) { enet_protocol_send_acknowledgements(host, currentPeer); } if (checkForTimeouts != 0 && !enet_list_empty(¤tPeer->sentReliableCommands) && ENET_TIME_GREATER_EQUAL(host->serviceTime, currentPeer->nextTimeout) && enet_protocol_check_timeouts(host, currentPeer, event) == 1 ) { if (event != NULL && event->type != ENET_EVENT_TYPE_NONE) { return 1; } else { continue; } } if ((enet_list_empty(¤tPeer->outgoingReliableCommands) || enet_protocol_send_reliable_outgoing_commands(host, currentPeer)) && enet_list_empty(¤tPeer->sentReliableCommands) && ENET_TIME_DIFFERENCE(host->serviceTime, currentPeer->lastReceiveTime) >= currentPeer->pingInterval && currentPeer->mtu - host->packetSize >= sizeof(ENetProtocolPing) ) { enet_peer_ping(currentPeer); enet_protocol_send_reliable_outgoing_commands(host, currentPeer); } if (!enet_list_empty(¤tPeer->outgoingUnreliableCommands)) { enet_protocol_send_unreliable_outgoing_commands(host, currentPeer); } if (host->commandCount == 0) { continue; } if (currentPeer->packetLossEpoch == 0) { currentPeer->packetLossEpoch = host->serviceTime; } else if (ENET_TIME_DIFFERENCE(host->serviceTime, currentPeer->packetLossEpoch) >= ENET_PEER_PACKET_LOSS_INTERVAL && currentPeer->packetsSent > 0) { enet_uint32 packetLoss = currentPeer->packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer->packetsSent; #ifdef ENET_DEBUG LOGD( "Peer {}: {:.1f}±{:.1f}% packet loss, {}±{} ms round trip time, {}% throttle, {}/{} outgoing, {}/{} incoming", currentPeer->incomingPeerID, currentPeer->packetLoss / (float)ENET_PEER_PACKET_LOSS_SCALE, currentPeer->packetLossVariance / (float)ENET_PEER_PACKET_LOSS_SCALE, currentPeer->roundTripTime, currentPeer->roundTripTimeVariance, currentPeer->packetThrottle / (float)ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size(¤tPeer->outgoingReliableCommands), enet_list_size(¤tPeer->outgoingUnreliableCommands), currentPeer->channels != NULL ? enet_list_size( ¤tPeer->channels->incomingReliableCommands) : 0, currentPeer->channels != NULL ? enet_list_size(¤tPeer->channels->incomingUnreliableCommands) : 0 ); #endif currentPeer->packetLossVariance -= currentPeer->packetLossVariance / 4; if (packetLoss >= currentPeer->packetLoss) { currentPeer->packetLoss += (packetLoss - currentPeer->packetLoss) / 8; currentPeer->packetLossVariance += (packetLoss - currentPeer->packetLoss) / 4; } else { currentPeer->packetLoss -= (currentPeer->packetLoss - packetLoss) / 8; currentPeer->packetLossVariance += (currentPeer->packetLoss - packetLoss) / 4; } currentPeer->packetLossEpoch = host->serviceTime; currentPeer->packetsSent = 0; currentPeer->packetsLost = 0; } host->buffers[0].data = headerData; if (host->headerFlags & ENET_PROTOCOL_HEADER_FLAG_SENT_TIME) { header->sentTime = ENET_HOST_TO_NET_16(host->serviceTime & 0xFFFF); host->buffers[0].dataLength = sizeof(ENetProtocolHeader); } else { host->buffers[0].dataLength = (size_t) &((ENetProtocolHeader *) 0)->sentTime; } shouldCompress = 0; if (host->compressor.context != NULL && host->compressor.compress != NULL) { size_t originalSize = host->packetSize - sizeof(ENetProtocolHeader), compressedSize = host->compressor.compress(host->compressor.context, &host->buffers[1], host->bufferCount - 1, originalSize, host->packetData[1], originalSize); if (compressedSize > 0 && compressedSize < originalSize) { host->headerFlags |= ENET_PROTOCOL_HEADER_FLAG_COMPRESSED; shouldCompress = compressedSize; #ifdef ENET_DEBUG_COMPRESS LOGD("peer {}: compressed {}->{} ({}%)", currentPeer->incomingPeerID, originalSize, compressedSize, (compressedSize * 100) / originalSize); #endif } } if (currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID) { host->headerFlags |= currentPeer->outgoingSessionID << ENET_PROTOCOL_HEADER_SESSION_SHIFT; } header->peerID = ENET_HOST_TO_NET_16(currentPeer->outgoingPeerID | host->headerFlags); if (host->checksum != NULL) { enet_uint32 *checksum = (enet_uint32 *) &headerData[host->buffers[0].dataLength]; *checksum = currentPeer->outgoingPeerID < ENET_PROTOCOL_MAXIMUM_PEER_ID ? currentPeer->connectID : 0; host->buffers[0].dataLength += sizeof(enet_uint32); *checksum = host->checksum(host->buffers, host->bufferCount); } if (shouldCompress > 0) { host->buffers[1].data = host->packetData[1]; host->buffers[1].dataLength = shouldCompress; host->bufferCount = 2; } currentPeer->lastSendTime = host->serviceTime; sentLength = enet_socket_send(host->socket, ¤tPeer->address, host->buffers, host->bufferCount); enet_protocol_remove_sent_unreliable_commands(currentPeer); if (sentLength < 0) { // The local 'headerData' array (to which 'data' is assigned) goes out // of scope on return from this function, so ensure we no longer point to it. host->buffers[0].data = NULL; return -1; } host->totalSentData += sentLength; currentPeer->totalDataSent += sentLength; host->totalSentPackets++; } // The local 'headerData' array (to which 'data' is assigned) goes out // of scope on return from this function, so ensure we no longer point to it. host->buffers[0].data = NULL; return 0; } /* enet_protocol_send_outgoing_commands */ /** Sends any queued packets on the host specified to its designated peers. * * @param host host to flush * @remarks this function need only be used in circumstances where one wishes to send queued packets earlier than in a call to enet_host_service(). * @ingroup host */ void enet_host_flush(ENetHost* host) { host->serviceTime = enet_time_get(); enet_protocol_send_outgoing_commands(host, NULL, 0); } /** Checks for any queued events on the host and dispatches one if available. * * @param host host to check for events * @param event an event structure where event details will be placed if available * @retval > 0 if an event was dispatched * @retval 0 if no events are available * @retval < 0 on failure * @ingroup host */ int enet_host_check_events(ENetHost* host, ENetEvent* event) { if (event == NULL) { return -1; } event->type = ENET_EVENT_TYPE_NONE; event->peer = NULL; event->packet = NULL; return enet_protocol_dispatch_incoming_commands(host, event); } /** Waits for events on the host specified and shuttles packets between * the host and its peers. * * @param host host to service * @param event an event structure where event details will be placed if one occurs * if event == NULL then no events will be delivered * @param timeout number of milliseconds that ENet should wait for events * @retval > 0 if an event occurred within the specified time limit * @retval 0 if no event occurred * @retval < 0 on failure * @remarks enet_host_service should be called fairly regularly for adequate performance * @ingroup host */ int enet_host_service(ENetHost* host, ENetEvent* event, enet_uint32 timeout) { enet_uint32 waitCondition; if (event != NULL) { event->type = ENET_EVENT_TYPE_NONE; event->peer = NULL; event->packet = NULL; switch (enet_protocol_dispatch_incoming_commands(host, event)) { case 1: return 1; case -1: #ifdef ENET_DEBUG LOGE("Error dispatching incoming packets"); #endif return -1; default: break; } } host->serviceTime = enet_time_get(); timeout += host->serviceTime; do { if (ENET_TIME_DIFFERENCE(host->serviceTime, host->bandwidthThrottleEpoch) >= ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) { enet_host_bandwidth_throttle(host); } switch (enet_protocol_send_outgoing_commands(host, event, 1)) { case 1: return 1; case -1: #ifdef ENET_DEBUG LOGE("Error sending outgoing packets"); #endif return -1; default: break; } switch (enet_protocol_receive_incoming_commands(host, event)) { case 1: return 1; case -1: #ifdef ENET_DEBUG LOGE("Error receiving incoming packets"); #endif return -1; default: break; } switch (enet_protocol_send_outgoing_commands(host, event, 1)) { case 1: return 1; case -1: #ifdef ENET_DEBUG LOGE("Error sending outgoing packets"); #endif return -1; default: break; } if (event != NULL) { switch (enet_protocol_dispatch_incoming_commands(host, event)) { case 1: return 1; case -1: #ifdef ENET_DEBUG LOGE("Error dispatching incoming packets"); #endif return -1; default: break; } } if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout)) { return 0; } do { host->serviceTime = enet_time_get(); if (ENET_TIME_GREATER_EQUAL(host->serviceTime, timeout)) { return 0; } waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_INTERRUPT; if (enet_socket_wait(host->socket, &waitCondition, ENET_TIME_DIFFERENCE(timeout, host->serviceTime)) != 0) { return -1; } } while (waitCondition & ENET_SOCKET_WAIT_INTERRUPT); host->serviceTime = enet_time_get(); } while (waitCondition & ENET_SOCKET_WAIT_RECEIVE); return 0; } /* enet_host_service */ // =======================================================================// // ! // ! Peer // ! // =======================================================================// /** Configures throttle parameter for a peer. * * Unreliable packets are dropped by ENet in response to the varying conditions * of the Internet connection to the peer. The throttle represents a probability * that an unreliable packet should not be dropped and thus sent by ENet to the peer. * The lowest mean round trip time from the sending of a reliable packet to the * receipt of its acknowledgement is measured over an amount of time specified by * the interval parameter in milliseconds. If a measured round trip time happens to * be significantly less than the mean round trip time measured over the interval, * then the throttle probability is increased to allow more traffic by an amount * specified in the acceleration parameter, which is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE * constant. If a measured round trip time happens to be significantly greater than * the mean round trip time measured over the interval, then the throttle probability * is decreased to limit traffic by an amount specified in the deceleration parameter, which * is a ratio to the ENET_PEER_PACKET_THROTTLE_SCALE constant. When the throttle has * a value of ENET_PEER_PACKET_THROTTLE_SCALE, no unreliable packets are dropped by * ENet, and so 100% of all unreliable packets will be sent. When the throttle has a * value of 0, all unreliable packets are dropped by ENet, and so 0% of all unreliable * packets will be sent. Intermediate values for the throttle represent intermediate * probabilities between 0% and 100% of unreliable packets being sent. The bandwidth * limits of the local and foreign hosts are taken into account to determine a * sensible limit for the throttle probability above which it should not raise even in * the best of conditions. * * @param peer peer to configure * @param interval interval, in milliseconds, over which to measure lowest mean RTT; the default value is ENET_PEER_PACKET_THROTTLE_INTERVAL. * @param acceleration rate at which to increase the throttle probability as mean RTT declines * @param deceleration rate at which to decrease the throttle probability as mean RTT increases */ void enet_peer_throttle_configure(ENetPeer* peer, enet_uint32 interval, enet_uint32 acceleration, enet_uint32 deceleration) { ENetProtocol command; peer->packetThrottleInterval = interval; peer->packetThrottleAcceleration = acceleration; peer->packetThrottleDeceleration = deceleration; command.header.command = ENET_PROTOCOL_COMMAND_THROTTLE_CONFIGURE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; command.header.channelID = 0xFF; command.throttleConfigure.packetThrottleInterval = ENET_HOST_TO_NET_32(interval); command.throttleConfigure.packetThrottleAcceleration = ENET_HOST_TO_NET_32(acceleration); command.throttleConfigure.packetThrottleDeceleration = ENET_HOST_TO_NET_32(deceleration); enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); } int enet_peer_throttle(ENetPeer* peer, enet_uint32 rtt) { if (peer->lastRoundTripTime <= peer->lastRoundTripTimeVariance) { peer->packetThrottle = peer->packetThrottleLimit; } else if (rtt < peer->lastRoundTripTime) { peer->packetThrottle += peer->packetThrottleAcceleration; if (peer->packetThrottle > peer->packetThrottleLimit) { peer->packetThrottle = peer->packetThrottleLimit; } return 1; } else if (rtt > peer->lastRoundTripTime + 2 * peer->lastRoundTripTimeVariance) { if (peer->packetThrottle > peer->packetThrottleDeceleration) { peer->packetThrottle -= peer->packetThrottleDeceleration; } else { peer->packetThrottle = 0; } return -1; } return 0; } /* Extended functionality for easier binding in other programming languages */ enet_uint32 enet_host_get_peers_count(ENetHost* host) { return host->connectedPeers; } enet_uint32 enet_host_get_packets_sent(ENetHost* host) { return host->totalSentPackets; } enet_uint32 enet_host_get_packets_received(ENetHost* host) { return host->totalReceivedPackets; } enet_uint32 enet_host_get_bytes_sent(ENetHost* host) { return host->totalSentData; } enet_uint32 enet_host_get_bytes_received(ENetHost* host) { return host->totalReceivedData; } /** Gets received data buffer. Returns buffer length. * @param host host to access recevie buffer * @param data ouput parameter for recevied data * @retval buffer length */ enet_uint32 enet_host_get_received_data(ENetHost* host, /*out*/ enet_uint8** data) { *data = host->receivedData; return host->receivedDataLength; } enet_uint32 enet_host_get_mtu(ENetHost* host) { return host->mtu; } enet_uint32 enet_peer_get_id(ENetPeer* peer) { return peer->connectID; } enet_uint32 enet_peer_get_ip(ENetPeer* peer, char* ip, size_t ipLength) { return enet_address_get_host_ip(&peer->address, ip, ipLength); } enet_uint16 enet_peer_get_port(ENetPeer* peer) { return peer->address.port; } ENetPeerState enet_peer_get_state(ENetPeer* peer) { return peer->state; } enet_uint32 enet_peer_get_rtt(ENetPeer* peer) { return peer->roundTripTime; } enet_uint64 enet_peer_get_packets_sent(ENetPeer* peer) { return peer->totalPacketsSent; } enet_uint32 enet_peer_get_packets_lost(ENetPeer* peer) { return peer->totalPacketsLost; } enet_uint64 enet_peer_get_bytes_sent(ENetPeer* peer) { return peer->totalDataSent; } enet_uint64 enet_peer_get_bytes_received(ENetPeer* peer) { return peer->totalDataReceived; } void * enet_peer_get_data(ENetPeer* peer) { return (void*)peer->data; } void enet_peer_set_data(ENetPeer* peer, const void* data) { peer->data = (enet_uint32*)data; } void * enet_packet_get_data(ENetPacket* packet) { return (void*)packet->data; } enet_uint32 enet_packet_get_length(ENetPacket* packet) { return packet->dataLength; } void enet_packet_set_free_callback(ENetPacket* packet, void* callback) { packet->freeCallback = (ENetPacketFreeCallback)callback; } /** Queues a packet to be sent. * @param peer destination for the packet * @param channelID channel on which to send * @param packet packet to send * @retval 0 on success * @retval < 0 on failure */ int enet_peer_send(ENetPeer* peer, enet_uint8 channelID, ENetPacket* packet) { ENetChannel* channel = &peer->channels[channelID]; ENetProtocol command; size_t fragmentLength; if (peer->state != ENET_PEER_STATE_CONNECTED || channelID >= peer->channelCount || packet->dataLength > peer->host->maximumPacketSize) { return -1; } fragmentLength = peer->mtu - sizeof(ENetProtocolHeader) - sizeof(ENetProtocolSendFragment); if (peer->host->checksum != NULL) { fragmentLength -= sizeof(enet_uint32); } if (packet->dataLength > fragmentLength) { enet_uint32 fragmentCount = (packet->dataLength + fragmentLength - 1) / fragmentLength, fragmentNumber, fragmentOffset; enet_uint8 commandNumber; enet_uint16 startSequenceNumber; ENetList fragments; ENetOutgoingCommand* fragment; if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) { return -1; } if ((packet->flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT)) == ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT && channel->outgoingUnreliableSequenceNumber < 0xFFFF) { commandNumber = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT; startSequenceNumber = ENET_HOST_TO_NET_16(channel->outgoingUnreliableSequenceNumber + 1); } else { commandNumber = ENET_PROTOCOL_COMMAND_SEND_FRAGMENT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; startSequenceNumber = ENET_HOST_TO_NET_16(channel->outgoingReliableSequenceNumber + 1); } enet_list_clear(&fragments); for (fragmentNumber = 0, fragmentOffset = 0; fragmentOffset < packet->dataLength; ++fragmentNumber, fragmentOffset += fragmentLength) { if (packet->dataLength - fragmentOffset < fragmentLength) { fragmentLength = packet->dataLength - fragmentOffset; } fragment = (ENetOutgoingCommand *) enet_malloc(sizeof(ENetOutgoingCommand)); if (fragment == NULL) { while (!enet_list_empty(&fragments)) { fragment = (ENetOutgoingCommand *) enet_list_remove(enet_list_begin(&fragments)); enet_free(fragment); } return -1; } fragment->fragmentOffset = fragmentOffset; fragment->fragmentLength = fragmentLength; fragment->packet = packet; fragment->command.header.command = commandNumber; fragment->command.header.channelID = channelID; fragment->command.sendFragment.startSequenceNumber = startSequenceNumber; fragment->command.sendFragment.dataLength = ENET_HOST_TO_NET_16(fragmentLength); fragment->command.sendFragment.fragmentCount = ENET_HOST_TO_NET_32(fragmentCount); fragment->command.sendFragment.fragmentNumber = ENET_HOST_TO_NET_32(fragmentNumber); fragment->command.sendFragment.totalLength = ENET_HOST_TO_NET_32(packet->dataLength); fragment->command.sendFragment.fragmentOffset = ENET_NET_TO_HOST_32(fragmentOffset); enet_list_insert(enet_list_end(&fragments), fragment); } packet->referenceCount += fragmentNumber; while (!enet_list_empty(&fragments)) { fragment = (ENetOutgoingCommand*)enet_list_remove(enet_list_begin(&fragments)); enet_peer_setup_outgoing_command(peer, fragment); } return 0; } command.header.channelID = channelID; if ((packet->flags & (ENET_PACKET_FLAG_RELIABLE | ENET_PACKET_FLAG_UNSEQUENCED)) == ENET_PACKET_FLAG_UNSEQUENCED) { command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; command.sendUnsequenced.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); } else if (packet->flags & ENET_PACKET_FLAG_RELIABLE || channel->outgoingUnreliableSequenceNumber >= 0xFFFF) { command.header.command = ENET_PROTOCOL_COMMAND_SEND_RELIABLE | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; command.sendReliable.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); } else { command.header.command = ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE; command.sendUnreliable.dataLength = ENET_HOST_TO_NET_16(packet->dataLength); } if (enet_peer_queue_outgoing_command(peer, &command, packet, 0, packet->dataLength) == NULL) { return -1; } return 0; } // enet_peer_send /** Attempts to dequeue any incoming queued packet. * @param peer peer to dequeue packets from * @param channelID holds the channel ID of the channel the packet was received on success * @returns a pointer to the packet, or NULL if there are no available incoming queued packets */ ENetPacket * enet_peer_receive(ENetPeer* peer, enet_uint8* channelID) { ENetIncomingCommand* incomingCommand; ENetPacket* packet; if (enet_list_empty(&peer->dispatchedCommands)) { return NULL; } incomingCommand = (ENetIncomingCommand *) enet_list_remove(enet_list_begin(&peer->dispatchedCommands)); if (channelID != NULL) { *channelID = incomingCommand->command.header.channelID; } packet = incomingCommand->packet; --packet->referenceCount; if (incomingCommand->fragments != NULL) { enet_free(incomingCommand->fragments); } enet_free(incomingCommand); peer->totalWaitingData -= ENET_MIN(peer->totalWaitingData, packet->dataLength); return packet; } static void enet_peer_reset_outgoing_commands(ENetPeer* peer, ENetList* queue) { ENetOutgoingCommand *outgoingCommand; while (!enet_list_empty(queue)) { outgoingCommand = (ENetOutgoingCommand*)enet_list_remove(enet_list_begin(queue)); if (outgoingCommand->packet != NULL) { --outgoingCommand->packet->referenceCount; if (outgoingCommand->packet->referenceCount == 0) { callbacks.packet_destroy(outgoingCommand->packet); } } enet_free(outgoingCommand); } } static void enet_peer_remove_incoming_commands(ENetPeer* peer, ENetList* queue, ENetListIterator startCommand, ENetListIterator endCommand) { ENET_UNUSED(queue) ENetListIterator currentCommand; for (currentCommand = startCommand; currentCommand != endCommand;) { ENetIncomingCommand *incomingCommand = (ENetIncomingCommand*)currentCommand; currentCommand = enet_list_next(currentCommand); enet_list_remove(&incomingCommand->incomingCommandList); if (incomingCommand->packet != NULL) { --incomingCommand->packet->referenceCount; peer->totalWaitingData -= ENET_MIN(peer->totalWaitingData, incomingCommand->packet->dataLength); if (incomingCommand->packet->referenceCount == 0) { callbacks.packet_destroy(incomingCommand->packet); } } if (incomingCommand->fragments != NULL) { enet_free(incomingCommand->fragments); } enet_free(incomingCommand); } } static void enet_peer_reset_incoming_commands(ENetPeer* peer, ENetList* queue) { enet_peer_remove_incoming_commands(peer, queue, enet_list_begin(queue), enet_list_end(queue)); } void enet_peer_reset_queues(ENetPeer* peer) { ENetChannel* channel; if (peer->needsDispatch) { enet_list_remove(&peer->dispatchList); peer->needsDispatch = 0; } while (!enet_list_empty(&peer->acknowledgements)) { enet_free(enet_list_remove(enet_list_begin(&peer->acknowledgements))); } enet_peer_reset_outgoing_commands(peer, &peer->sentReliableCommands); enet_peer_reset_outgoing_commands(peer, &peer->sentUnreliableCommands); enet_peer_reset_outgoing_commands(peer, &peer->outgoingReliableCommands); enet_peer_reset_outgoing_commands(peer, &peer->outgoingUnreliableCommands); enet_peer_reset_incoming_commands(peer, &peer->dispatchedCommands); if (peer->channels != NULL && peer->channelCount > 0) { for (channel = peer->channels; channel < &peer->channels[peer->channelCount]; ++channel) { enet_peer_reset_incoming_commands(peer, &channel->incomingReliableCommands); enet_peer_reset_incoming_commands(peer, &channel->incomingUnreliableCommands); } enet_free(peer->channels); } peer->channels = NULL; peer->channelCount = 0; } void enet_peer_on_connect(ENetPeer* peer) { if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { if (peer->incomingBandwidth != 0) { ++peer->host->bandwidthLimitedPeers; } ++peer->host->connectedPeers; } } void enet_peer_on_disconnect(ENetPeer* peer) { if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { if (peer->incomingBandwidth != 0) { --peer->host->bandwidthLimitedPeers; } --peer->host->connectedPeers; } } /** Forcefully disconnects a peer. * @param peer peer to forcefully disconnect * @remarks The foreign host represented by the peer is not notified of the disconnection and will timeout * on its connection to the local host. */ void enet_peer_reset(ENetPeer* peer) { enet_peer_on_disconnect(peer); // We don't want to reset connectID here, otherwise, we can't get it in the Disconnect event // peer->connectID = 0; peer->outgoingPeerID = ENET_PROTOCOL_MAXIMUM_PEER_ID; peer->state = ENET_PEER_STATE_DISCONNECTED; peer->incomingBandwidth = 0; peer->outgoingBandwidth = 0; peer->incomingBandwidthThrottleEpoch = 0; peer->outgoingBandwidthThrottleEpoch = 0; peer->incomingDataTotal = 0; peer->totalDataReceived = 0; peer->outgoingDataTotal = 0; peer->totalDataSent = 0; peer->lastSendTime = 0; peer->lastReceiveTime = 0; peer->nextTimeout = 0; peer->earliestTimeout = 0; peer->packetLossEpoch = 0; peer->packetsSent = 0; peer->totalPacketsSent = 0; peer->packetsLost = 0; peer->totalPacketsLost = 0; peer->packetLoss = 0; peer->packetLossVariance = 0; peer->packetThrottle = ENET_PEER_DEFAULT_PACKET_THROTTLE; peer->packetThrottleLimit = ENET_PEER_PACKET_THROTTLE_SCALE; peer->packetThrottleCounter = 0; peer->packetThrottleEpoch = 0; peer->packetThrottleAcceleration = ENET_PEER_PACKET_THROTTLE_ACCELERATION; peer->packetThrottleDeceleration = ENET_PEER_PACKET_THROTTLE_DECELERATION; peer->packetThrottleInterval = ENET_PEER_PACKET_THROTTLE_INTERVAL; peer->pingInterval = ENET_PEER_PING_INTERVAL; peer->timeoutLimit = ENET_PEER_TIMEOUT_LIMIT; peer->timeoutMinimum = ENET_PEER_TIMEOUT_MINIMUM; peer->timeoutMaximum = ENET_PEER_TIMEOUT_MAXIMUM; peer->lastRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; peer->lowestRoundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; peer->lastRoundTripTimeVariance = 0; peer->highestRoundTripTimeVariance = 0; peer->roundTripTime = ENET_PEER_DEFAULT_ROUND_TRIP_TIME; peer->roundTripTimeVariance = 0; peer->mtu = peer->host->mtu; peer->reliableDataInTransit = 0; peer->outgoingReliableSequenceNumber = 0; peer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; peer->incomingUnsequencedGroup = 0; peer->outgoingUnsequencedGroup = 0; peer->eventData = 0; peer->totalWaitingData = 0; memset(peer->unsequencedWindow, 0, sizeof(peer->unsequencedWindow)); enet_peer_reset_queues(peer); } /** Sends a ping request to a peer. * @param peer destination for the ping request * @remarks ping requests factor into the mean round trip time as designated by the * roundTripTime field in the ENetPeer structure. ENet automatically pings all connected * peers at regular intervals, however, this function may be called to ensure more * frequent ping requests. */ void enet_peer_ping(ENetPeer* peer) { ENetProtocol command; if (peer->state != ENET_PEER_STATE_CONNECTED) { return; } command.header.command = ENET_PROTOCOL_COMMAND_PING | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; command.header.channelID = 0xFF; enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); } /** Sets the interval at which pings will be sent to a peer. * * Pings are used both to monitor the liveness of the connection and also to dynamically * adjust the throttle during periods of low traffic so that the throttle has reasonable * responsiveness during traffic spikes. * * @param peer the peer to adjust * @param pingInterval the interval at which to send pings; defaults to ENET_PEER_PING_INTERVAL if 0 */ void enet_peer_ping_interval(ENetPeer* peer, enet_uint32 pingInterval) { peer->pingInterval = pingInterval ? pingInterval : (enet_uint32)ENET_PEER_PING_INTERVAL; } /** Sets the timeout parameters for a peer. * * The timeout parameter control how and when a peer will timeout from a failure to acknowledge * reliable traffic. Timeout values use an exponential backoff mechanism, where if a reliable * packet is not acknowledge within some multiple of the average RTT plus a variance tolerance, * the timeout will be doubled until it reaches a set limit. If the timeout is thus at this * limit and reliable packets have been sent but not acknowledged within a certain minimum time * period, the peer will be disconnected. Alternatively, if reliable packets have been sent * but not acknowledged for a certain maximum time period, the peer will be disconnected regardless * of the current timeout limit value. * * @param peer the peer to adjust * @param timeoutLimit the timeout limit; defaults to ENET_PEER_TIMEOUT_LIMIT if 0 * @param timeoutMinimum the timeout minimum; defaults to ENET_PEER_TIMEOUT_MINIMUM if 0 * @param timeoutMaximum the timeout maximum; defaults to ENET_PEER_TIMEOUT_MAXIMUM if 0 */ void enet_peer_timeout(ENetPeer* peer, enet_uint32 timeoutLimit, enet_uint32 timeoutMinimum, enet_uint32 timeoutMaximum) { peer->timeoutLimit = timeoutLimit ? timeoutLimit : (enet_uint32)ENET_PEER_TIMEOUT_LIMIT; peer->timeoutMinimum = timeoutMinimum ? timeoutMinimum : (enet_uint32)ENET_PEER_TIMEOUT_MINIMUM; peer->timeoutMaximum = timeoutMaximum ? timeoutMaximum : (enet_uint32)ENET_PEER_TIMEOUT_MAXIMUM; } /** Force an immediate disconnection from a peer. * @param peer peer to disconnect * @param data data describing the disconnection * @remarks No ENET_EVENT_DISCONNECT event will be generated. The foreign peer is not * guaranteed to receive the disconnect notification, and is reset immediately upon * return from this function. */ void enet_peer_disconnect_now(ENetPeer* peer, enet_uint32 data) { ENetProtocol command; if (peer->state == ENET_PEER_STATE_DISCONNECTED) { return; } if (peer->state != ENET_PEER_STATE_ZOMBIE && peer->state != ENET_PEER_STATE_DISCONNECTING) { enet_peer_reset_queues(peer); command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT | ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; command.header.channelID = 0xFF; command.disconnect.data = ENET_HOST_TO_NET_32(data); enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); enet_host_flush(peer->host); } enet_peer_reset(peer); } /** Request a disconnection from a peer. * @param peer peer to request a disconnection * @param data data describing the disconnection * @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() * once the disconnection is complete. */ void enet_peer_disconnect(ENetPeer* peer, enet_uint32 data) { ENetProtocol command; if (peer->state == ENET_PEER_STATE_DISCONNECTING || peer->state == ENET_PEER_STATE_DISCONNECTED || peer->state == ENET_PEER_STATE_ACKNOWLEDGING_DISCONNECT || peer->state == ENET_PEER_STATE_ZOMBIE ) { return; } enet_peer_reset_queues(peer); command.header.command = ENET_PROTOCOL_COMMAND_DISCONNECT; command.header.channelID = 0xFF; command.disconnect.data = ENET_HOST_TO_NET_32(data); if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; } else { command.header.command |= ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED; } enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); if (peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { enet_peer_on_disconnect(peer); peer->state = ENET_PEER_STATE_DISCONNECTING; } else { enet_host_flush(peer->host); enet_peer_reset(peer); } } /** Request a disconnection from a peer, but only after all queued outgoing packets are sent. * @param peer peer to request a disconnection * @param data data describing the disconnection * @remarks An ENET_EVENT_DISCONNECT event will be generated by enet_host_service() * once the disconnection is complete. */ void enet_peer_disconnect_later(ENetPeer* peer, enet_uint32 data) { if ((peer->state == ENET_PEER_STATE_CONNECTED || peer->state == ENET_PEER_STATE_DISCONNECT_LATER) && !(enet_list_empty(&peer->outgoingReliableCommands) && enet_list_empty(&peer->outgoingUnreliableCommands) && enet_list_empty(&peer->sentReliableCommands)) ) { peer->state = ENET_PEER_STATE_DISCONNECT_LATER; peer->eventData = data; } else { enet_peer_disconnect(peer, data); } } ENetAcknowledgement* enet_peer_queue_acknowledgement(ENetPeer* peer, const ENetProtocol* command, enet_uint16 sentTime) { ENetAcknowledgement *acknowledgement; if (command->header.channelID < peer->channelCount) { ENetChannel *channel = &peer->channels[command->header.channelID]; enet_uint16 reliableWindow = command->header.reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; enet_uint16 currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (command->header.reliableSequenceNumber < channel->incomingReliableSequenceNumber) { reliableWindow += ENET_PEER_RELIABLE_WINDOWS; } if (reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1 && reliableWindow <= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS) { return NULL; } } acknowledgement = (ENetAcknowledgement *) enet_malloc(sizeof(ENetAcknowledgement)); if (acknowledgement == NULL) { return NULL; } peer->outgoingDataTotal += sizeof(ENetProtocolAcknowledge); acknowledgement->sentTime = sentTime; acknowledgement->command = *command; enet_list_insert(enet_list_end(&peer->acknowledgements), acknowledgement); return acknowledgement; } void enet_peer_setup_outgoing_command(ENetPeer* peer, ENetOutgoingCommand* outgoingCommand) { ENetChannel* channel = &peer->channels[outgoingCommand->command.header.channelID]; peer->outgoingDataTotal += enet_protocol_command_size(outgoingCommand->command.header.command) + outgoingCommand->fragmentLength; if (outgoingCommand->command.header.channelID == 0xFF) { ++peer->outgoingReliableSequenceNumber; outgoingCommand->reliableSequenceNumber = peer->outgoingReliableSequenceNumber; outgoingCommand->unreliableSequenceNumber = 0; } else if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { ++channel->outgoingReliableSequenceNumber; channel->outgoingUnreliableSequenceNumber = 0; outgoingCommand->reliableSequenceNumber = channel->outgoingReliableSequenceNumber; outgoingCommand->unreliableSequenceNumber = 0; } else if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED) { ++peer->outgoingUnsequencedGroup; outgoingCommand->reliableSequenceNumber = 0; outgoingCommand->unreliableSequenceNumber = 0; } else { if (outgoingCommand->fragmentOffset == 0) { ++channel->outgoingUnreliableSequenceNumber; } outgoingCommand->reliableSequenceNumber = channel->outgoingReliableSequenceNumber; outgoingCommand->unreliableSequenceNumber = channel->outgoingUnreliableSequenceNumber; } outgoingCommand->sendAttempts = 0; outgoingCommand->sentTime = 0; outgoingCommand->roundTripTimeout = 0; outgoingCommand->roundTripTimeoutLimit = 0; outgoingCommand->command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16(outgoingCommand->reliableSequenceNumber); switch (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) { case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: outgoingCommand->command.sendUnreliable.unreliableSequenceNumber = ENET_HOST_TO_NET_16(outgoingCommand->unreliableSequenceNumber); break; case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: outgoingCommand->command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16(peer->outgoingUnsequencedGroup); break; default: break; } if (outgoingCommand->command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) { enet_list_insert(enet_list_end(&peer->outgoingReliableCommands), outgoingCommand); } else { enet_list_insert(enet_list_end(&peer->outgoingUnreliableCommands), outgoingCommand); } } ENetOutgoingCommand* enet_peer_queue_outgoing_command(ENetPeer* peer, const ENetProtocol* command, ENetPacket* packet, enet_uint32 offset, enet_uint16 length) { ENetOutgoingCommand* outgoingCommand = (ENetOutgoingCommand *) enet_malloc(sizeof(ENetOutgoingCommand)); if (outgoingCommand == NULL) { return NULL; } outgoingCommand->command = *command; outgoingCommand->fragmentOffset = offset; outgoingCommand->fragmentLength = length; outgoingCommand->packet = packet; if (packet != NULL) { ++packet->referenceCount; } enet_peer_setup_outgoing_command(peer, outgoingCommand); return outgoingCommand; } void enet_peer_dispatch_incoming_unreliable_commands(ENetPeer* peer, ENetChannel* channel) { ENetListIterator droppedCommand, startCommand, currentCommand; for (droppedCommand = startCommand = currentCommand = enet_list_begin(&channel->incomingUnreliableCommands); currentCommand != enet_list_end(&channel->incomingUnreliableCommands); currentCommand = enet_list_next(currentCommand) ) { ENetIncomingCommand* incomingCommand = (ENetIncomingCommand *) currentCommand; if ((incomingCommand->command.header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { continue; } if (incomingCommand->reliableSequenceNumber == channel->incomingReliableSequenceNumber) { if (incomingCommand->fragmentsRemaining <= 0) { channel->incomingUnreliableSequenceNumber = incomingCommand->unreliableSequenceNumber; continue; } if (startCommand != currentCommand) { enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); if (!peer->needsDispatch) { enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); peer->needsDispatch = 1; } droppedCommand = currentCommand; } else if (droppedCommand != currentCommand) { droppedCommand = enet_list_previous(currentCommand); } } else { enet_uint16 reliableWindow = incomingCommand->reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; enet_uint16 currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { reliableWindow += ENET_PEER_RELIABLE_WINDOWS; } if (reliableWindow >= currentWindow && reliableWindow < currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { break; } droppedCommand = enet_list_next(currentCommand); if (startCommand != currentCommand) { enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); if (!peer->needsDispatch) { enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); peer->needsDispatch = 1; } } } startCommand = enet_list_next(currentCommand); } if (startCommand != currentCommand) { enet_list_move(enet_list_end(&peer->dispatchedCommands), startCommand, enet_list_previous(currentCommand)); if (!peer->needsDispatch) { enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); peer->needsDispatch = 1; } droppedCommand = currentCommand; } enet_peer_remove_incoming_commands(peer, &channel->incomingUnreliableCommands,enet_list_begin(&channel->incomingUnreliableCommands), droppedCommand); } void enet_peer_dispatch_incoming_reliable_commands(ENetPeer* peer, ENetChannel* channel) { ENetListIterator currentCommand; for (currentCommand = enet_list_begin(&channel->incomingReliableCommands); currentCommand != enet_list_end(&channel->incomingReliableCommands); currentCommand = enet_list_next(currentCommand) ) { ENetIncomingCommand *incomingCommand = (ENetIncomingCommand *) currentCommand; if (incomingCommand->fragmentsRemaining > 0 || incomingCommand->reliableSequenceNumber != (enet_uint16) (channel->incomingReliableSequenceNumber + 1)) { break; } channel->incomingReliableSequenceNumber = incomingCommand->reliableSequenceNumber; if (incomingCommand->fragmentCount > 0) { channel->incomingReliableSequenceNumber += incomingCommand->fragmentCount - 1; } } if (currentCommand == enet_list_begin(&channel->incomingReliableCommands)) { return; } channel->incomingUnreliableSequenceNumber = 0; enet_list_move(enet_list_end(&peer->dispatchedCommands), enet_list_begin(&channel->incomingReliableCommands), enet_list_previous(currentCommand)); if (!peer->needsDispatch) { enet_list_insert(enet_list_end(&peer->host->dispatchQueue), &peer->dispatchList); peer->needsDispatch = 1; } if (!enet_list_empty(&channel->incomingUnreliableCommands)) { enet_peer_dispatch_incoming_unreliable_commands(peer, channel); } } ENetIncomingCommand * enet_peer_queue_incoming_command(ENetPeer* peer, const ENetProtocol* command, const void* data, size_t dataLength, enet_uint32 flags, enet_uint32 fragmentCount) { static ENetIncomingCommand dummyCommand; ENetChannel* channel = &peer->channels[command->header.channelID]; enet_uint32 unreliableSequenceNumber = 0, reliableSequenceNumber = 0; enet_uint16 reliableWindow, currentWindow; ENetIncomingCommand* incomingCommand; ENetListIterator currentCommand; ENetPacket* packet = NULL; if (peer->state == ENET_PEER_STATE_DISCONNECT_LATER) { goto discardCommand; } if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) != ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { reliableSequenceNumber = command->header.reliableSequenceNumber; reliableWindow = reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; currentWindow = channel->incomingReliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE; if (reliableSequenceNumber < channel->incomingReliableSequenceNumber) { reliableWindow += ENET_PEER_RELIABLE_WINDOWS; } if (reliableWindow < currentWindow || reliableWindow >= currentWindow + ENET_PEER_FREE_RELIABLE_WINDOWS - 1) { goto discardCommand; } } switch (command->header.command & ENET_PROTOCOL_COMMAND_MASK) { case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: if (reliableSequenceNumber == channel->incomingReliableSequenceNumber) { goto discardCommand; } for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingReliableCommands)); currentCommand != enet_list_end(&channel->incomingReliableCommands); currentCommand = enet_list_previous(currentCommand) ) { incomingCommand = (ENetIncomingCommand *) currentCommand; if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { continue; } } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { break; } if (incomingCommand->reliableSequenceNumber <= reliableSequenceNumber) { if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) { break; } goto discardCommand; } } break; case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE: case ENET_PROTOCOL_COMMAND_SEND_UNRELIABLE_FRAGMENT: unreliableSequenceNumber = ENET_NET_TO_HOST_16(command->sendUnreliable.unreliableSequenceNumber); if (reliableSequenceNumber == channel->incomingReliableSequenceNumber && unreliableSequenceNumber <= channel->incomingUnreliableSequenceNumber) { goto discardCommand; } for (currentCommand = enet_list_previous(enet_list_end(&channel->incomingUnreliableCommands)); currentCommand != enet_list_end(&channel->incomingUnreliableCommands); currentCommand = enet_list_previous(currentCommand) ) { incomingCommand = (ENetIncomingCommand *) currentCommand; if ((command->header.command & ENET_PROTOCOL_COMMAND_MASK) == ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED) { continue; } if (reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { if (incomingCommand->reliableSequenceNumber < channel->incomingReliableSequenceNumber) { continue; } } else if (incomingCommand->reliableSequenceNumber >= channel->incomingReliableSequenceNumber) { break; } if (incomingCommand->reliableSequenceNumber < reliableSequenceNumber) { break; } if (incomingCommand->reliableSequenceNumber > reliableSequenceNumber) { continue; } if (incomingCommand->unreliableSequenceNumber <= unreliableSequenceNumber) { if (incomingCommand->unreliableSequenceNumber < unreliableSequenceNumber) { break; } goto discardCommand; } } break; case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED: currentCommand = enet_list_end(&channel->incomingUnreliableCommands); break; default: goto discardCommand; } if (peer->totalWaitingData >= peer->host->maximumWaitingData) { goto notifyError; } packet = callbacks.packet_create(data, dataLength, flags); if (packet == NULL) { goto notifyError; } incomingCommand = (ENetIncomingCommand *) enet_malloc(sizeof(ENetIncomingCommand)); if (incomingCommand == NULL) { goto notifyError; } incomingCommand->reliableSequenceNumber = command->header.reliableSequenceNumber; incomingCommand->unreliableSequenceNumber = unreliableSequenceNumber & 0xFFFF; incomingCommand->command = *command; incomingCommand->fragmentCount = fragmentCount; incomingCommand->fragmentsRemaining = fragmentCount; incomingCommand->packet = packet; incomingCommand->fragments = NULL; if (fragmentCount > 0) { if (fragmentCount <= ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT) { incomingCommand->fragments = (enet_uint32 *) enet_malloc((fragmentCount + 31) / 32 * sizeof(enet_uint32)); } if (incomingCommand->fragments == NULL) { enet_free(incomingCommand); goto notifyError; } memset(incomingCommand->fragments, 0, (fragmentCount + 31) / 32 * sizeof(enet_uint32)); } assert(packet != NULL); ++packet->referenceCount; peer->totalWaitingData += packet->dataLength; enet_list_insert(enet_list_next(currentCommand), incomingCommand); switch (command->header.command & ENET_PROTOCOL_COMMAND_MASK) { case ENET_PROTOCOL_COMMAND_SEND_FRAGMENT: case ENET_PROTOCOL_COMMAND_SEND_RELIABLE: enet_peer_dispatch_incoming_reliable_commands(peer, channel); break; default: enet_peer_dispatch_incoming_unreliable_commands(peer, channel); break; } return incomingCommand; discardCommand: if (fragmentCount > 0) { goto notifyError; } if (packet != NULL && packet->referenceCount == 0) { callbacks.packet_destroy(packet); } return &dummyCommand; notifyError: if (packet != NULL && packet->referenceCount == 0) { callbacks.packet_destroy(packet); } return NULL; } /* enet_peer_queue_incoming_command */ // =======================================================================// // ! // ! Host // ! // =======================================================================// /** Creates a host for communicating to peers. * * @param address the address at which other peers may connect to this host. If NULL, then no peers may connect to the host. * @param peerCount the maximum number of peers that should be allocated for the host. * @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT * @param incomingBandwidth downstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. * @param outgoingBandwidth upstream bandwidth of the host in bytes/second; if 0, ENet will assume unlimited bandwidth. * * @returns the host on success and NULL on failure * * @remarks ENet will strategically drop packets on specific sides of a connection between hosts * to ensure the host's bandwidth is not overwhelmed. The bandwidth parameters also determine * the window size of a connection which limits the amount of reliable packets that may be in transit * at any given time. */ ENetHost* enet_host_create(const ENetAddress* address, size_t peerCount, size_t channelLimit, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) { ENetHost* host; ENetPeer* currentPeer; if (peerCount > ENET_PROTOCOL_MAXIMUM_PEER_ID) { return NULL; } host = (ENetHost*)enet_malloc(sizeof(ENetHost)); if (host == NULL) { return NULL; } memset(host, 0, sizeof(ENetHost)); host->peers = (ENetPeer*)enet_malloc(peerCount * sizeof(ENetPeer)); if (host->peers == NULL) { enet_free(host); return NULL; } memset(host->peers, 0, peerCount * sizeof(ENetPeer)); host->socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); #if ENET_IPV6 if (host->socket != ENET_SOCKET_NULL) { enet_socket_set_option(host->socket, ENET_SOCKOPT_IPV6_V6ONLY, 0); } #endif if (host->socket == ENET_SOCKET_NULL || (address != NULL && enet_socket_bind(host->socket, address) < 0)) { if (host->socket != ENET_SOCKET_NULL) { enet_socket_destroy(host->socket); } enet_free(host->peers); enet_free(host); return NULL; } enet_socket_set_option(host->socket, ENET_SOCKOPT_NONBLOCK, 1); enet_socket_set_option(host->socket, ENET_SOCKOPT_BROADCAST, 1); enet_socket_set_option(host->socket, ENET_SOCKOPT_RCVBUF, ENET_HOST_RECEIVE_BUFFER_SIZE); enet_socket_set_option(host->socket, ENET_SOCKOPT_SNDBUF, ENET_HOST_SEND_BUFFER_SIZE); #if ENET_IPV6 enet_socket_set_option(host->socket, ENET_SOCKOPT_IPV6_V6ONLY, 0); #endif if (address != NULL && enet_socket_get_address(host->socket, &host->address) < 0) { host->address = *address; } if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; } host->randomSeed = (enet_uint32) ((uintptr_t) host % UINT32_MAX); host->randomSeed += enet_host_random_seed(); host->randomSeed = (host->randomSeed << 16) | (host->randomSeed >> 16); host->channelLimit = channelLimit; host->incomingBandwidth = incomingBandwidth; host->outgoingBandwidth = outgoingBandwidth; host->bandwidthThrottleEpoch = 0; host->recalculateBandwidthLimits = 0; host->mtu = ENET_HOST_DEFAULT_MTU; host->peerCount = peerCount; host->commandCount = 0; host->bufferCount = 0; host->checksum = NULL; host->receivedAddress.host = ENET_HOST_ANY; host->receivedAddress.port = 0; host->receivedData = NULL; host->receivedDataLength = 0; host->totalSentData = 0; host->totalSentPackets = 0; host->totalReceivedData = 0; host->totalReceivedPackets = 0; host->connectedPeers = 0; host->bandwidthLimitedPeers = 0; host->duplicatePeers = ENET_PROTOCOL_MAXIMUM_PEER_ID; host->maximumPacketSize = ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE; host->maximumWaitingData = ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA; host->compressor.context = NULL; host->compressor.compress = NULL; host->compressor.decompress = NULL; host->compressor.destroy = NULL; host->intercept = NULL; enet_list_clear(&host->dispatchQueue); for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { currentPeer->host = host; currentPeer->incomingPeerID = currentPeer - host->peers; currentPeer->outgoingSessionID = currentPeer->incomingSessionID = 0xFF; currentPeer->data = NULL; enet_list_clear(¤tPeer->acknowledgements); enet_list_clear(¤tPeer->sentReliableCommands); enet_list_clear(¤tPeer->sentUnreliableCommands); enet_list_clear(¤tPeer->outgoingReliableCommands); enet_list_clear(¤tPeer->outgoingUnreliableCommands); enet_list_clear(¤tPeer->dispatchedCommands); enet_peer_reset(currentPeer); } LOGD("enet_host_create() successfully created socket 0x{:.8x}", std::uint64_t(host->socket)); return host; } /* enet_host_create */ /** Destroys the host and all resources associated with it. * @param host pointer to the host to destroy */ void enet_host_destroy(ENetHost* host) { ENetPeer* currentPeer; if (host == NULL) { return; } enet_socket_destroy(host->socket); for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { enet_peer_reset(currentPeer); } if (host->compressor.context != NULL && host->compressor.destroy) { (*host->compressor.destroy)(host->compressor.context); } enet_free(host->peers); enet_free(host); } /** Initiates a connection to a foreign host. * @param host host seeking the connection * @param address destination for the connection * @param channelCount number of channels to allocate * @param data user data supplied to the receiving host * @returns a peer representing the foreign host on success, NULL on failure * @remarks The peer returned will have not completed the connection until enet_host_service() * notifies of an ENET_EVENT_TYPE_CONNECT event for the peer. */ ENetPeer* enet_host_connect(ENetHost* host, const ENetAddress* address, size_t channelCount, enet_uint32 data) { ENetPeer* currentPeer; ENetChannel* channel; ENetProtocol command; if (channelCount < ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT) { channelCount = ENET_PROTOCOL_MINIMUM_CHANNEL_COUNT; } else if (channelCount > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { channelCount = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; } for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { if (currentPeer->state == ENET_PEER_STATE_DISCONNECTED) { break; } } if (currentPeer >= &host->peers[host->peerCount]) { return NULL; } currentPeer->channels = (ENetChannel*)enet_malloc(channelCount * sizeof(ENetChannel)); if (currentPeer->channels == NULL) { return NULL; } currentPeer->channelCount = channelCount; currentPeer->state = ENET_PEER_STATE_CONNECTING; currentPeer->address = *address; currentPeer->connectID = ++host->randomSeed; if (host->outgoingBandwidth == 0) { currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } else { currentPeer->windowSize = (host->outgoingBandwidth / ENET_PEER_WINDOW_SIZE_SCALE) * ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } if (currentPeer->windowSize < ENET_PROTOCOL_MINIMUM_WINDOW_SIZE) { currentPeer->windowSize = ENET_PROTOCOL_MINIMUM_WINDOW_SIZE; } else if (currentPeer->windowSize > ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE) { currentPeer->windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE; } for (channel = currentPeer->channels; channel < ¤tPeer->channels[channelCount]; ++channel) { channel->outgoingReliableSequenceNumber = 0; channel->outgoingUnreliableSequenceNumber = 0; channel->incomingReliableSequenceNumber = 0; channel->incomingUnreliableSequenceNumber = 0; enet_list_clear(&channel->incomingReliableCommands); enet_list_clear(&channel->incomingUnreliableCommands); channel->usedReliableWindows = 0; memset(channel->reliableWindows, 0, sizeof(channel->reliableWindows)); } command.header.command = ENET_PROTOCOL_COMMAND_CONNECT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; command.header.channelID = 0xFF; command.connect.outgoingPeerID = ENET_HOST_TO_NET_16(currentPeer->incomingPeerID); command.connect.incomingSessionID = currentPeer->incomingSessionID; command.connect.outgoingSessionID = currentPeer->outgoingSessionID; command.connect.mtu = ENET_HOST_TO_NET_32(currentPeer->mtu); command.connect.windowSize = ENET_HOST_TO_NET_32(currentPeer->windowSize); command.connect.channelCount = ENET_HOST_TO_NET_32(channelCount); command.connect.incomingBandwidth = ENET_HOST_TO_NET_32(host->incomingBandwidth); command.connect.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); command.connect.packetThrottleInterval = ENET_HOST_TO_NET_32(currentPeer->packetThrottleInterval); command.connect.packetThrottleAcceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleAcceleration); command.connect.packetThrottleDeceleration = ENET_HOST_TO_NET_32(currentPeer->packetThrottleDeceleration); command.connect.connectID = currentPeer->connectID; command.connect.data = ENET_HOST_TO_NET_32(data); enet_peer_queue_outgoing_command(currentPeer, &command, NULL, 0, 0); return currentPeer; } /* enet_host_connect */ /** Queues a packet to be sent to all peers associated with the host. * @param host host on which to broadcast the packet * @param channelID channel on which to broadcast * @param packet packet to broadcast */ void enet_host_broadcast(ENetHost* host, enet_uint8 channelID, ENetPacket* packet) { ENetPeer* currentPeer; for (currentPeer = host->peers; currentPeer < &host->peers[host->peerCount]; ++currentPeer) { if (currentPeer->state != ENET_PEER_STATE_CONNECTED) { continue; } enet_peer_send(currentPeer, channelID, packet); } if (packet->referenceCount == 0) { callbacks.packet_destroy(packet); } } /** Sends raw data to specified address. Useful when you want to send unconnected data using host's socket. * @param host host sending data * @param address destination address * @param data data pointer * @param dataLength length of data to send * @retval >=0 bytes sent * @retval <0 error * @sa enet_socket_send */ int enet_host_send_raw(ENetHost* host, const ENetAddress* address, enet_uint8* data, size_t dataLength) { ENetBuffer buffer; buffer.data = data; buffer.dataLength = dataLength; return enet_socket_send(host->socket, address, &buffer, 1); } /** Sends raw data to specified address with extended arguments. Allows to send only part of data, handy for other programming languages. * I.e. if you have data =- { 0, 1, 2, 3 } and call function as enet_host_send_raw_ex(data, 1, 2) then it will skip 1 byte and send 2 bytes { 1, 2 }. * @param host host sending data * @param address destination address * @param data data pointer * @param skipBytes number of bytes to skip from start of data * @param bytesToSend number of bytes to send * @retval >=0 bytes sent * @retval <0 error * @sa enet_socket_send */ int enet_host_send_raw_ex(ENetHost* host, const ENetAddress* address, enet_uint8* data, size_t skipBytes, size_t bytesToSend) { ENetBuffer buffer; buffer.data = data + skipBytes; buffer.dataLength = bytesToSend; return enet_socket_send(host->socket, address, &buffer, 1); } /** Sets intercept callback for the host. * @param host host to set a callback * @param callback intercept callback */ void enet_host_set_intercept(ENetHost* host, const ENetInterceptCallback callback) { host->intercept = callback; } /** Sets the packet compressor the host should use to compress and decompress packets. * @param host host to enable or disable compression for * @param compressor callbacks for for the packet compressor; if NULL, then compression is disabled */ void enet_host_compress(ENetHost* host, const ENetCompressor* compressor) { if (host->compressor.context != NULL && host->compressor.destroy) { (*host->compressor.destroy)(host->compressor.context); } if (compressor) { host->compressor = *compressor; } else { host->compressor.context = NULL; } } /** Limits the maximum allowed channels of future incoming connections. * @param host host to limit * @param channelLimit the maximum number of channels allowed; if 0, then this is equivalent to ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT */ void enet_host_channel_limit(ENetHost* host, size_t channelLimit) { if (!channelLimit || channelLimit > ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT) { channelLimit = ENET_PROTOCOL_MAXIMUM_CHANNEL_COUNT; } host->channelLimit = channelLimit; } /** Adjusts the bandwidth limits of a host. * @param host host to adjust * @param incomingBandwidth new incoming bandwidth * @param outgoingBandwidth new outgoing bandwidth * @remarks the incoming and outgoing bandwidth parameters are identical in function to those * specified in enet_host_create(). */ void enet_host_bandwidth_limit(ENetHost* host, enet_uint32 incomingBandwidth, enet_uint32 outgoingBandwidth) { host->incomingBandwidth = incomingBandwidth; host->outgoingBandwidth = outgoingBandwidth; host->recalculateBandwidthLimits = 1; } void enet_host_bandwidth_throttle(ENetHost* host) { enet_uint32 timeCurrent = enet_time_get(); enet_uint32 elapsedTime = timeCurrent - host->bandwidthThrottleEpoch; enet_uint32 peersRemaining = (enet_uint32) host->connectedPeers; enet_uint32 dataTotal = ~0; enet_uint32 bandwidth = ~0; enet_uint32 throttle = 0; enet_uint32 bandwidthLimit = 0; int needsAdjustment = host->bandwidthLimitedPeers > 0 ? 1 : 0; ENetPeer* peer; ENetProtocol command; if (elapsedTime < ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL) { return; } if (host->outgoingBandwidth == 0 && host->incomingBandwidth == 0) { return; } host->bandwidthThrottleEpoch = timeCurrent; if (peersRemaining == 0) { return; } if (host->outgoingBandwidth != 0) { dataTotal = 0; bandwidth = (host->outgoingBandwidth * elapsedTime) / 1000; for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { continue; } dataTotal += peer->outgoingDataTotal; } } while (peersRemaining > 0 && needsAdjustment != 0) { needsAdjustment = 0; if (dataTotal <= bandwidth) { throttle = ENET_PEER_PACKET_THROTTLE_SCALE; } else { throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; } for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { enet_uint32 peerBandwidth; if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->incomingBandwidth == 0 || peer->outgoingBandwidthThrottleEpoch == timeCurrent ) { continue; } peerBandwidth = (peer->incomingBandwidth * elapsedTime) / 1000; if ((throttle * peer->outgoingDataTotal) / ENET_PEER_PACKET_THROTTLE_SCALE <= peerBandwidth) { continue; } peer->packetThrottleLimit = (peerBandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / peer->outgoingDataTotal; if (peer->packetThrottleLimit == 0) { peer->packetThrottleLimit = 1; } if (peer->packetThrottle > peer->packetThrottleLimit) { peer->packetThrottle = peer->packetThrottleLimit; } peer->outgoingBandwidthThrottleEpoch = timeCurrent; peer->incomingDataTotal = 0; peer->outgoingDataTotal = 0; needsAdjustment = 1; --peersRemaining; bandwidth -= peerBandwidth; dataTotal -= peerBandwidth; } } if (peersRemaining > 0) { if (dataTotal <= bandwidth) { throttle = ENET_PEER_PACKET_THROTTLE_SCALE; } else { throttle = (bandwidth * ENET_PEER_PACKET_THROTTLE_SCALE) / dataTotal; } for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->outgoingBandwidthThrottleEpoch == timeCurrent) { continue; } peer->packetThrottleLimit = throttle; if (peer->packetThrottle > peer->packetThrottleLimit) { peer->packetThrottle = peer->packetThrottleLimit; } peer->incomingDataTotal = 0; peer->outgoingDataTotal = 0; } } if (host->recalculateBandwidthLimits) { host->recalculateBandwidthLimits = 0; peersRemaining = (enet_uint32) host->connectedPeers; bandwidth = host->incomingBandwidth; needsAdjustment = 1; if (bandwidth == 0) { bandwidthLimit = 0; } else { while (peersRemaining > 0 && needsAdjustment != 0) { needsAdjustment = 0; bandwidthLimit = bandwidth / peersRemaining; for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { if ((peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) || peer->incomingBandwidthThrottleEpoch == timeCurrent ) { continue; } if (peer->outgoingBandwidth > 0 && peer->outgoingBandwidth >= bandwidthLimit) { continue; } peer->incomingBandwidthThrottleEpoch = timeCurrent; needsAdjustment = 1; --peersRemaining; bandwidth -= peer->outgoingBandwidth; } } } for (peer = host->peers; peer < &host->peers[host->peerCount]; ++peer) { if (peer->state != ENET_PEER_STATE_CONNECTED && peer->state != ENET_PEER_STATE_DISCONNECT_LATER) { continue; } command.header.command = ENET_PROTOCOL_COMMAND_BANDWIDTH_LIMIT | ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE; command.header.channelID = 0xFF; command.bandwidthLimit.outgoingBandwidth = ENET_HOST_TO_NET_32(host->outgoingBandwidth); if (peer->incomingBandwidthThrottleEpoch == timeCurrent) { command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(peer->outgoingBandwidth); } else { command.bandwidthLimit.incomingBandwidth = ENET_HOST_TO_NET_32(bandwidthLimit); } enet_peer_queue_outgoing_command(peer, &command, NULL, 0, 0); } } } /* enet_host_bandwidth_throttle */ // =======================================================================// // ! // ! Time // ! // =======================================================================// #ifdef _WIN32 static LARGE_INTEGER getFILETIMEoffset() { SYSTEMTIME s; FILETIME f; LARGE_INTEGER t; s.wYear = 1970; s.wMonth = 1; s.wDay = 1; s.wHour = 0; s.wMinute = 0; s.wSecond = 0; s.wMilliseconds = 0; SystemTimeToFileTime(&s, &f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; return (t); } int clock_gettime(int X, struct timespec* tv) { (void)X; LARGE_INTEGER t; FILETIME f; double microseconds; static LARGE_INTEGER offset; static double frequencyToMicroseconds; static int initialized = 0; static BOOL usePerformanceCounter = 0; if (!initialized) { LARGE_INTEGER performanceFrequency; initialized = 1; usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); if (usePerformanceCounter) { QueryPerformanceCounter(&offset); frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; } else { offset = getFILETIMEoffset(); frequencyToMicroseconds = 10.; } } if (usePerformanceCounter) { QueryPerformanceCounter(&t); } else { GetSystemTimeAsFileTime(&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; } t.QuadPart -= offset.QuadPart; microseconds = (double)t.QuadPart / frequencyToMicroseconds; t.QuadPart = (LONGLONG)microseconds; tv->tv_sec = (long)(t.QuadPart / 1000000); tv->tv_nsec = t.QuadPart % 1000000 * 1000; return (0); } #elif __APPLE__ && __MAC_OS_X_VERSION_MIN_REQUIRED < 101200 #define CLOCK_MONOTONIC 0 int clock_gettime(int X, struct timespec *ts) { clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); ts->tv_sec = mts.tv_sec; ts->tv_nsec = mts.tv_nsec; return 0; } #endif enet_uint32 enet_time_get() { // TODO enet uses 32 bit timestamps. We should modify it to use // 64 bit timestamps, but this is not trivial since we'd end up // changing half the structs in enet. For now, retain 32 bits, but // use an offset so we don't run out of bits. Basically, the first // call of enet_time_get() will always return 1, and follow-up calls // indicate elapsed time since the first call. // // Note that we don't want to return 0 from the first call, in case // some part of enet uses 0 as a special value (meaning time not set // for example). static uint64_t start_time_ns = 0; struct timespec ts; #if defined(CLOCK_MONOTONIC_RAW) clock_gettime(CLOCK_MONOTONIC_RAW, &ts); #else clock_gettime(CLOCK_MONOTONIC, &ts); #endif static const uint64_t ns_in_s = 1000 * 1000 * 1000; static const uint64_t ns_in_ms = 1000 * 1000; uint64_t current_time_ns = ts.tv_nsec + (uint64_t)ts.tv_sec * ns_in_s; // Most of the time we just want to atomically read the start time. We // could just use a single CAS instruction instead of this if, but it // would be slower in the average case. // // Note that statics are auto-initialized to zero, and starting a thread // implies a memory barrier. So we know that whatever thread calls this, // it correctly sees the start_time_ns as 0 initially. uint64_t offset_ns = ENET_ATOMIC_READ(&start_time_ns); if (offset_ns == 0) { // We still need to CAS, since two different threads can get here // at the same time. // // We assume that current_time_ns is > 1ms. // // Set the value of the start_time_ns, such that the first timestamp // is at 1ms. This ensures 0 remains a special value. uint64_t want_value = current_time_ns - 1 * ns_in_ms; uint64_t old_value = ENET_ATOMIC_CAS(&start_time_ns, 0, want_value); offset_ns = old_value == 0 ? want_value : old_value; } uint64_t result_in_ns = current_time_ns - offset_ns; return (enet_uint32)(result_in_ns / ns_in_ms); } #if ENET_IPV6 void enet_inaddr_map4to6(struct in_addr in, struct in6_addr* out) { if (in.s_addr == 0x00000000) { /* 0.0.0.0 */ *out = enet_v6_anyaddr; } else if (in.s_addr == 0xFFFFFFFF) { /* 255.255.255.255 */ *out = enet_v6_noaddr; } else { *out = enet_v4_anyaddr; out->s6_addr[10] = 0xFF; out->s6_addr[11] = 0xFF; out->s6_addr[12] = ((uint8_t *)&in.s_addr)[0]; out->s6_addr[13] = ((uint8_t *)&in.s_addr)[1]; out->s6_addr[14] = ((uint8_t *)&in.s_addr)[2]; out->s6_addr[15] = ((uint8_t *)&in.s_addr)[3]; } } void enet_inaddr_map6to4(const struct in6_addr* in, struct in_addr* out) { memset(out, 0, sizeof(struct in_addr)); ((uint8_t *)&out->s_addr)[0] = in->s6_addr[12]; ((uint8_t *)&out->s_addr)[1] = in->s6_addr[13]; ((uint8_t *)&out->s_addr)[2] = in->s6_addr[14]; ((uint8_t *)&out->s_addr)[3] = in->s6_addr[15]; } int enet_in6addr_lookup_host(const char* name, bool nodns, ENetAddress* out) { struct addrinfo hints, *resultList = NULL, *result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; if (nodns) { hints.ai_flags = AI_NUMERICHOST; /* prevent actual DNS lookups! */ } if (getaddrinfo(name, NULL, &hints, &resultList) != 0) { freeaddrinfo(resultList); return -1; } for (result = resultList; result != NULL; result = result->ai_next) { if (result->ai_addr != NULL) { if (result->ai_family == AF_INET || (result->ai_family == AF_UNSPEC && result->ai_addrlen == sizeof(struct sockaddr_in))) { enet_inaddr_map4to6(((struct sockaddr_in*)result->ai_addr)->sin_addr, &out->host); out->sin6_scope_id = 0; freeaddrinfo(resultList); return 0; } else if (result->ai_family == AF_INET6 || (result->ai_family == AF_UNSPEC && result->ai_addrlen == sizeof(struct sockaddr_in6))) { memcpy(&out->host, &((struct sockaddr_in6*)result->ai_addr)->sin6_addr, sizeof(struct in6_addr)); out->sin6_scope_id = (enet_uint16) ((struct sockaddr_in6*)result->ai_addr)->sin6_scope_id; freeaddrinfo(resultList); return 0; } } } freeaddrinfo(resultList); return -1; } int enet_address_set_host_ip_new(ENetAddress* address, const char* name) { return enet_in6addr_lookup_host(name, true, address); } int enet_address_set_host_new(ENetAddress* address, const char* name) { return enet_in6addr_lookup_host(name, false, address); } int enet_address_get_host_ip_new(const ENetAddress* address, char* name, size_t nameLength) { if (IN6_IS_ADDR_V4MAPPED(&address->host)) { struct in_addr buf; enet_inaddr_map6to4(&address->host, &buf); if (inet_ntop(AF_INET, &buf, name, nameLength) == NULL) { return -1; } } else { if (inet_ntop(AF_INET6, (void*)&address->host, name, nameLength) == NULL) { return -1; } } return 0; } /* enet_address_get_host_ip_new */ int enet_address_get_host_new(const ENetAddress* address, char* name, size_t nameLength) { struct sockaddr_in6 sin; memset(&sin, 0, sizeof(struct sockaddr_in6)); int err; sin.sin6_family = AF_INET6; sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; err = getnameinfo((struct sockaddr*)&sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); if (!err) { if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) { return -1; } return 0; } if (err != EAI_NONAME) { return -1; } return enet_address_get_host_ip_new(address, name, nameLength); } /* enet_address_get_host_new */ #else #endif // =======================================================================// // ! // ! Platform Specific (Unix) // ! // =======================================================================// #ifndef _WIN32 #if defined(__MINGW32__) && defined(ENET_MINGW_COMPAT) // inet_ntop/inet_pton for MinGW from http://mingw-users.1079350.n2.nabble.com/IPv6-getaddrinfo-amp-inet-ntop-td5891996.html const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt) { if (af == AF_INET) { struct sockaddr_in in; memset(&in, 0, sizeof(in)); in.sin_family = AF_INET; memcpy(&in.sin_addr, src, sizeof(struct in_addr)); getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in), dst, cnt, NULL, 0, NI_NUMERICHOST); return dst; } else if (af == AF_INET6) { struct sockaddr_in6 in; memset(&in, 0, sizeof(in)); in.sin6_family = AF_INET6; memcpy(&in.sin6_addr, src, sizeof(struct in_addr6)); getnameinfo((struct sockaddr *)&in, sizeof(struct sockaddr_in6), dst, cnt, NULL, 0, NI_NUMERICHOST); return dst; } return NULL; } #define NS_INADDRSZ 4 #define NS_IN6ADDRSZ 16 #define NS_INT16SZ 2 int inet_pton4(const char* src, char* dst) { uint8_t tmp[NS_INADDRSZ], *tp; int saw_digit = 0; int octets = 0; *(tp = tmp) = 0; int ch; while ((ch = *src++) != '\0') { if (ch >= '0' && ch <= '9') { uint32_t n = *tp * 10 + (ch - '0'); if (saw_digit && *tp == 0) return 0; if (n > 255) return 0; *tp = n; if (!saw_digit) { if (++octets > 4) return 0; saw_digit = 1; } } else if (ch == '.' && saw_digit) { if (octets == 4) return 0; *++tp = 0; saw_digit = 0; } else { return 0; } } if (octets < 4) return 0; memcpy(dst, tmp, NS_INADDRSZ); return 1; } int inet_pton6(const char* src, char* dst) { static const char xdigits[] = "0123456789abcdef"; uint8_t tmp[NS_IN6ADDRSZ]; uint8_t *tp = (uint8_t*)memset(tmp, '\0', NS_IN6ADDRSZ); uint8_t *endp = tp + NS_IN6ADDRSZ; uint8_t *colonp = NULL; /* Leading :: requires some special handling. */ if (*src == ':') { if (*++src != ':') return 0; } const char *curtok = src; int saw_xdigit = 0; uint32_t val = 0; int ch; while ((ch = tolower(*src++)) != '\0') { const char *pch = strchr(xdigits, ch); if (pch != NULL) { val <<= 4; val |= (pch - xdigits); if (val > 0xffff) return 0; saw_xdigit = 1; continue; } if (ch == ':') { curtok = src; if (!saw_xdigit) { if (colonp) return 0; colonp = tp; continue; } else if (*src == '\0') { return 0; } if (tp + NS_INT16SZ > endp) return 0; *tp++ = (uint8_t) (val >> 8) & 0xff; *tp++ = (uint8_t) val & 0xff; saw_xdigit = 0; val = 0; continue; } if (ch == '.' && ((tp + NS_INADDRSZ) <= endp) && inet_pton4(curtok, (char*) tp) > 0) { tp += NS_INADDRSZ; saw_xdigit = 0; break; /* '\0' was seen by inet_pton4(). */ } return 0; } if (saw_xdigit) { if (tp + NS_INT16SZ > endp) return 0; *tp++ = (uint8_t) (val >> 8) & 0xff; *tp++ = (uint8_t) val & 0xff; } if (colonp != NULL) { /* * Since some memmove()'s erroneously fail to handle * overlapping regions, we'll do the shift by hand. */ const int n = tp - colonp; if (tp == endp) return 0; for (int i = 1; i <= n; i++) { endp[-i] = colonp[n - i]; colonp[n - i] = 0; } tp = endp; } if (tp != endp) return 0; memcpy(dst, tmp, NS_IN6ADDRSZ); return 1; } int inet_pton(int af, const char* src, struct in6_addr* dst) { switch (af) { case AF_INET: return inet_pton4(src, (char*)dst); case AF_INET6: return inet_pton6(src, (char*)dst); default: return -1; } } #endif // __MINGW__ int enet_initialize(void) { return 0; } void enet_deinitialize(void) {} enet_uint64 enet_host_random_seed(void) { return (enet_uint64)time(NULL); } #if !ENET_IPV6 int enet_address_set_host_ip(ENetAddress* address, const char* name) { /*#ifdef HAS_INET_PTON*/ if (!inet_pton(AF_INET, name, &address->host)) /*#else if (!inet_aton(name, (struct in_addr*)&address->host)) #endif*/ return -1; return 0; } int enet_address_set_host(ENetAddress* address, const char* name) { /*#ifdef HAS_GETADDRINFO*/ struct addrinfo hints, *resultList = NULL, *result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; if (getaddrinfo(name, NULL, NULL, &resultList) != 0) return -1; for (result = resultList; result != NULL; result = result->ai_next) { if (result->ai_family == AF_INET && result->ai_addr != NULL && result->ai_addrlen >= sizeof(struct sockaddr_in)) { struct sockaddr_in* sin = (struct sockaddr_in*)result->ai_addr; address->host = sin->sin_addr.s_addr; freeaddrinfo(resultList); return 0; } } if (resultList != NULL) freeaddrinfo(resultList); /*#else struct hostent* hostEntry = NULL; #ifdef HAS_GETHOSTBYNAME_R struct hostent hostData; char buffer[2048]; int errnum; #if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) || defined(__GNU__) gethostbyname_r(name, &hostData, buffer, sizeof(buffer), &hostEntry, &errnum); #else hostEntry = gethostbyname_r(name, &hostData, buffer, sizeof(buffer), &errnum); #endif #else hostEntry = gethostbyname(name); #endif if (hostEntry != NULL && hostEntry->h_addrtype == AF_INET) { address->host = *(enet_uint32*)hostEntry->h_addr_list[0]; return 0; } #endif*/ return enet_address_set_host_ip(address, name); } int enet_address_get_host_ip(const ENetAddress* address, char* name, size_t nameLength) { /*#ifdef HAS_INET_NTOP*/ if (inet_ntop(AF_INET, &address->host, name, nameLength) == NULL) /*#else char* addr = inet_ntoa(*(struct in_addr*)&address->host); if (addr != NULL) { size_t addrLen = strlen(addr); if (addrLen >= nameLength) return -1; memcpy(name, addr, addrLen + 1); } else #endif*/ return -1; return 0; } int enet_address_get_host(const ENetAddress* address, char* name, size_t nameLength) { /*#ifdef HAS_GETNAMEINFO*/ struct sockaddr_in sin; int err; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; err = getnameinfo((struct sockaddr*)&sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); if (!err) { if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) return -1; return 0; } if (err != EAI_NONAME) return -1; /*#else struct in_addr in; struct hostent* hostEntry = NULL; #ifdef HAS_GETHOSTBYADDR_R struct hostent hostData; char buffer[2048]; int errnum; in.s_addr = address->host; #if defined(linux) || defined(__linux) || defined(__linux__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) || defined(__GNU__) gethostbyaddr_r((char*)&in, sizeof(struct in_addr), AF_INET, &hostData, buffer, sizeof(buffer), &hostEntry, &errnum); #else hostEntry = gethostbyaddr_r((char*)&in, sizeof(struct in_addr), AF_INET, &hostData, buffer, sizeof(buffer), &errnum); #endif #else in.s_addr = address->host; hostEntry = gethostbyaddr((char*)&in, sizeof(struct in_addr), AF_INET); #endif if (hostEntry != NULL) { size_t hostLen = strlen(hostEntry->h_name); if (hostLen >= nameLength) return -1; memcpy(name, hostEntry->h_name, hostLen + 1); return 0; } #endif*/ return enet_address_get_host_ip(address, name, nameLength); } #elif !defined(ENET_FEATURE_ADDRESS_MAPPING) int enet_address_set_host_ip_old(ENetAddress* address, const char* name) { if (!inet_pton(AF_INET6, name, &address->host)) { return -1; } return 0; } int enet_address_set_host_old(ENetAddress* address, const char* name) { struct addrinfo hints, *resultList = NULL, *result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; if (getaddrinfo(name, NULL, &hints, &resultList) != 0) { return -1; } for (result = resultList; result != NULL; result = result->ai_next) { if (result->ai_addr != NULL && result->ai_addrlen >= sizeof(struct sockaddr_in)) { if (result->ai_family == AF_INET) { struct sockaddr_in * sin = (struct sockaddr_in *) result->ai_addr; ((uint32_t*)&address->host.s6_addr)[0] = 0; ((uint32_t*)&address->host.s6_addr)[1] = 0; ((uint32_t*)&address->host.s6_addr)[2] = htonl(0xffff); ((uint32_t*)&address->host.s6_addr)[3] = sin->sin_addr.s_addr; freeaddrinfo(resultList); return 0; } else if(result->ai_family == AF_INET6) { struct sockaddr_in6 * sin = (struct sockaddr_in6 *)result->ai_addr; address->host = sin->sin6_addr; address->sin6_scope_id = sin->sin6_scope_id; freeaddrinfo(resultList); return 0; } } } freeaddrinfo(resultList); return enet_address_set_host_ip(address, name); } /* enet_address_set_host_old */ int enet_address_get_host_ip_old(const ENetAddress* address, char* name, size_t nameLength) { if (inet_ntop(AF_INET6, &address->host, name, nameLength) == NULL) { return -1; } return 0; } int enet_address_get_host_old(const ENetAddress* address, char* name, size_t nameLength) { struct sockaddr_in6 sin; int err; memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; sin.sin6_port = ENET_HOST_TO_NET_16 (address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; err = getnameinfo((struct sockaddr *) &sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); if (!err) { if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) { return -1; } return 0; } if (err != EAI_NONAME) { return -1; } return enet_address_get_host_ip(address, name, nameLength); } /* enet_address_get_host_old */ #endif int enet_socket_bind(ENetSocket socket, const ENetAddress* address) { #if ENET_IPV6 struct sockaddr_in6 sin; memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; if (address != NULL) { sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; } else { sin.sin6_port = 0; sin.sin6_addr = ENET_HOST_ANY; sin.sin6_scope_id = 0; } if (bind(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)) < 0) { #ifdef ENET_DEBUG LOGW("enet_socket_bind() failed with error {}", errno); #endif return -1; } return 0; #else struct sockaddr_in sin; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; if (address != NULL) { sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; } else { sin.sin_port = 0; sin.sin_addr.s_addr = INADDR_ANY; } if (bind(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) < 0) { #ifdef ENET_DEBUG LOGW("enet_socket_bind() failed with error {}", errno); #endif return -1; } return 0; #endif } int enet_socket_get_address(ENetSocket socket, ENetAddress* address) { #if ENET_IPV6 struct sockaddr_in6 sin; socklen_t sinLength = sizeof(struct sockaddr_in6); if (getsockname(socket, (struct sockaddr*)&sin, &sinLength) == -1) { return -1; } address->host = sin.sin6_addr; address->port = ENET_NET_TO_HOST_16(sin.sin6_port); address->sin6_scope_id = sin.sin6_scope_id; return 0; #else struct sockaddr_in sin; socklen_t sinLength = sizeof(struct sockaddr_in); if (getsockname(socket, (struct sockaddr*)&sin, &sinLength) == -1) { return -1; } address->host = (enet_uint32)sin.sin_addr.s_addr; address->port = ENET_NET_TO_HOST_16(sin.sin_port); return 0; #endif } int enet_socket_listen(ENetSocket socket, int backlog) { return listen(socket, backlog < 0 ? SOMAXCONN : backlog); } ENetSocket enet_socket_create(ENetSocketType type) { #if ENET_IPV6 return socket(PF_INET6, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); #else return socket(PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); #endif } int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) { int result = -1; switch (option) { case ENET_SOCKOPT_NONBLOCK: result = fcntl(socket, F_SETFL, (value ? O_NONBLOCK : 0) | (fcntl(socket, F_GETFL) & ~O_NONBLOCK)); break; case ENET_SOCKOPT_BROADCAST: result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char*)&value, sizeof(int)); break; case ENET_SOCKOPT_REUSEADDR: result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char*)&value, sizeof(int)); break; case ENET_SOCKOPT_RCVBUF: result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_SNDBUF: result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_RCVTIMEO: { struct timeval timeVal; timeVal.tv_sec = value / 1000; timeVal.tv_usec = (value % 1000) * 1000; result = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeVal, sizeof(struct timeval)); break; } case ENET_SOCKOPT_SNDTIMEO: { struct timeval timeVal; timeVal.tv_sec = value / 1000; timeVal.tv_usec = (value % 1000) * 1000; result = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeVal, sizeof(struct timeval)); break; } case ENET_SOCKOPT_NODELAY: result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int)); break; #if ENET_IPV6 case ENET_SOCKOPT_IPV6_V6ONLY: result = setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&value, sizeof(int)); break; #endif } return (result == -1 ? -1 : 0); } /* enet_socket_set_option */ int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int* value) { int result = -1; socklen_t len; switch (option) { case ENET_SOCKOPT_ERROR: len = sizeof(int); result = getsockopt(socket, SOL_SOCKET, SO_ERROR, value, &len); break; } return (result == -1 ? -1 : 0); } int enet_socket_connect(ENetSocket socket, const ENetAddress* address) { #if ENET_IPV6 struct sockaddr_in6 sin; int result; memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; result = connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)); if (result == -1) { if (errno == EINPROGRESS) { return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_connect() failed with error {}", errno); #endif } return result; #else struct sockaddr_in sin; int result; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; result = connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)); if (result == -1) { if (errno == EINPROGRESS) { return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_connect() failed with error {}", errno); #endif } return result; #endif } ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress* address) { #if ENET_IPV6 int result; struct sockaddr_in6 sin; socklen_t sinLength = sizeof(struct sockaddr_in6); result = accept(socket,address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL); if (result == -1) { return ENET_SOCKET_NULL; } if (address != NULL) { address->host = sin.sin6_addr; address->port = ENET_NET_TO_HOST_16 (sin.sin6_port); address->sin6_scope_id = sin.sin6_scope_id; } return result; #else int result; struct sockaddr_in sin; socklen_t sinLength = sizeof(struct sockaddr_in); result = accept(socket, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL); if (result == -1) { return ENET_SOCKET_NULL; } if (address != NULL) { address->host = (enet_uint32)sin.sin_addr.s_addr; address->port = ENET_NET_TO_HOST_16(sin.sin_port); } return result; #endif } int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) { return shutdown(socket, (int)how); } void enet_socket_destroy(ENetSocket socket) { if (socket != -1) { close(socket); } } int enet_socket_send(ENetSocket socket, const ENetAddress* address, const ENetBuffer* buffers, size_t bufferCount) { #if ENET_IPV6 struct msghdr msgHdr; struct sockaddr_in6 sin; int sentLength; memset(&msgHdr, 0, sizeof(struct msghdr)); if (address != NULL) { memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; msgHdr.msg_name = &sin; msgHdr.msg_namelen = sizeof(struct sockaddr_in6); } msgHdr.msg_iov = (struct iovec *) buffers; msgHdr.msg_iovlen = bufferCount; sentLength = sendmsg(socket, &msgHdr, MSG_NOSIGNAL); if (sentLength == -1) { if (errno == EWOULDBLOCK || errno == ENOBUFS) { return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_send() failed with error {}", errno); #endif return -1; } return sentLength; #else struct msghdr msgHdr; struct sockaddr_in sin; int sentLength; memset(&msgHdr, 0, sizeof(struct msghdr)); if (address != NULL) { memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; msgHdr.msg_name = &sin; msgHdr.msg_namelen = sizeof(struct sockaddr_in); } msgHdr.msg_iov = (struct iovec*)buffers; msgHdr.msg_iovlen = bufferCount; sentLength = sendmsg(socket, &msgHdr, MSG_NOSIGNAL); if (sentLength == -1) { if (errno == EWOULDBLOCK || errno == ENOBUFS) { return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_send() failed with error {}", errno); #endif return -1; } return sentLength; #endif } /* enet_socket_send */ int enet_socket_receive(ENetSocket socket, ENetAddress* address, ENetBuffer* buffers, size_t bufferCount) { #if ENET_IPV6 struct msghdr msgHdr; struct sockaddr_in6 sin; int recvLength; memset(&msgHdr, 0, sizeof(struct msghdr)); if (address != NULL) { msgHdr.msg_name = &sin; msgHdr.msg_namelen = sizeof(struct sockaddr_in6); } msgHdr.msg_iov = (struct iovec *) buffers; msgHdr.msg_iovlen = bufferCount; recvLength = recvmsg(socket, &msgHdr, MSG_NOSIGNAL); if (recvLength == -1) { if (errno == EWOULDBLOCK) { return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_receive() failed with error {}", errno); #endif return -1; } if (msgHdr.msg_flags & MSG_TRUNC) { return -1; } if (address != NULL) { address->host = sin.sin6_addr; address->port = ENET_NET_TO_HOST_16(sin.sin6_port); address->sin6_scope_id = sin.sin6_scope_id; } return recvLength; #else struct msghdr msgHdr; struct sockaddr_in sin; int recvLength; memset(&msgHdr, 0, sizeof(struct msghdr)); if (address != NULL) { msgHdr.msg_name = &sin; msgHdr.msg_namelen = sizeof(struct sockaddr_in); } msgHdr.msg_iov = (struct iovec*)buffers; msgHdr.msg_iovlen = bufferCount; recvLength = recvmsg(socket, &msgHdr, MSG_NOSIGNAL); if (recvLength == -1) { switch (errno) { case EWOULDBLOCK: return 0; case EINTR: case EMSGSIZE: return -2; } #ifdef ENET_DEBUG LOGW("enet_socket_receive() failed with error {}", errno); #endif return -1; } #ifdef HAS_MSGHDR_FLAGS if (msgHdr.msg_flags & MSG_TRUNC) return -2; #endif if (address != NULL) { address->host = (enet_uint32)sin.sin_addr.s_addr; address->port = ENET_NET_TO_HOST_16(sin.sin_port); } return recvLength; #endif } /* enet_socket_receive */ int enet_socketset_select(ENetSocket maxSocket, ENetSocketSet* readSet, ENetSocketSet* writeSet, enet_uint32 timeout) { struct timeval timeVal; timeVal.tv_sec = timeout / 1000; timeVal.tv_usec = (timeout % 1000) * 1000; return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal); } int enet_socket_wait(ENetSocket socket, enet_uint32* condition, enet_uint64 timeout) { struct pollfd pollSocket; int pollCount; pollSocket.fd = socket; pollSocket.events = 0; if (*condition & ENET_SOCKET_WAIT_SEND) { pollSocket.events |= POLLOUT; } if (*condition & ENET_SOCKET_WAIT_RECEIVE) { pollSocket.events |= POLLIN; } pollCount = poll(&pollSocket, 1, timeout); if (pollCount < 0) { if (errno == EINTR && *condition & ENET_SOCKET_WAIT_INTERRUPT) { *condition = ENET_SOCKET_WAIT_INTERRUPT; return 0; } return -1; } *condition = ENET_SOCKET_WAIT_NONE; if (pollCount == 0) { return 0; } if (pollSocket.revents & POLLOUT) { *condition |= ENET_SOCKET_WAIT_SEND; } if (pollSocket.revents & POLLIN) { *condition |= ENET_SOCKET_WAIT_RECEIVE; } return 0; } /* enet_socket_wait */ #endif // !_WIN32 // =======================================================================// // ! // ! Platform Specific (Win) // ! // =======================================================================// #ifdef _WIN32 int enet_initialize(void) { WORD versionRequested = MAKEWORD(1, 1); WSADATA wsaData; if (WSAStartup(versionRequested, &wsaData)) { return -1; } if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) { WSACleanup(); return -1; } //timeBeginPeriod(1); return 0; } void enet_deinitialize(void) { //timeEndPeriod(1); WSACleanup(); } enet_uint64 enet_host_random_seed(void) { #ifdef DEATH_TARGET_WINDOWS_RT return (enet_uint64)GetTickCount(); #else return (enet_uint64)timeGetTime(); #endif } #if !ENET_IPV6 int enet_address_set_host_ip_old(ENetAddress* address, const char* name) { enet_uint8 vals[4] = { 0, 0, 0, 0 }; int i; for (i = 0; i < 4; ++i) { const char* next = name + 1; if (*name != '0') { long val = strtol(name, (char**)&next, 10); if (val < 0 || val > 255 || next == name || next - name > 3) return -1; vals[i] = (enet_uint8)val; } if (*next != (i < 3 ? '.' : '\0')) return -1; name = next + 1; } memcpy(&address->host, vals, sizeof(enet_uint32)); return 0; } int enet_address_set_host_old(ENetAddress* address, const char* name) { struct addrinfo hints, *resultList = NULL, *result = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; if (getaddrinfo(name, NULL, NULL, &resultList) != 0) return -1; for (result = resultList; result != NULL; result = result->ai_next) { if (result->ai_family == AF_INET && result->ai_addr != NULL && result->ai_addrlen >= sizeof(struct sockaddr_in)) { struct sockaddr_in* sin = (struct sockaddr_in*)result->ai_addr; address->host = sin->sin_addr.s_addr; freeaddrinfo(resultList); return 0; } } if (resultList != NULL) { freeaddrinfo(resultList); } return 0; } int enet_address_get_host_ip_old(const ENetAddress* address, char* name, size_t nameLength) { if (inet_ntop(AF_INET, &address->host, name, nameLength) == NULL) return -1; return 0; } int enet_address_get_host_old(const ENetAddress* address, char* name, size_t nameLength) { struct sockaddr_in sin; int err; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; err = getnameinfo((struct sockaddr*)&sin, sizeof(sin), name, nameLength, NULL, 0, NI_NAMEREQD); if (!err) { if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) return -1; return 0; } if (err != EAI_NONAME) { return -1; } return 0; } #elif !defined(ENET_FEATURE_ADDRESS_MAPPING) int enet_address_set_host_ip_old(ENetAddress* address, const char* name) { enet_uint8 vals[4] = { 0, 0, 0, 0 }; int i; for (i = 0; i < 4; ++i) { const char* next = name + 1; if (*name != '0') { long val = strtol(name, (char**)&next, 10); if (val < 0 || val > 255 || next == name || next - name > 3) { return -1; } vals[i] = (enet_uint8) val; } if (*next != (i < 3 ? '.' : '\0')) { return -1; } name = next + 1; } memcpy(&address->host, vals, sizeof(enet_uint32)); return 0; } int enet_address_set_host_old(ENetAddress* address, const char* name) { struct hostent* hostEntry = NULL; hostEntry = gethostbyname(name); if (hostEntry == NULL || hostEntry->h_addrtype != AF_INET) { if (!inet_pton(AF_INET6, name, &address->host)) { return -1; } return 0; } ((enet_uint32*)&address->host.s6_addr)[0] = 0; ((enet_uint32*)&address->host.s6_addr)[1] = 0; ((enet_uint32*)&address->host.s6_addr)[2] = htonl(0xffff); ((enet_uint32*)&address->host.s6_addr)[3] = *(enet_uint32 *)hostEntry->h_addr_list[0]; return 0; } int enet_address_get_host_ip_old(const ENetAddress* address, char* name, size_t nameLength) { if (inet_ntop(AF_INET6, (PVOID)&address->host, name, nameLength) == NULL) { return -1; } return 0; } int enet_address_get_host_old(const ENetAddress* address, char* name, size_t nameLength) { struct in6_addr in; struct hostent* hostEntry = NULL; in = address->host; hostEntry = gethostbyaddr((char*)&in, sizeof(struct in6_addr), AF_INET6); if (hostEntry == NULL) { return enet_address_get_host_ip(address, name, nameLength); } else { size_t hostLen = strlen(hostEntry->h_name); if (hostLen >= nameLength) { return -1; } memcpy(name, hostEntry->h_name, hostLen + 1); } return 0; } #endif int enet_socket_bind(ENetSocket socket, const ENetAddress* address) { #if ENET_IPV6 struct sockaddr_in6 sin; memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; if (address != NULL) { sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; } else { sin.sin6_port = 0; sin.sin6_addr = in6addr_any; sin.sin6_scope_id = 0; } if (bind(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)) == SOCKET_ERROR) { #ifdef ENET_DEBUG int error = WSAGetLastError(); LOGW("enet_socket_bind() failed with error {}", error); #endif return -1; } return 0; #else struct sockaddr_in sin; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; if (address != NULL) { sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; } else { sin.sin_port = 0; sin.sin_addr.s_addr = INADDR_ANY; } if (bind(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) == SOCKET_ERROR) { #ifdef ENET_DEBUG int error = WSAGetLastError(); LOGW("enet_socket_bind() failed with error {}", error); #endif return -1; } return 0; #endif } int enet_socket_get_address(ENetSocket socket, ENetAddress* address) { #if ENET_IPV6 struct sockaddr_in6 sin; int sinLength = sizeof(struct sockaddr_in6); if (getsockname(socket, (struct sockaddr *) &sin, &sinLength) == -1) { return -1; } address->host = sin.sin6_addr; address->port = ENET_NET_TO_HOST_16(sin.sin6_port); address->sin6_scope_id = sin.sin6_scope_id; return 0; #else struct sockaddr_in sin; int sinLength = sizeof(struct sockaddr_in); if (getsockname(socket, (struct sockaddr*)&sin, &sinLength) == -1) { return -1; } address->host = (enet_uint32)sin.sin_addr.s_addr; address->port = ENET_NET_TO_HOST_16(sin.sin_port); return 0; #endif } int enet_socket_listen(ENetSocket socket, int backlog) { return listen(socket, backlog < 0 ? SOMAXCONN : backlog) == SOCKET_ERROR ? -1 : 0; } ENetSocket enet_socket_create(ENetSocketType type) { #if ENET_IPV6 return socket(PF_INET6, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); #else return socket(PF_INET, type == ENET_SOCKET_TYPE_DATAGRAM ? SOCK_DGRAM : SOCK_STREAM, 0); #endif } int enet_socket_set_option(ENetSocket socket, ENetSocketOption option, int value) { int result = SOCKET_ERROR; switch (option) { case ENET_SOCKOPT_NONBLOCK: { u_long nonBlocking = (u_long) value; result = ioctlsocket(socket, FIONBIO, &nonBlocking); break; } case ENET_SOCKOPT_BROADCAST: result = setsockopt(socket, SOL_SOCKET, SO_BROADCAST, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_REUSEADDR: result = setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_RCVBUF: result = setsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_SNDBUF: result = setsockopt(socket, SOL_SOCKET, SO_SNDBUF, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_RCVTIMEO: result = setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_SNDTIMEO: result = setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&value, sizeof(int)); break; case ENET_SOCKOPT_NODELAY: result = setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *)&value, sizeof(int)); break; #if ENET_IPV6 case ENET_SOCKOPT_IPV6_V6ONLY: result = setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&value, sizeof(int)); break; #endif default: break; } return (result == SOCKET_ERROR ? -1 : 0); } /* enet_socket_set_option */ int enet_socket_get_option(ENetSocket socket, ENetSocketOption option, int* value) { int result = SOCKET_ERROR, len; switch (option) { case ENET_SOCKOPT_ERROR: len = sizeof(int); result = getsockopt(socket, SOL_SOCKET, SO_ERROR, (char*)value, &len); break; } return (result == SOCKET_ERROR ? -1 : 0); } int enet_socket_connect(ENetSocket socket, const ENetAddress* address) { #if ENET_IPV6 struct sockaddr_in6 sin; int result; memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; result = connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in6)); if (result == SOCKET_ERROR) { int error = WSAGetLastError(); #ifdef ENET_DEBUG LOGW("enet_socket_connect() failed with error {}", error); #endif if (error != WSAEWOULDBLOCK) { return -1; } } return 0; #else struct sockaddr_in sin; int result; memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; result = connect(socket, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)); if (result == SOCKET_ERROR) { int error = WSAGetLastError(); #ifdef ENET_DEBUG LOGW("enet_socket_connect() failed with error {}", error); #endif if (error != WSAEWOULDBLOCK) { return -1; } } return 0; #endif } ENetSocket enet_socket_accept(ENetSocket socket, ENetAddress* address) { #if ENET_IPV6 SOCKET result; struct sockaddr_in6 sin; int sinLength = sizeof(struct sockaddr_in6); result = accept(socket, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL); if (result == INVALID_SOCKET) { return ENET_SOCKET_NULL; } if (address != NULL) { address->host = sin.sin6_addr; address->port = ENET_NET_TO_HOST_16(sin.sin6_port); address->sin6_scope_id = sin.sin6_scope_id; } return result; #else SOCKET result; struct sockaddr_in sin; int sinLength = sizeof(struct sockaddr_in); result = accept(socket, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL); if (result == INVALID_SOCKET) { return ENET_SOCKET_NULL; } if (address != NULL) { address->host = (enet_uint32)sin.sin_addr.s_addr; address->port = ENET_NET_TO_HOST_16(sin.sin_port); } return result; #endif } int enet_socket_shutdown(ENetSocket socket, ENetSocketShutdown how) { return shutdown(socket, (int)how) == SOCKET_ERROR ? -1 : 0; } void enet_socket_destroy(ENetSocket socket) { if (socket != INVALID_SOCKET) { closesocket(socket); } } int enet_socket_send(ENetSocket socket, const ENetAddress* address, const ENetBuffer* buffers, size_t bufferCount) { #if ENET_IPV6 struct sockaddr_in6 sin; DWORD sentLength; if (address != NULL) { memset(&sin, 0, sizeof(struct sockaddr_in6)); sin.sin6_family = AF_INET6; sin.sin6_port = ENET_HOST_TO_NET_16(address->port); sin.sin6_addr = address->host; sin.sin6_scope_id = address->sin6_scope_id; } if (WSASendTo(socket, (LPWSABUF)buffers, (DWORD)bufferCount, &sentLength, 0, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? sizeof(struct sockaddr_in6) : 0, NULL, NULL) == SOCKET_ERROR ) { int error = WSAGetLastError(); switch (error) { case WSAEWOULDBLOCK: case WSAENOBUFS: return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_send() failed with error {}", error); #endif return -1; } return (int)sentLength; #else struct sockaddr_in sin; DWORD sentLength = 0; if (address != NULL) { memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = ENET_HOST_TO_NET_16(address->port); sin.sin_addr.s_addr = address->host; } if (WSASendTo(socket, (LPWSABUF)buffers, (DWORD)bufferCount, &sentLength, 0, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? sizeof(struct sockaddr_in) : 0, NULL, NULL) == SOCKET_ERROR ) { int error = WSAGetLastError(); switch (error) { case WSAEWOULDBLOCK: case WSAENOBUFS: return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_send() failed with error {}", error); #endif return -1; } return (int)sentLength; #endif } int enet_socket_receive(ENetSocket socket, ENetAddress* address, ENetBuffer* buffers, size_t bufferCount) { #if ENET_IPV6 INT sinLength = sizeof(struct sockaddr_in6); DWORD flags = 0, recvLength; struct sockaddr_in6 sin; if (WSARecvFrom(socket, (LPWSABUF)buffers, (DWORD)bufferCount, &recvLength, &flags, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL, NULL, NULL) == SOCKET_ERROR ) { int error = WSAGetLastError(); switch (error) { case WSAEWOULDBLOCK: case WSAECONNRESET: return 0; } #ifdef ENET_DEBUG LOGW("enet_socket_receive() failed with error {}", error); #endif return -1; } if (flags & MSG_PARTIAL) { return -1; } if (address != NULL) { address->host = sin.sin6_addr; address->port = ENET_NET_TO_HOST_16(sin.sin6_port); address->sin6_scope_id = sin.sin6_scope_id; } return (int)recvLength; #else INT sinLength = sizeof(struct sockaddr_in); DWORD flags = 0, recvLength = 0; struct sockaddr_in sin; if (WSARecvFrom(socket, (LPWSABUF)buffers, (DWORD)bufferCount, &recvLength, &flags, address != NULL ? (struct sockaddr*)&sin : NULL, address != NULL ? &sinLength : NULL, NULL, NULL) == SOCKET_ERROR ) { int error = WSAGetLastError(); switch (error) { case WSAEWOULDBLOCK: case WSAECONNRESET: return 0; case WSAEINTR: case WSAEMSGSIZE: return -2; } #ifdef ENET_DEBUG LOGW("enet_socket_receive() failed with error {}", error); #endif return -1; } if (flags & MSG_PARTIAL) { return -2; } if (address != NULL) { address->host = (enet_uint32)sin.sin_addr.s_addr; address->port = ENET_NET_TO_HOST_16(sin.sin_port); } return (int)recvLength; #endif } /* enet_socket_receive */ int enet_socketset_select(ENetSocket maxSocket, ENetSocketSet* readSet, ENetSocketSet* writeSet, enet_uint32 timeout) { struct timeval timeVal; timeVal.tv_sec = timeout / 1000; timeVal.tv_usec = (timeout % 1000) * 1000; return select(maxSocket + 1, readSet, writeSet, NULL, &timeVal); } int enet_socket_wait(ENetSocket socket, enet_uint32* condition, enet_uint64 timeout) { fd_set readSet, writeSet; struct timeval timeVal; int selectCount; timeVal.tv_sec = timeout / 1000; timeVal.tv_usec = (timeout % 1000) * 1000; FD_ZERO(&readSet); FD_ZERO(&writeSet); if (*condition & ENET_SOCKET_WAIT_SEND) { FD_SET(socket, &writeSet); } if (*condition & ENET_SOCKET_WAIT_RECEIVE) { FD_SET(socket, &readSet); } selectCount = select(socket + 1, &readSet, &writeSet, NULL, &timeVal); if (selectCount < 0) { return -1; } *condition = ENET_SOCKET_WAIT_NONE; if (selectCount == 0) { return 0; } if (FD_ISSET(socket, &writeSet)) { *condition |= ENET_SOCKET_WAIT_SEND; } if (FD_ISSET(socket, &readSet)) { *condition |= ENET_SOCKET_WAIT_RECEIVE; } return 0; } /* enet_socket_wait */ #endif // _WIN32 #ifdef __cplusplus } #endif #endif // ENET_IMPLEMENTATION #endif // ENET_INCLUDE_H deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/Backends/ifaddrs-android.h000066400000000000000000000147771512772601700307770ustar00rootroot00000000000000/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef IFADDRS_ANDROID_H_included #define IFADDRS_ANDROID_H_included #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Source-compatible subset of the BSD struct. struct ifaddrs { // Pointer to next struct in list, or NULL at end. ifaddrs* ifa_next; // Interface name. char* ifa_name; // Interface flags. unsigned int ifa_flags; // Interface network address. sockaddr* ifa_addr; // Interface netmask. sockaddr* ifa_netmask; ifaddrs(ifaddrs* next) : ifa_next(next), ifa_name(NULL), ifa_flags(0), ifa_addr(NULL), ifa_netmask(NULL) {} ~ifaddrs() { delete ifa_next; delete[] ifa_name; delete ifa_addr; delete ifa_netmask; } // Sadly, we can't keep the interface index for portability with BSD. // We'll have to keep the name instead, and re-query the index when // we need it later. bool setNameAndFlagsByIndex(int interfaceIndex) { // Get the name. char buf[IFNAMSIZ]; char* name = if_indextoname(interfaceIndex, buf); if (name == NULL) { return false; } ifa_name = new char[strlen(name) + 1]; strcpy(ifa_name, name); // Get the flags. int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd == -1) { return false; } ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, name); int rc = ioctl(fd, SIOCGIFFLAGS, &ifr); close(fd); if (rc == -1) { return false; } ifa_flags = ifr.ifr_flags; return true; } // Netlink gives us the address family in the header, and the // sockaddr_in or sockaddr_in6 bytes as the payload. We need to // stitch the two bits together into the sockaddr that's part of // our portable interface. void setAddress(int family, void* data, size_t byteCount) { // Set the address proper... sockaddr_storage* ss = new sockaddr_storage; memset(ss, 0, sizeof(*ss)); ifa_addr = reinterpret_cast(ss); ss->ss_family = family; uint8_t* dst = sockaddrBytes(family, ss); memcpy(dst, data, byteCount); } // Netlink gives us the prefix length as a bit count. We need to turn // that into a BSD-compatible netmask represented by a sockaddr*. void setNetmask(int family, size_t prefixLength) { // ...and work out the netmask from the prefix length. sockaddr_storage* ss = new sockaddr_storage; memset(ss, 0, sizeof(*ss)); ifa_netmask = reinterpret_cast(ss); ss->ss_family = family; uint8_t* dst = sockaddrBytes(family, ss); memset(dst, 0xff, prefixLength / 8); if ((prefixLength % 8) != 0) { dst[prefixLength/8] = (0xff << (8 - (prefixLength % 8))); } } // Returns a pointer to the first byte in the address data (which is // stored in network byte order). uint8_t* sockaddrBytes(int family, sockaddr_storage* ss) { if (family == AF_INET) { sockaddr_in* ss4 = reinterpret_cast(ss); return reinterpret_cast(&ss4->sin_addr); } else if (family == AF_INET6) { sockaddr_in6* ss6 = reinterpret_cast(ss); return reinterpret_cast(&ss6->sin6_addr); } return NULL; } private: // Disallow copy and assignment. ifaddrs(const ifaddrs&); void operator=(const ifaddrs&); }; // FIXME: use iovec instead. struct addrReq_struct { nlmsghdr netlinkHeader; ifaddrmsg msg; }; inline bool sendNetlinkMessage(int fd, const void* data, size_t byteCount) { ssize_t sentByteCount = TEMP_FAILURE_RETRY(send(fd, data, byteCount, 0)); return (sentByteCount == static_cast(byteCount)); } inline ssize_t recvNetlinkMessage(int fd, char* buf, size_t byteCount) { return TEMP_FAILURE_RETRY(recv(fd, buf, byteCount, 0)); } // Source-compatible with the BSD function. inline int getifaddrs(ifaddrs** result) { // Simplify cleanup for callers. *result = NULL; // Create a netlink socket. int fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); if (fd < 0) { return -1; } // Ask for the address information. addrReq_struct addrRequest; memset(&addrRequest, 0, sizeof(addrRequest)); addrRequest.netlinkHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH; addrRequest.netlinkHeader.nlmsg_type = RTM_GETADDR; addrRequest.netlinkHeader.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(addrRequest))); addrRequest.msg.ifa_family = AF_UNSPEC; // All families. addrRequest.msg.ifa_index = 0; // All interfaces. if (!sendNetlinkMessage(fd, &addrRequest, addrRequest.netlinkHeader.nlmsg_len)) { close(fd); return -1; } // Read the responses. Death::Containers::SmallVector buf(65536); ssize_t bytesRead; while ((bytesRead = recvNetlinkMessage(fd, &buf[0], buf.size())) > 0) { nlmsghdr* hdr = reinterpret_cast(&buf[0]); for (; NLMSG_OK(hdr, (size_t)bytesRead); hdr = NLMSG_NEXT(hdr, bytesRead)) { switch (hdr->nlmsg_type) { case NLMSG_DONE: close(fd); return 0; case NLMSG_ERROR: close(fd); return -1; case RTM_NEWADDR: { ifaddrmsg* address = reinterpret_cast(NLMSG_DATA(hdr)); rtattr* rta = IFA_RTA(address); size_t ifaPayloadLength = IFA_PAYLOAD(hdr); while (RTA_OK(rta, ifaPayloadLength)) { if (rta->rta_type == IFA_LOCAL) { int family = address->ifa_family; if (family == AF_INET || family == AF_INET6) { *result = new ifaddrs(*result); if (!(*result)->setNameAndFlagsByIndex(address->ifa_index)) { close(fd); return -1; } (*result)->setAddress(family, RTA_DATA(rta), RTA_PAYLOAD(rta)); (*result)->setNetmask(family, address->ifa_prefixlen); } } rta = RTA_NEXT(rta, ifaPayloadLength); } } break; } } } // We only get here if recv fails before we see a NLMSG_DONE. close(fd); return -1; } // Source-compatible with the BSD function. inline void freeifaddrs(ifaddrs* addresses) { delete addresses; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/ConnectionResult.cpp000066400000000000000000000006621512772601700300500ustar00rootroot00000000000000#include "ConnectionResult.h" #if defined(WITH_MULTIPLAYER) #include "../../Main.h" namespace Jazz2::Multiplayer { ConnectionResult::ConnectionResult(Reason reason) : FailureReason(reason) { } ConnectionResult::ConnectionResult(bool success) : FailureReason(success ? (Reason)UINT32_MAX : Reason::Unknown) { } bool ConnectionResult::IsSuccessful() const { return FailureReason == (Reason)UINT32_MAX; } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/ConnectionResult.h000066400000000000000000000011151512772601700275070ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "Reason.h" namespace Jazz2::Multiplayer { /** @brief Describes a connection result of @ref INetworkHandler::OnPeerConnected() */ struct ConnectionResult { /** @brief Failure reason if the connection was not successful */ Reason FailureReason; ConnectionResult(Reason reason); ConnectionResult(bool success); explicit operator bool() const { return IsSuccessful(); } /** @brief Returns `true` if the connection was successful */ bool IsSuccessful() const; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/INetworkHandler.h000066400000000000000000000017021512772601700272530ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "ConnectionResult.h" #include "Peer.h" #include "Reason.h" #include using namespace Death::Containers; namespace Jazz2::Multiplayer { /** @brief Interface to handle incomming network requests @experimental */ class INetworkHandler { public: /** @brief Called when a peer connects to the local server or the local client connects to a server */ virtual ConnectionResult OnPeerConnected(const Peer& peer, std::uint32_t clientData) = 0; /** @brief Called when a peer disconnects from the local server or the local client disconnects from a server */ virtual void OnPeerDisconnected(const Peer& peer, Reason reason) = 0; /** @brief Called when a packet is received */ virtual void OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t packetType, ArrayView data) = 0; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/MpGameMode.h000066400000000000000000000010041512772601700261610ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) namespace Jazz2::Multiplayer { /** @brief Multiplayer game mode */ enum class MpGameMode { Unknown = 0, /**< Unspecified */ Battle, /**< Battle */ TeamBattle, /**< Team Battle */ Race, /**< Race */ TeamRace, /**< Team Race */ TreasureHunt, /**< Treasure Hunt */ TeamTreasureHunt, /**< Team Treasure Hunt */ CaptureTheFlag, /**< Capture The Flag */ Cooperation /**< Cooperation */ }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/MpLevelHandler.cpp000066400000000000000000007155641512772601700274320ustar00rootroot00000000000000#include "MpLevelHandler.h" #if defined(WITH_MULTIPLAYER) #include "PacketTypes.h" #include "../ContentResolver.h" #include "../PreferencesCache.h" #include "../UI/InGameConsole.h" #include "../UI/Multiplayer/MpHUD.h" #include "../UI/Multiplayer/MpInGameCanvasLayer.h" #include "../UI/Multiplayer/MpInGameLobby.h" #if defined(WITH_ANGELSCRIPT) # include "../Scripting/LevelScriptLoader.h" #endif #include "../../nCine/Application.h" #include "../../nCine/I18n.h" #include "../../nCine/Base/Random.h" #include "../../nCine/Primitives/Half.h" #include "../Actors/Player.h" #include "../Actors/Multiplayer/LocalPlayerOnServer.h" #include "../Actors/Multiplayer/RemotablePlayer.h" #include "../Actors/Multiplayer/RemotePlayerOnServer.h" #include "../Actors/Multiplayer/RemoteActor.h" #include "../Actors/Enemies/Bosses/BossBase.h" #include "../Actors/Environment/AirboardGenerator.h" #include "../Actors/Environment/SteamNote.h" #include "../Actors/Environment/SwingingVine.h" #include "../Actors/Solid/Bridge.h" #include "../Actors/Solid/MovingPlatform.h" #include "../Actors/Solid/PinballBumper.h" #include "../Actors/Solid/PinballPaddle.h" #include "../Actors/Solid/Pole.h" #include "../Actors/Solid/SpikeBall.h" #include "../Actors/Weapons/ElectroShot.h" #include "../Actors/Weapons/ShotBase.h" #include "../Actors/Weapons/TNT.h" #include #include #include #include #include #include #include #include #include using namespace Death::IO::Compression; using namespace nCine; using namespace Jazz2::Actors::Multiplayer; namespace Jazz2::Multiplayer { // The first 15 positions are awarded with points static const std::uint32_t PointsPerPosition[] = { 20, 17, 15, 13, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 }; // TODO: levelState is unused, it needs to be set after LevelState::InitialUpdatePending is processed MpLevelHandler::MpLevelHandler(IRootController* root, NetworkManager* networkManager, MpLevelHandler::LevelState levelState, bool enableLedgeClimb) : LevelHandler(root), _networkManager(networkManager), _updateTimeLeft(1.0f), _gameTimeLeft(0.0f), _levelState(LevelState::InitialUpdatePending), _forceResyncPending(true), _enableSpawning(true), _lastSpawnedActorId(-1), _waitingForPlayerCount(0), _lastUpdated(0), _seqNumWarped(0), _suppressRemoting(false), _ignorePackets(false), _enableLedgeClimb(enableLedgeClimb), _controllableExternal(true), _autoWeightTreasure(false), _activePoll(VoteType::None), _activePollTimeLeft(0.0f), _recalcPositionInRoundTime(0.0f), _limitCameraLeft(0), _limitCameraWidth(0), _totalTreasureCount(0) #if defined(DEATH_DEBUG) , _debugAverageUpdatePacketSize(0) #endif #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) , _plotIndex(0), _actorsMaxCount(0.0f), _actorsCount{}, _remoteActorsCount{}, _remotingActorsCount{}, _mirroredActorsCount{}, _updatePacketMaxSize(0.0f), _updatePacketSize{}, _compressedUpdatePacketSize{} #endif { _isServer = (networkManager->GetState() == NetworkState::Listening); } MpLevelHandler::~MpLevelHandler() { } bool MpLevelHandler::Initialize(const LevelInitialization& levelInit) { DEATH_DEBUG_ASSERT(!levelInit.IsLocalSession); _suppressRemoting = true; bool initialized = LevelHandler::Initialize(levelInit); _suppressRemoting = false; if (!initialized) { return false; } InitializeRequiredAssets(); if (_isServer) { // Reserve first 255 indices for players auto& serverConfig = _networkManager->GetServerConfiguration(); _lastSpawnedActorId = UINT8_MAX; _autoWeightTreasure = (serverConfig.TotalTreasureCollected == 0); for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { peerDesc->LevelState = PeerLevelState::ValidatingAssets; peerDesc->LastUpdated = 0; if (peerDesc->RemotePeer) { peerDesc->Player = nullptr; } } } auto& resolver = ContentResolver::Get(); resolver.PreloadMetadataAsync("Interactive/PlayerJazz"_s); resolver.PreloadMetadataAsync("Interactive/PlayerSpaz"_s); resolver.PreloadMetadataAsync("Interactive/PlayerLori"_s); _inGameCanvasLayer = std::make_unique(this); _inGameCanvasLayer->setParent(_rootNode.get()); _inGameLobby = std::make_unique(this); return true; } bool MpLevelHandler::IsLocalSession() const { return false; } bool MpLevelHandler::IsServer() const { return _isServer; } bool MpLevelHandler::IsPausable() const { return false; } bool MpLevelHandler::CanActivateSugarRush() const { const auto& serverConfig = _networkManager->GetServerConfiguration(); return (serverConfig.GameMode == MpGameMode::Cooperation); } bool MpLevelHandler::CanEventDisappear(EventType eventType) const { if (eventType == EventType::Gem) { const auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt) { return false; } } return true; } float MpLevelHandler::GetHurtInvulnerableTime() const { const auto& serverConfig = _networkManager->GetServerConfiguration(); return (serverConfig.GameMode == MpGameMode::Cooperation ? 180.0f : 80.0f); } float MpLevelHandler::GetDefaultAmbientLight() const { // TODO: Remove this override return LevelHandler::GetDefaultAmbientLight(); } void MpLevelHandler::SetAmbientLight(Actors::Player* player, float value) { if (_isServer) { if (auto* remotePlayerOnServer = runtime_cast(player)) { // TODO: Send it to remote peer return; } } LevelHandler::SetAmbientLight(player, value); } void MpLevelHandler::OnBeginFrame() { LevelHandler::OnBeginFrame(); if (_isServer) { // Send pending SFX for (const auto& sfx : _pendingSfx) { std::uint32_t actorId; { std::unique_lock lock(_lock); auto it = _remotingActors.find(sfx.Actor); if (it == _remotingActors.end()) { continue; } actorId = it->second.ActorID; } MemoryStream packet(12 + sfx.Identifier.size()); packet.WriteVariableUint32(actorId); // TODO: sourceRelative // TODO: looping packet.WriteValue(sfx.Gain); packet.WriteValue(sfx.Pitch); packet.WriteVariableUint32((std::uint32_t)sfx.Identifier.size()); packet.Write(sfx.Identifier.data(), (std::uint32_t)sfx.Identifier.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlaySfx, packet); } _pendingSfx.clear(); } else { auto& input = _playerInputs[0]; if (input.PressedActions != input.PressedActionsLast) { MemoryStream packet(12); packet.WriteVariableUint32(_lastSpawnedActorId); packet.WriteVariableUint64(_console->IsVisible() ? 0 : input.PressedActions); _networkManager->SendTo(AllPeers, NetworkChannel::UnreliableUpdates, (std::uint8_t)ClientPacketType::PlayerKeyPress, packet); } } } void MpLevelHandler::OnEndFrame() { LevelHandler::OnEndFrame(); float timeMult = theApplication().GetTimeMult(); std::uint32_t frameCount = theApplication().GetFrameCount(); auto& serverConfig = _networkManager->GetServerConfiguration(); if (_isServer) { // Update last pressed keys only if it wasn't done this frame yet (because of PlayerKeyPress packet) for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (auto* remotePlayerOnServer = runtime_cast(peerDesc->Player)) { if (remotePlayerOnServer->UpdatedFrame != frameCount) { remotePlayerOnServer->UpdatedFrame = frameCount; remotePlayerOnServer->PressedKeysLast = remotePlayerOnServer->PressedKeys; } if (remotePlayerOnServer->PressedKeys == 0) { peerDesc->IdleElapsedFrames += timeMult; if (serverConfig.IdleKickTimeSecs > 0 && serverConfig.IdleKickTimeSecs <= (std::int32_t)(peerDesc->IdleElapsedFrames * FrameTimer::SecondsPerFrame)) { peerDesc->IdleElapsedFrames = 0.0f; _networkManager->Kick(peer, Reason::Idle); } } else { peerDesc->IdleElapsedFrames = 0.0f; } } } if (_activePoll != VoteType::None) { _activePollTimeLeft -= timeMult; if (_activePollTimeLeft <= 0.0f) { EndActivePoll(); } } } _updateTimeLeft -= timeMult; _gameTimeLeft -= timeMult; if (_updateTimeLeft < 0.0f) { _updateTimeLeft = FrameTimer::FramesPerSecond / UpdatesPerSecond; switch (_levelState) { case LevelState::InitialUpdatePending: { LOGD("[MP] Level \"{}\" is ready", _levelName); if (_isServer) { MemoryStream packet; InitializeValidateAssetsPacket(packet); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->IsAuthenticated); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ValidateAssets, packet); if (serverConfig.GameMode == MpGameMode::Cooperation) { // Skip pre-game and countdown in cooperation _levelState = LevelState::Running; } else { _levelState = LevelState::PreGame; _gameTimeLeft = serverConfig.PreGameSecs * FrameTimer::FramesPerSecond; ShowAlertToAllPlayers(_("\n\nThe game will begin shortly!")); } } else { _levelState = LevelState::Running; std::uint8_t flags = 0; if (PreferencesCache::EnableLedgeClimb) { flags |= 0x02; } MemoryStream packet(1); packet.WriteValue(flags); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::LevelReady, packet); } break; } case LevelState::PreGame: { if (_isServer && _gameTimeLeft <= 0.0f) { // TODO: Check all players are ready if (_players.size() >= serverConfig.MinPlayerCount) { _levelState = LevelState::Countdown3; _gameTimeLeft = FrameTimer::FramesPerSecond; SetControllableToAllPlayers(false); WarpAllPlayersToStart(); SynchronizeGameMode(); ResetAllPlayerStats(); static_cast(_hud.get())->ShowCountdown(3); LOGI("[MP] Starting round..."); } else { _levelState = LevelState::WaitingForMinPlayers; _waitingForPlayerCount = (std::int32_t)serverConfig.MinPlayerCount - (std::int32_t)_players.size(); } SendLevelStateToAllPlayers(); } break; } case LevelState::WaitingForMinPlayers: { if (_isServer) { // TODO: Check all players are ready if (_waitingForPlayerCount <= 0) { _levelState = LevelState::Countdown3; _gameTimeLeft = FrameTimer::FramesPerSecond; SetControllableToAllPlayers(false); WarpAllPlayersToStart(); SynchronizeGameMode(); ResetAllPlayerStats(); static_cast(_hud.get())->ShowCountdown(3); SendLevelStateToAllPlayers(); LOGI("[MP] Starting round..."); } } break; } case LevelState::Countdown3: { if (_isServer && _gameTimeLeft <= 0.0f) { _levelState = LevelState::Countdown2; _gameTimeLeft = FrameTimer::FramesPerSecond; static_cast(_hud.get())->ShowCountdown(2); SendLevelStateToAllPlayers(); RollbackLevelState(); } break; } case LevelState::Countdown2: { if (_isServer && _gameTimeLeft <= 0.0f) { _levelState = LevelState::Countdown1; _gameTimeLeft = FrameTimer::FramesPerSecond; static_cast(_hud.get())->ShowCountdown(1); SendLevelStateToAllPlayers(); } break; } case LevelState::Countdown1: { if (_isServer && _gameTimeLeft <= 0.0f) { _levelState = LevelState::Running; _gameTimeLeft = serverConfig.MaxGameTimeSecs * FrameTimer::FramesPerSecond; _recalcPositionInRoundTime = FrameTimer::FramesPerSecond; SetControllableToAllPlayers(true); static_cast(_hud.get())->ShowCountdown(0); SendLevelStateToAllPlayers(); CalculatePositionInRound(true); for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); mpPlayer->GetPeerDescriptor()->LapStarted = TimeStamp::now(); // The player is invulnerable for a short time after starting a round player->SetInvulnerability(serverConfig.SpawnInvulnerableSecs * FrameTimer::FramesPerSecond, Actors::Player::InvulnerableType::Blinking); if (peerDesc->RemotePeer) { MemoryStream packet(13); packet.WriteValue((std::uint8_t)PlayerPropertyType::Laps); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableUint32(peerDesc->Laps); packet.WriteVariableUint32(serverConfig.TotalLaps); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } break; } case LevelState::Running: { if (_isServer) { if (serverConfig.GameMode == MpGameMode::Race || serverConfig.GameMode == MpGameMode::TeamRace) { _recalcPositionInRoundTime -= timeMult; if (_recalcPositionInRoundTime <= 0.0f) { _recalcPositionInRoundTime = FrameTimer::FramesPerSecond; CalculatePositionInRound(); } } if (serverConfig.GameMode != MpGameMode::Cooperation && serverConfig.MaxGameTimeSecs > 0 && _gameTimeLeft <= 0.0f) { EndGameOnTimeOut(); } } break; } case LevelState::Ending: { if (_isServer && _gameTimeLeft <= 0.0f) { if (serverConfig.Playlist.size() > 1) { serverConfig.PlaylistIndex++; if (serverConfig.PlaylistIndex >= serverConfig.Playlist.size()) { serverConfig.PlaylistIndex = 0; if (serverConfig.RandomizePlaylist) { Random().Shuffle(serverConfig.Playlist); } } ApplyFromPlaylist(); } else { // TODO: Reload the level LevelInitialization levelInit; PrepareNextLevelInitialization(levelInit); levelInit.LevelName = _levelName; levelInit.LastExitType = ExitType::Normal; HandleLevelChange(std::move(levelInit)); } } break; } } #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) _plotIndex = (_plotIndex + 1) % PlotValueCount; _actorsCount[_plotIndex] = _actors.size(); _actorsMaxCount = std::max(_actorsMaxCount, _actorsCount[_plotIndex]); _remoteActorsCount[_plotIndex] = 0; _mirroredActorsCount[_plotIndex] = 0; for (auto& actor : _remoteActors) { if (actor.second == nullptr) { continue; } bool isMirrored = (_isServer ? ActorShouldBeMirrored(actor.second.get()) : !runtime_cast(actor.second)); if (isMirrored) { _mirroredActorsCount[_plotIndex]++; } else { _remoteActorsCount[_plotIndex]++; } } _remotingActorsCount[_plotIndex] = _remotingActors.size(); #endif if (_isServer) { if (_networkManager->HasInboundConnections()) { std::uint32_t actorCount = (std::uint32_t)(_players.size() + _remotingActors.size()); MemoryStream packet(8 + actorCount * 24); packet.WriteVariableUint32(_lastUpdated); packet.WriteVariableUint64((std::uint64_t)_elapsedFrames); packet.WriteVariableUint32((actorCount << 1) | (_forceResyncPending ? 1 : 0)); for (Actors::Player* player : _players) { auto* mpPlayer = static_cast(player); /*Vector2f pos; auto it = _playerStates.find(player->_playerIndex); if (it != _playerStates.end()) { // Remote players pos = player->_pos; // TODO: This should be WarpUpdatesLeft if (it->second.WarpTimeLeft > 0.0f) { it->second.WarpTimeLeft -= timeMult; if (it->second.WarpTimeLeft <= 0.0f) { it->second.WarpTimeLeft = 0.0f; LOGW("Player warped without permission (possible cheating attempt)"); } } else if (it->second.WarpTimeLeft < 0.0f) { it->second.WarpTimeLeft += timeMult; if (it->second.WarpTimeLeft >= 0.0f) { it->second.WarpTimeLeft = 0.0f; LOGW("Player failed to warp in time (possible cheating attempt)"); } } } else { // Local players pos = player->_pos; }*/ Vector2f pos = player->_pos; packet.WriteVariableUint32(player->_playerIndex); std::uint8_t flags = 0x01 | 0x02; // PositionChanged | AnimationChanged if (player->_renderer.isDrawEnabled()) { flags |= 0x04; } if (player->_renderer.AnimPaused) { flags |= 0x08; } if (player->_renderer.isFlippedX()) { flags |= 0x10; } if (player->_renderer.isFlippedY()) { flags |= 0x20; } if (mpPlayer->_justWarped) { mpPlayer->_justWarped = false; flags |= 0x40; } packet.WriteValue(flags); packet.WriteValue((std::int32_t)(pos.X * 512.0f)); packet.WriteValue((std::int32_t)(pos.Y * 512.0f)); packet.WriteVariableUint32((std::uint32_t)(player->_currentTransition != nullptr ? player->_currentTransition->State : player->_currentAnimation->State)); float rotation = player->_renderer.rotation(); if (rotation < 0.0f) rotation += fRadAngle360; packet.WriteValue((std::uint16_t)(rotation * UINT16_MAX / fRadAngle360)); Vector2f scale = player->_renderer.scale(); packet.WriteValue((std::uint16_t)Half{scale.X}); packet.WriteValue((std::uint16_t)Half{scale.Y}); Actors::ActorRendererType rendererType = player->_renderer.GetRendererType(); if (rendererType == Actors::ActorRendererType::Outline) { // Outline renderer type is local-only rendererType = Actors::ActorRendererType::Default; } packet.WriteValue((std::uint8_t)rendererType); } // TODO: Does this need to be locked? { std::unique_lock lock(_lock); for (auto& [remotingActor, remotingActorInfo] : _remotingActors) { packet.WriteVariableUint32(remotingActorInfo.ActorID); std::int32_t newPosX = (std::int32_t)(remotingActor->_pos.X * 512.0f); std::int32_t newPosY = (std::int32_t)(remotingActor->_pos.Y * 512.0f); bool positionChanged = (_forceResyncPending || newPosX != remotingActorInfo.LastPosX || newPosY != remotingActorInfo.LastPosY); std::uint32_t newAnimation = (std::uint32_t)(remotingActor->_currentTransition != nullptr ? remotingActor->_currentTransition->State : (remotingActor->_currentAnimation != nullptr ? remotingActor->_currentAnimation->State : AnimState::Idle)); float rotation = remotingActor->_renderer.rotation(); if (rotation < 0.0f) rotation += fRadAngle360; std::uint16_t newRotation = (std::uint16_t)(rotation * UINT16_MAX / fRadAngle360); Vector2f newScale = remotingActor->_renderer.scale(); std::uint16_t newScaleX = (std::uint16_t)Half{newScale.X}; std::uint16_t newScaleY = (std::uint16_t)Half{newScale.Y}; std::uint8_t newRendererType = (std::uint8_t)remotingActor->_renderer.GetRendererType(); bool animationChanged = (_forceResyncPending || newAnimation != remotingActorInfo.LastAnimation || newRotation != remotingActorInfo.LastRotation || newScaleX != remotingActorInfo.LastScaleX || newScaleY != remotingActorInfo.LastScaleY || newRendererType != remotingActorInfo.LastRendererType); std::uint8_t flags = 0; if (positionChanged) { flags |= 0x01; } if (animationChanged) { flags |= 0x02; } if (remotingActor->_renderer.isDrawEnabled()) { flags |= 0x04; } if (remotingActor->_renderer.AnimPaused) { flags |= 0x08; } if (remotingActor->_renderer.isFlippedX()) { flags |= 0x10; } if (remotingActor->_renderer.isFlippedY()) { flags |= 0x20; } packet.WriteValue(flags); if (positionChanged) { packet.WriteValue(newPosX); packet.WriteValue(newPosY); remotingActorInfo.LastPosX = newPosX; remotingActorInfo.LastPosY = newPosY; } if (animationChanged) { packet.WriteVariableUint32(newAnimation); packet.WriteValue(newRotation); packet.WriteValue(newScaleX); packet.WriteValue(newScaleY); packet.WriteValue(newRendererType); remotingActorInfo.LastAnimation = newAnimation; remotingActorInfo.LastRotation = newRotation; remotingActorInfo.LastScaleX = newScaleX; remotingActorInfo.LastScaleY = newScaleY; remotingActorInfo.LastRendererType = newRendererType; } } } MemoryStream packetCompressed(1024); { DeflateWriter dw(packetCompressed); dw.Write(packet.GetBuffer(), packet.GetSize()); } #if defined(DEATH_DEBUG) _debugAverageUpdatePacketSize = lerp(_debugAverageUpdatePacketSize, (std::int32_t)(packet.GetSize() * UpdatesPerSecond), 0.04f * timeMult); #endif #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) _updatePacketSize[_plotIndex] = packet.GetSize(); _updatePacketMaxSize = std::max(_updatePacketMaxSize, _updatePacketSize[_plotIndex]); _compressedUpdatePacketSize[_plotIndex] = packetCompressed.GetSize(); #endif _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, _forceResyncPending ? NetworkChannel::Main : NetworkChannel::UnreliableUpdates, (std::uint8_t)ServerPacketType::UpdateAllActors, packetCompressed); _lastUpdated++; _forceResyncPending = false; SynchronizePeers(); } else { #if defined(DEATH_DEBUG) _debugAverageUpdatePacketSize = 0; #endif } } else { if (!_players.empty()) { Clock& c = nCine::clock(); std::uint64_t now = c.now() * 1000 / c.frequency(); auto player = _players[0]; RemotePlayerOnServer::PlayerFlags flags = (RemotePlayerOnServer::PlayerFlags)player->_currentSpecialMove; if (player->IsFacingLeft()) { flags |= RemotePlayerOnServer::PlayerFlags::IsFacingLeft; } if (player->_renderer.isDrawEnabled()) { flags |= RemotePlayerOnServer::PlayerFlags::IsVisible; } if (player->_isActivelyPushing) { flags |= RemotePlayerOnServer::PlayerFlags::IsActivelyPushing; } if (_seqNumWarped != 0) { flags |= RemotePlayerOnServer::PlayerFlags::JustWarped; } if (PreferencesCache::EnableContinuousJump) { flags |= RemotePlayerOnServer::PlayerFlags::EnableContinuousJump; } MemoryStream packet(20); packet.WriteVariableUint32(_lastSpawnedActorId); packet.WriteVariableUint64(now); packet.WriteValue((std::int32_t)(player->_pos.X * 512.0f)); packet.WriteValue((std::int32_t)(player->_pos.Y * 512.0f)); packet.WriteValue((std::int16_t)(player->_speed.X * 512.0f)); packet.WriteValue((std::int16_t)(player->_speed.Y * 512.0f)); packet.WriteVariableUint32((std::uint32_t)flags); if (_seqNumWarped != 0) { packet.WriteVariableUint64(_seqNumWarped); } #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) _updatePacketSize[_plotIndex] = packet.GetSize(); _compressedUpdatePacketSize[_plotIndex] = _updatePacketSize[_plotIndex]; _updatePacketMaxSize = std::max(_updatePacketMaxSize, _updatePacketSize[_plotIndex]); #endif _networkManager->SendTo(AllPeers, NetworkChannel::UnreliableUpdates, (std::uint8_t)ClientPacketType::PlayerUpdate, packet); } } } #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) ShowDebugWindow(); if (PreferencesCache::ShowPerformanceMetrics) { ImDrawList* drawList = ImGui::GetBackgroundDrawList(); /*if (_isServer) { for (Actors::Player* player : _players) { char actorIdString[32]; Vector2f pos; auto it = _playerStates.find(player->_playerIndex); if (it != _playerStates.end()) { for (std::size_t i = 0; i < arraySize(it->second.StateBuffer); i++) { auto posFrom = WorldPosToScreenSpace(it->second.StateBuffer[i].Pos); auto posTo = WorldPosToScreenSpace(it->second.StateBuffer[i].Pos + it->second.StateBuffer[i].Speed); drawList->AddLine(posFrom, posTo, ImColor(255, 200, 120, 220), 2.0f); } std::int32_t prevIdx = it->second.StateBufferPos - 1; while (prevIdx < 0) { prevIdx += std::int32_t(arraySize(it->second.StateBuffer)); } auto posPrev = WorldPosToScreenSpace(it->second.StateBuffer[prevIdx].Pos); auto posLocal = WorldPosToScreenSpace(player->_pos); drawList->AddLine(posPrev, posLocal, ImColor(255, 60, 60, 220), 2.0f); formatString(actorIdString, "%i [%0.1f] %04x", player->_playerIndex, it->second.DeviationTime, it->second.Flags); } else { formatString(actorIdString, "%i", player->_playerIndex); } auto aabbMin = WorldPosToScreenSpace({ player->AABB.L, player->AABB.T }); aabbMin.x += 4.0f; aabbMin.y += 3.0f; drawList->AddText(aabbMin, ImColor(255, 255, 255), actorIdString); } for (const auto& [actor, actorId] : _remotingActors) { char actorIdString[16]; formatString(actorIdString, "%u", actorId); auto aabbMin = WorldPosToScreenSpace({ actor->AABB.L, actor->AABB.T }); aabbMin.x += 4.0f; aabbMin.y += 3.0f; drawList->AddText(aabbMin, ImColor(255, 255, 255), actorIdString); } } else { for (Actors::Player* player : _players) { char actorIdString[16]; formatString(actorIdString, "%i", player->_playerIndex); auto aabbMin = WorldPosToScreenSpace({ player->AABB.L, player->AABB.T }); aabbMin.x += 4.0f; aabbMin.y += 3.0f; drawList->AddText(aabbMin, ImColor(255, 255, 255), actorIdString); } for (const auto& [actorId, actor] : _remoteActors) { char actorIdString[16]; formatString(actorIdString, "%u", actorId); auto aabbMin = WorldPosToScreenSpace({ actor->AABBInner.L, actor->AABBInner.T }); aabbMin.x += 4.0f; aabbMin.y += 3.0f; drawList->AddText(aabbMin, ImColor(255, 255, 255), actorIdString); } }*/ } #endif if (_isServer && _players.empty()) { // If no players are connected, slow the server down to save resources Thread::Sleep(500); } } void MpLevelHandler::OnInitializeViewport(std::int32_t width, std::int32_t height) { LevelHandler::OnInitializeViewport(width, height); if (_inGameLobby != nullptr) { _inGameLobby->setParent(_upscalePass.GetNode()); } } bool MpLevelHandler::OnConsoleCommand(StringView line) { if (LevelHandler::OnConsoleCommand(line)) { return true; } if (_isServer) { if (line.hasPrefix('/')) { _console->WriteLine(UI::MessageLevel::Echo, line); return ProcessCommand({}, line, true); } auto peerDesc = _networkManager->GetPeerDescriptor(LocalPeer); String prefixedMessage = "\f[c:#907060]"_s + peerDesc->PlayerName + ":\f[/c] "_s + line; _console->WriteLine(UI::MessageLevel::Echo, prefixedMessage); // Chat message MemoryStream packet(9 + prefixedMessage.size()); packet.WriteVariableUint32(0); // TODO: Player index packet.WriteValue((std::uint8_t)UI::MessageLevel::Chat); packet.WriteVariableUint32((std::uint32_t)prefixedMessage.size()); packet.Write(prefixedMessage.data(), (std::uint32_t)prefixedMessage.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState != PeerLevelState::Unknown); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ChatMessage, packet); } else { // Chat message MemoryStream packet(9 + line.size()); packet.WriteVariableUint32(_lastSpawnedActorId); packet.WriteValue(0); // Reserved packet.WriteVariableUint32((std::uint32_t)line.size()); packet.Write(line.data(), (std::uint32_t)line.size()); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::ChatMessage, packet); } return true; } void MpLevelHandler::OnTouchEvent(const TouchEvent& event) { LevelHandler::OnTouchEvent(event); if (_inGameLobby->IsVisible() && _pauseMenu == nullptr && !_console->IsVisible()) { _inGameLobby->OnTouchEvent(event); } } void MpLevelHandler::AddActor(std::shared_ptr actor) { LevelHandler::AddActor(actor); if (!_suppressRemoting && _isServer) { Actors::ActorBase* actorPtr = actor.get(); std::uint32_t actorId; { std::unique_lock lock(_lock); actorId = FindFreeActorId(); _remotingActors[actorPtr] = { actorId }; // Store only used IDs on server-side _remoteActors[actorId] = nullptr; } if (ActorShouldBeMirrored(actorPtr)) { Vector2i originTile = actorPtr->_originTile; const auto& eventTile = _eventMap->GetEventTile(originTile.X, originTile.Y); if (eventTile.Event != EventType::Empty) { MemoryStream packet(24 + Events::EventSpawner::SpawnParamsSize); packet.WriteVariableUint32(actorId); packet.WriteVariableUint32((std::uint32_t)eventTile.Event); packet.Write(eventTile.EventParams, Events::EventSpawner::SpawnParamsSize); packet.WriteVariableUint32((std::uint32_t)eventTile.EventFlags); packet.WriteVariableInt32((std::int32_t)originTile.X); packet.WriteVariableInt32((std::int32_t)originTile.Y); packet.WriteVariableInt32((std::int32_t)actorPtr->_renderer.layer()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateMirroredActor, packet); } } else { MemoryStream packet; InitializeCreateRemoteActorPacket(packet, actorId, actorPtr); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateRemoteActor, packet); } } } std::shared_ptr MpLevelHandler::PlaySfx(Actors::ActorBase* self, StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) { Vector3f adjustedPos = pos; if (_isServer) { std::uint32_t actorId; bool excludeSelf; if (auto* player = runtime_cast(self)) { actorId = player->_playerIndex; excludeSelf = (!identifier.hasPrefix("EndOfLevel"_s) && !identifier.hasPrefix("Pickup"_s) && !identifier.hasPrefix("Weapon"_s)); if (sourceRelative) { // Remote players don't have local viewport, so SFX cannot be relative to them if (auto* remotePlayer = runtime_cast(self)) { sourceRelative = false; Vector2f pos = remotePlayer->GetPos(); adjustedPos.X += pos.X; adjustedPos.Y += pos.Y; } } } else { std::unique_lock lock(_lock); auto it = _remotingActors.find(self); actorId = (it != _remotingActors.end() ? it->second.ActorID : UINT32_MAX); excludeSelf = false; } if (actorId != UINT32_MAX) { MemoryStream packet(12 + identifier.size()); packet.WriteVariableUint32(actorId); // TODO: sourceRelative // TODO: looping packet.WriteValue(floatToHalf(gain)); packet.WriteValue(floatToHalf(pitch)); packet.WriteVariableUint32((std::uint32_t)identifier.size()); packet.Write(identifier.data(), (std::uint32_t)identifier.size()); Actors::ActorBase* excludedPlayer = (excludeSelf ? self : nullptr); _networkManager->SendTo([this, excludedPlayer](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized && (excludedPlayer == nullptr || excludedPlayer != peerDesc->Player)); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlaySfx, packet); } else { // Actor is probably not fully created yet, try it later again _pendingSfx.emplace_back(self, identifier, floatToHalf(gain), floatToHalf(pitch)); } } return LevelHandler::PlaySfx(self, identifier, buffer, adjustedPos, sourceRelative, gain, pitch); } std::shared_ptr MpLevelHandler::PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain, float pitch) { if (_isServer) { MemoryStream packet(16 + identifier.size()); packet.WriteVariableInt32((std::int32_t)pos.X); packet.WriteVariableInt32((std::int32_t)pos.Y); // TODO: looping packet.WriteValue(floatToHalf(gain)); packet.WriteValue(floatToHalf(pitch)); packet.WriteVariableUint32((std::uint32_t)identifier.size()); packet.Write(identifier.data(), (std::uint32_t)identifier.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayCommonSfx, packet); } return LevelHandler::PlayCommonSfx(identifier, pos, gain, pitch); } void MpLevelHandler::WarpCameraToTarget(Actors::ActorBase* actor, bool fast) { LevelHandler::WarpCameraToTarget(actor, fast); } void MpLevelHandler::BroadcastTriggeredEvent(Actors::ActorBase* initiator, EventType eventType, std::uint8_t* eventParams) { LevelHandler::BroadcastTriggeredEvent(initiator, eventType, eventParams); } void MpLevelHandler::BeginLevelChange(Actors::ActorBase* initiator, ExitType exitType, StringView nextLevel) { if (!_isServer || _nextLevelType != ExitType::None || _levelState != LevelState::Running) { // Level can be changed only by server return; } auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode == MpGameMode::Race || serverConfig.GameMode == MpGameMode::TeamRace) { auto* mpPlayer = static_cast(initiator); if (mpPlayer->_currentTransition == nullptr || (mpPlayer->_currentTransition->State != AnimState::TransitionWarpIn && mpPlayer->_currentTransition->State != AnimState::TransitionWarpOut && mpPlayer->_currentTransition->State != AnimState::TransitionWarpInFreefall && mpPlayer->_currentTransition->State != AnimState::TransitionWarpOutFreefall)) { Vector2f spawnPosition = GetSpawnPoint(mpPlayer->_playerTypeOriginal); mpPlayer->WarpToPosition(spawnPosition, WarpFlags::IncrementLaps); } return; } else if (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt) { // Player has to stand on EndOfLevel event with required treasure amount auto* mpPlayer = static_cast(initiator); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->TreasureCollected >= serverConfig.TotalTreasureCollected) { peerDesc->TreasureCollected++; // The escaped player should have the most points CalculatePositionInRound(); EndGame(mpPlayer); } return; } else if (serverConfig.GameMode != MpGameMode::Cooperation) { // Ignore end of the level in all game modes except cooperation return; } if (nextLevel.empty()) { nextLevel = _defaultNextLevel; } LOGD("[MP] Changing level to \"{}\" (0x{:.2x})", nextLevel, exitType); if ((nextLevel == ":end"_s || nextLevel == ":credits"_s) && !serverConfig.Playlist.empty()) { serverConfig.PlaylistIndex++; if (serverConfig.PlaylistIndex >= serverConfig.Playlist.size()) { serverConfig.PlaylistIndex = 0; if (serverConfig.RandomizePlaylist) { Random().Shuffle(serverConfig.Playlist); } } // TODO: Implement transition if (ApplyFromPlaylist()) { return; } } LevelHandler::BeginLevelChange(initiator, exitType, nextLevel); if ((exitType & ExitType::FastTransition) != ExitType::FastTransition) { float fadeOutDelay = _nextLevelTime - 40.0f; MemoryStream packet(4); packet.WriteVariableInt32((std::int32_t)fadeOutDelay); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelLoaded); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::FadeOut, packet); } } void MpLevelHandler::SendPacket(const Actors::ActorBase* self, ArrayView data) { if DEATH_LIKELY(_isServer) { std::uint32_t targetActorId = 0; { std::unique_lock lock(_lock); auto it = _remotingActors.find(const_cast(self)); if (it != _remotingActors.end()) { targetActorId = it->second.ActorID; } } if (targetActorId != 0) { MemoryStream packet(4 + data.size()); packet.WriteVariableUint32(targetActorId); packet.Write(data.data(), data.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelLoaded); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::Rpc, packet); } else { LOGW("Remote actor not found"); } } else { std::uint32_t targetActorId = 0; { std::unique_lock lock(_lock); for (const auto& [actorId, actor] : _remoteActors) { if (actor.get() == self) { targetActorId = actorId; break; } } } if (targetActorId != 0) { MemoryStream packet(4 + data.size()); packet.WriteVariableUint32(targetActorId); packet.Write(data.data(), data.size()); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::Rpc, packet); } else { LOGW("Remote actor not found"); } } } void MpLevelHandler::HandleBossActivated(Actors::Bosses::BossBase* boss, Actors::ActorBase* initiator) { constexpr float MinDistance = 128.0f; if (initiator == nullptr) { return; } Vector2f pos = initiator->_pos; for (auto* player : _players) { if (player == initiator || (player->_pos - pos).Length() < MinDistance) { continue; } player->WarpToPosition(pos, WarpFlags::Default); // TODO: Deactivate sugar rush } } void MpLevelHandler::HandleLevelChange(LevelInitialization&& levelInit) { levelInit.IsLocalSession = false; LevelHandler::HandleLevelChange(std::move(levelInit)); } void MpLevelHandler::HandleGameOver(Actors::Player* player) { // TODO //LevelHandler::HandleGameOver(player); } bool MpLevelHandler::HandlePlayerDied(Actors::Player* player) { #if defined(WITH_ANGELSCRIPT) if (_scripts != nullptr) { // TODO: killer _scripts->OnPlayerDied(player, nullptr); } #endif // TODO //return LevelHandler::HandlePlayerDied(player, collider); if (_isServer) { auto& serverConfig = _networkManager->GetServerConfiguration(); auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); bool canRespawn = (_enableSpawning && _nextLevelType == ExitType::None && (_levelState != LevelState::Running || !serverConfig.Elimination || peerDesc->Deaths < serverConfig.TotalKills)); bool shouldRollback = false; if (canRespawn && _activeBoss != nullptr) { bool anyoneAlive = false; for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->Player && peerDesc->Player->_health > 0) { anyoneAlive = true; break; } } if (anyoneAlive) { // If at least one player is alive, don't rollback yet canRespawn = false; } else { if (_activeBoss->OnPlayerDied()) { _activeBoss = nullptr; } shouldRollback = true; } } if (canRespawn) { LimitCameraView(mpPlayer, mpPlayer->_pos, 0, 0); if (shouldRollback) { LOGI("Rolling back to checkpoint"); for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->Player && peerDesc->Player != player) { peerDesc->Player->Respawn(mpPlayer->_checkpointPos); if (peerDesc->RemotePeer) { peerDesc->LastUpdated = UINT64_MAX; static_cast(peerDesc->Player)->_canTakeDamage = false; MemoryStream packet2(12); packet2.WriteVariableUint32(peerDesc->Player->_playerIndex); packet2.WriteValue((std::int32_t)(mpPlayer->_checkpointPos.X * 512.0f)); packet2.WriteValue((std::int32_t)(mpPlayer->_checkpointPos.Y * 512.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerRespawn, packet2); } } } RollbackLevelState(); BeginPlayMusic(_musicDefaultPath); } if (serverConfig.GameMode != MpGameMode::Cooperation) { mpPlayer->_checkpointPos = GetSpawnPoint(peerDesc->PreferredPlayerType); // The player is invulnerable for a short time after respawning mpPlayer->SetInvulnerability(serverConfig.SpawnInvulnerableSecs * FrameTimer::FramesPerSecond, Actors::Player::InvulnerableType::Blinking); } } if (_levelState != LevelState::Running) { if (peerDesc->RemotePeer) { peerDesc->LastUpdated = UINT64_MAX; mpPlayer->_canTakeDamage = false; MemoryStream packet2(12); packet2.WriteVariableUint32(mpPlayer->_playerIndex); packet2.WriteValue((std::int32_t)(mpPlayer->_checkpointPos.X * 512.0f)); packet2.WriteValue((std::int32_t)(mpPlayer->_checkpointPos.Y * 512.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerRespawn, packet2); } return canRespawn; } if (serverConfig.Elimination && peerDesc->Deaths >= serverConfig.TotalKills) { peerDesc->DeathElapsedFrames = _elapsedFrames; } if (canRespawn && peerDesc->RemotePeer) { peerDesc->LastUpdated = UINT64_MAX; mpPlayer->_canTakeDamage = false; MemoryStream packet2(12); packet2.WriteVariableUint32(mpPlayer->_playerIndex); packet2.WriteValue((std::int32_t)(mpPlayer->_checkpointPos.X * 512.0f)); packet2.WriteValue((std::int32_t)(mpPlayer->_checkpointPos.Y * 512.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerRespawn, packet2); } CheckGameEnds(); return canRespawn; } else { auto* mpPlayer = static_cast(player); if (mpPlayer->RespawnPending) { mpPlayer->RespawnPending = false; player->_checkpointPos = mpPlayer->RespawnPos; Clock& c = nCine::clock(); std::uint64_t now = c.now() * 1000 / c.frequency(); MemoryStream packetAck(24); packetAck.WriteVariableUint32(_lastSpawnedActorId); packetAck.WriteVariableUint64(now); packetAck.WriteValue((std::int32_t)(player->_checkpointPos.X * 512.0f)); packetAck.WriteValue((std::int32_t)(player->_checkpointPos.Y * 512.0f)); packetAck.WriteValue((std::int16_t)(player->_speed.X * 512.0f)); packetAck.WriteValue((std::int16_t)(player->_speed.Y * 512.0f)); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::PlayerAckWarped, packetAck); return true; } return false; } } void MpLevelHandler::HandlePlayerLevelChanging(Actors::Player* player, ExitType exitType) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(5); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::uint8_t)exitType); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerWarpIn, packet); } } } bool MpLevelHandler::HandlePlayerSpring(Actors::Player* player, Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { std::uint8_t flags = 0; if (keepSpeedX) { flags |= 0x01; } if (keepSpeedY) { flags |= 0x02; } MemoryStream packet(17); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::int32_t)(pos.X * 512.0f)); packet.WriteValue((std::int32_t)(pos.Y * 512.0f)); packet.WriteValue((std::int16_t)(force.X * 512.0f)); packet.WriteValue((std::int16_t)(force.Y * 512.0f)); packet.WriteValue(flags); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerActivateSpring, packet); } } return true; } void MpLevelHandler::HandlePlayerBeforeWarp(Actors::Player* player, Vector2f pos, WarpFlags flags) { if (_isServer) { if ((flags & (WarpFlags::Fast | WarpFlags::SkipWarpIn)) != WarpFlags::Default) { // Nothing to do, sending PlayerMoveInstantly packet is enough return; } auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(5); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue(0xFF); // Only temporary, no level changing _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerWarpIn, packet); } } } void MpLevelHandler::HandlePlayerSetModifier(Actors::Player* player, Actors::Player::Modifier modifier, const std::shared_ptr& decor) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { std::uint32_t actorId; { std::unique_lock lock(_lock); auto it = _remotingActors.find(decor.get()); if (it != _remotingActors.end()) { actorId = it->second.ActorID; } else { actorId = UINT32_MAX; } } MemoryStream packet(10); packet.WriteValue((std::uint8_t)PlayerPropertyType::Modifier); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::uint8_t)modifier); packet.WriteVariableUint32(actorId); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerFreeze(Actors::Player* player, float timeLeft) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Freeze); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)timeLeft); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerSetInvulnerability(Actors::Player* player, float timeLeft, Actors::Player::InvulnerableType type) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(10); packet.WriteValue((std::uint8_t)PlayerPropertyType::Invulnerable); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)timeLeft); packet.WriteValue((std::uint8_t)type); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerSetScore(Actors::Player* player, std::int32_t value) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Score); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)value); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerSetHealth(Actors::Player* player, std::int32_t count) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Health); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)count); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerSetLives(Actors::Player* player, std::int32_t count) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Lives); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)count); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerTakeDamage(Actors::Player* player, std::int32_t amount, float pushForce) { // TODO: Only called by PlayerOnServer if (_isServer) { auto& serverConfig = _networkManager->GetServerConfiguration(); auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(10); packet.WriteVariableUint32(player->_playerIndex); packet.WriteVariableInt32(player->_health); packet.WriteValue((std::int16_t)(pushForce * 512.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerTakeDamage, packet); } if (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt) { // TODO: Drop number of gems accorting to the gun strength (usually 3 times) std::uint32_t treasureLost = std::min(peerDesc->TreasureCollected, 3u); if (treasureLost > 0) { // If the player is dead, drop half of the collected treasure instead if (mpPlayer->_health <= 0 && treasureLost < peerDesc->TreasureCollected / 2) { treasureLost = peerDesc->TreasureCollected / 2; } peerDesc->TreasureCollected -= treasureLost; if (peerDesc->RemotePeer) { MemoryStream packet2(9); packet2.WriteValue((std::uint8_t)PlayerPropertyType::TreasureCollected); packet2.WriteVariableUint32(mpPlayer->_playerIndex); packet2.WriteVariableUint32(peerDesc->TreasureCollected); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet2); } Vector2f pos = mpPlayer->_pos; for (std::uint32_t i = 0; i < treasureLost; i++) { float dir = (Random().NextBool() ? -1.0f : 1.0f); float force = Random().NextFloat(10.0f, 20.0f); Vector3f spawnPos = Vector3f(pos.X, pos.Y, MainPlaneZ); std::uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] = { 0, 0x04 }; auto actor = _eventSpawner.SpawnEvent(EventType::Gem, spawnParams, Actors::ActorState::None, spawnPos.As()); if (actor != nullptr) { actor->AddExternalForce(dir * force, force); AddActor(actor); } } } } if (_levelState == LevelState::Running && player->_health <= 0) { peerDesc->Deaths++; if (peerDesc->RemotePeer) { MemoryStream packet3(9); packet3.WriteValue((std::uint8_t)PlayerPropertyType::Deaths); packet3.WriteVariableUint32(mpPlayer->_playerIndex); packet3.WriteVariableUint32(peerDesc->Deaths); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet3); } if (auto* attacker = GetWeaponOwner(mpPlayer->_lastAttacker.get())) { auto attackerPeerDesc = attacker->GetPeerDescriptor(); attackerPeerDesc->Kills++; if (attackerPeerDesc->RemotePeer) { MemoryStream packet4(9); packet4.WriteValue((std::uint8_t)PlayerPropertyType::Kills); packet4.WriteVariableUint32(attacker->_playerIndex); packet4.WriteVariableUint32(attackerPeerDesc->Kills); _networkManager->SendTo(attackerPeerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet4); } _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]", peerDesc->PlayerName, attackerPeerDesc->PlayerName)); MemoryStream packet5(19 + peerDesc->PlayerName.size() + attackerPeerDesc->PlayerName.size()); packet5.WriteValue((std::uint8_t)PeerPropertyType::Roasted); packet5.WriteVariableUint64((std::uint64_t)peerDesc->RemotePeer._enet); packet5.WriteValue((std::uint8_t)peerDesc->PlayerName.size()); packet5.Write(peerDesc->PlayerName.data(), (std::uint32_t)peerDesc->PlayerName.size()); packet5.WriteVariableUint64((std::uint64_t)attackerPeerDesc->RemotePeer._enet); packet5.WriteValue((std::uint8_t)attackerPeerDesc->PlayerName.size()); packet5.Write(attackerPeerDesc->PlayerName.data(), (std::uint32_t)attackerPeerDesc->PlayerName.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->IsAuthenticated); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PeerSetProperty, packet5); } else { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] was roasted by environment", peerDesc->PlayerName)); MemoryStream packet6(19 + peerDesc->PlayerName.size()); packet6.WriteValue((std::uint8_t)PeerPropertyType::Roasted); packet6.WriteVariableUint64((std::uint64_t)peerDesc->RemotePeer._enet); packet6.WriteValue((std::uint8_t)peerDesc->PlayerName.size()); packet6.Write(peerDesc->PlayerName.data(), (std::uint32_t)peerDesc->PlayerName.size()); packet6.WriteVariableUint64(0); packet6.WriteValue(0); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->IsAuthenticated); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PeerSetProperty, packet6); } } } } void MpLevelHandler::HandlePlayerRefreshAmmo(Actors::Player* player, WeaponType weaponType) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(8); packet.WriteValue((std::uint8_t)PlayerPropertyType::WeaponAmmo); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::uint8_t)weaponType); packet.WriteValue((std::uint16_t)mpPlayer->_weaponAmmo[(std::uint8_t)weaponType]); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerMorphTo(Actors::Player* player, PlayerType type) { // TODO: Only called by PlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); String metadataPath = fs::FromNativeSeparators(mpPlayer->_metadata->Path); MemoryStream packet(9 + metadataPath.size()); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue(0); // Flags (Reserved) packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); packet.Write(metadataPath.data(), (std::uint32_t)metadataPath.size()); _networkManager->SendTo([otherPeer = peerDesc->RemotePeer](const Peer& peer) { return (peer != otherPeer); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ChangeRemoteActorMetadata, packet); if (peerDesc->RemotePeer) { MemoryStream packet2(6); packet2.WriteValue((std::uint8_t)PlayerPropertyType::PlayerType); packet2.WriteVariableUint32(mpPlayer->_playerIndex); packet2.WriteValue((std::uint8_t)type); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet2); } } } void MpLevelHandler::HandlePlayerSetDizzy(Actors::Player* player, float timeLeft) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Dizzy); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)timeLeft); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerSetShield(Actors::Player* player, ShieldType shieldType, float timeLeft) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Shield); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::uint8_t)shieldType); packet.WriteVariableInt32((std::int32_t)timeLeft); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerRefreshWeaponUpgrades(Actors::Player* player, WeaponType weaponType) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(7); packet.WriteValue((std::uint8_t)PlayerPropertyType::WeaponUpgrades); packet.WriteVariableUint32(player->_playerIndex); packet.WriteValue((std::uint8_t)weaponType); packet.WriteValue((std::uint8_t)player->_weaponUpgrades[(std::uint8_t)weaponType]); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::HandlePlayerEmitWeaponFlare(Actors::Player* player) { // TODO: Only called by RemotePlayerOnServer if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(4); packet.WriteVariableUint32(mpPlayer->_playerIndex); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerEmitWeaponFlare, packet); } } } void MpLevelHandler::HandlePlayerWeaponChanged(Actors::Player* player, Actors::Player::SetCurrentWeaponReason reason) { if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(6); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::uint8_t)mpPlayer->_currentWeapon); packet.WriteValue((std::uint8_t)reason); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerChangeWeapon, packet); } } else { auto* remotablePlayer = static_cast(player); if (!remotablePlayer->ChangingWeaponFromServer) { MemoryStream packet(5); packet.WriteVariableUint32(_lastSpawnedActorId); packet.WriteValue((std::uint8_t)player->_currentWeapon); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::PlayerChangeWeaponRequest, packet); } } } void MpLevelHandler::HandlePlayerWarped(Actors::Player* player, Vector2f prevPos, WarpFlags flags) { LevelHandler::HandlePlayerWarped(player, prevPos, flags); /*if (_isServer) { auto it = _playerStates.find(player->_playerIndex); if (it != _playerStates.end()) { it->second.Flags |= PlayerFlags::JustWarped; } } else { _seqNumWarped = _seqNum; }*/ if (_isServer) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); mpPlayer->_justWarped = true; if ((flags & WarpFlags::IncrementLaps) == WarpFlags::IncrementLaps && _levelState == LevelState::Running) { // Don't allow laps to be quickly incremented twice in a row if ((_elapsedFrames - peerDesc->LapsElapsedFrames) > 2.0f * FrameTimer::FramesPerSecond) { peerDesc->Laps++; auto now = TimeStamp::now(); //peerDesc->LapsElapsedFrames += (now - peerDesc->LapStarted).seconds() * FrameTimer::FramesPerSecond; peerDesc->LapsElapsedFrames = _elapsedFrames; peerDesc->LapStarted = now; CheckGameEnds(); } else { LOGW("Player {} attempted to increment laps twice in a row", mpPlayer->_playerIndex); } } if (peerDesc->RemotePeer) { if ((flags & WarpFlags::IncrementLaps) == WarpFlags::IncrementLaps && _levelState == LevelState::Running) { auto& serverConfig = _networkManager->GetServerConfiguration(); MemoryStream packet2(13); packet2.WriteValue((std::uint8_t)PlayerPropertyType::Laps); packet2.WriteVariableUint32(mpPlayer->_playerIndex); packet2.WriteVariableUint32(peerDesc->Laps); packet2.WriteVariableUint32(serverConfig.TotalLaps); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet2); } MemoryStream packet(16); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue((std::int32_t)(mpPlayer->_pos.X * 512.0f)); packet.WriteValue((std::int32_t)(mpPlayer->_pos.Y * 512.0f)); packet.WriteValue((std::int16_t)(mpPlayer->_speed.X * 512.0f)); packet.WriteValue((std::int16_t)(mpPlayer->_speed.Y * 512.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerMoveInstantly, packet); } } else { Clock& c = nCine::clock(); std::uint64_t now = c.now() * 1000 / c.frequency(); MemoryStream packetAck(24); packetAck.WriteVariableUint32(_lastSpawnedActorId); packetAck.WriteVariableUint64(now); packetAck.WriteValue((std::int32_t)(player->_pos.X * 512.0f)); packetAck.WriteValue((std::int32_t)(player->_pos.Y * 512.0f)); packetAck.WriteValue((std::int16_t)(player->_speed.X * 512.0f)); packetAck.WriteValue((std::int16_t)(player->_speed.Y * 512.0f)); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::PlayerAckWarped, packetAck); } } void MpLevelHandler::HandlePlayerCoins(Actors::Player* player, std::int32_t prevCount, std::int32_t newCount) { //LevelHandler::HandlePlayerCoins(player, prevCount, newCount); if (_isServer) { // Coins are shared in cooperation, add it also to all other local players auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode == MpGameMode::Cooperation) { if (prevCount < newCount) { std::int32_t increment = (newCount - prevCount); for (auto current : _players) { if (current != player) { current->AddCoinsInternal(increment); } } } for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->RemotePeer && peerDesc->Player) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Coins); packet.WriteVariableUint32(peerDesc->Player->_playerIndex); packet.WriteVariableInt32(peerDesc->Player->_coins); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } _hud->ShowCoins(newCount); } else { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::Coins); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32(newCount); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } // Show notification only for local players (which have assigned viewport) for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { _hud->ShowCoins(newCount); break; } } } } } void MpLevelHandler::HandlePlayerGems(Actors::Player* player, std::uint8_t gemType, std::int32_t prevCount, std::int32_t newCount) { if (_isServer) { auto& serverConfig = _networkManager->GetServerConfiguration(); auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt) { // Count gems as treasure if (newCount > prevCount && _levelState == LevelState::Running) { std::int32_t weightedCount = GetTreasureWeight(gemType); peerDesc->TreasureCollected += (newCount - prevCount) * weightedCount; if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::TreasureCollected); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableUint32(peerDesc->TreasureCollected); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } CheckGameEnds(); } } else { // Show standard gems notification if (peerDesc->RemotePeer) { MemoryStream packet(10); packet.WriteValue((std::uint8_t)PlayerPropertyType::Gems); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue(gemType); packet.WriteVariableInt32(newCount); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); MemoryStream packet2(9); packet2.WriteValue((std::uint8_t)PlayerPropertyType::TreasureCollected); packet2.WriteVariableUint32(mpPlayer->_playerIndex); packet2.WriteVariableUint32(peerDesc->TreasureCollected); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet2); } // Show notification only for local players (which have assigned viewport) for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { _hud->ShowGems(gemType, newCount); break; } } } } } void MpLevelHandler::SetCheckpoint(Actors::Player* player, Vector2f pos) { LevelHandler::SetCheckpoint(player, pos); float ambientLight = _defaultAmbientLight.W; for (auto& viewport : _assignedViewports) { if (viewport->_targetActor == player) { ambientLight = viewport->_ambientLightTarget; break; } } _lastCheckpointPos = Vector2f(pos.X, pos.Y - 20.0f); _lastCheckpointLight = ambientLight; } void MpLevelHandler::RollbackToCheckpoint(Actors::Player* player) { // TODO: Remove this override LevelHandler::RollbackToCheckpoint(player); } void MpLevelHandler::HandleActivateSugarRush(Actors::Player* player) { // TODO: Remove this override LevelHandler::HandleActivateSugarRush(player); } void MpLevelHandler::HandleCreateParticleDebrisOnPerish(const Actors::ActorBase* self, Actors::ParticleDebrisEffect effect, Vector2f speed) { LevelHandler::HandleCreateParticleDebrisOnPerish(self, effect, speed); if (_isServer) { std::uint32_t targetActorId = 0; { std::unique_lock lock(_lock); auto it = _remotingActors.find(const_cast(self)); if (it != _remotingActors.end()) { targetActorId = it->second.ActorID; } } if (targetActorId != 0) { MemoryStream packet(13); packet.WriteValue((std::uint8_t)effect); packet.WriteVariableUint32(targetActorId); packet.WriteVariableInt32((std::int32_t)(speed.X * 100.0f)); packet.WriteVariableInt32((std::int32_t)(speed.Y * 100.0f)); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateDebris, packet); } else { LOGW("Remote actor not found"); } } } void MpLevelHandler::HandleCreateSpriteDebris(const Actors::ActorBase* self, AnimState state, std::int32_t count) { LevelHandler::HandleCreateSpriteDebris(self, state, count); if (_isServer) { std::uint32_t targetActorId = 0; { std::unique_lock lock(_lock); auto it = _remotingActors.find(const_cast(self)); if (it != _remotingActors.end()) { targetActorId = it->second.ActorID; } } if (targetActorId != 0) { MemoryStream packet(13); packet.WriteValue(UINT8_MAX); // Effect packet.WriteVariableUint32(targetActorId); packet.WriteVariableUint32((std::uint32_t)state); packet.WriteVariableInt32(count); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateDebris, packet); } else { LOGW("Remote actor not found"); } } } void MpLevelHandler::ShowLevelText(StringView text, Actors::ActorBase* initiator) { if (initiator == nullptr || IsLocalPlayer(initiator)) { // Pass through only messages for local players LevelHandler::ShowLevelText(text, initiator); } } StringView MpLevelHandler::GetLevelText(std::uint32_t textId, std::int32_t index, std::uint32_t delimiter) { return LevelHandler::GetLevelText(textId, index, delimiter); } void MpLevelHandler::OverrideLevelText(std::uint32_t textId, StringView value) { LevelHandler::OverrideLevelText(textId, value); if (_isServer) { std::uint32_t textLength = (std::uint32_t)value.size(); MemoryStream packet(9 + textLength); packet.WriteValue((std::uint8_t)LevelPropertyType::LevelText); packet.WriteVariableUint32(textId); packet.WriteVariableUint32(textLength); packet.Write(value.data(), textLength); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelLoaded); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LevelSetProperty, packet); } } bool MpLevelHandler::PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) { if (_isServer) { if (auto* remotePlayerOnServer = runtime_cast(player)) { // PlayerChangeWeaponRequest is sent from the client side everytime, suppress these actions on the server if (action == PlayerAction::ChangeWeapon || (action >= PlayerAction::SwitchToBlaster && action <= PlayerAction::SwitchToThunderbolt)) { return false; } if ((remotePlayerOnServer->PressedKeys & (1ull << (std::int32_t)action)) != 0) { isGamepad = (remotePlayerOnServer->PressedKeys & (1ull << (32 + (std::int32_t)action))) != 0; return true; } isGamepad = false; return false; } } return LevelHandler::PlayerActionPressed(player, action, includeGamepads, isGamepad); } bool MpLevelHandler::PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) { if (_isServer) { if (auto* remotePlayerOnServer = runtime_cast(player)) { // PlayerChangeWeaponRequest is sent from the client side everytime, suppress these actions on the server if (action == PlayerAction::ChangeWeapon || (action >= PlayerAction::SwitchToBlaster && action <= PlayerAction::SwitchToThunderbolt)) { return false; } if ((remotePlayerOnServer->PressedKeys & (1ull << (std::int32_t)action)) != 0 && (remotePlayerOnServer->PressedKeysLast & (1ull << (std::int32_t)action)) == 0) { isGamepad = (remotePlayerOnServer->PressedKeys & (1ull << (32 + (std::int32_t)action))) != 0; return true; } isGamepad = false; return false; } } return LevelHandler::PlayerActionHit(player, action, includeGamepads, isGamepad); } float MpLevelHandler::PlayerHorizontalMovement(Actors::Player* player) { if (_isServer) { if (auto* remotePlayerOnServer = runtime_cast(player)) { if ((remotePlayerOnServer->PressedKeys & (1ull << (std::int32_t)PlayerAction::Left)) != 0) { return -1.0f; } else if ((remotePlayerOnServer->PressedKeys & (1ull << (std::int32_t)PlayerAction::Right)) != 0) { return 1.0f; } else { return 0.0f; } } } return LevelHandler::PlayerHorizontalMovement(player); } float MpLevelHandler::PlayerVerticalMovement(Actors::Player* player) { if (_isServer) { if (auto* remotePlayerOnServer = runtime_cast(player)) { if ((remotePlayerOnServer->PressedKeys & (1ull << (std::int32_t)PlayerAction::Up)) != 0) { return -1.0f; } else if ((remotePlayerOnServer->PressedKeys & (1ull << (std::int32_t)PlayerAction::Down)) != 0) { return 1.0f; } else { return 0.0f; } } } return LevelHandler::PlayerVerticalMovement(player); } void MpLevelHandler::PlayerExecuteRumble(Actors::Player* player, StringView rumbleEffect) { if (_isServer) { if (auto* remotePlayerOnServer = runtime_cast(player)) { // Ignore remote players return; } } return LevelHandler::PlayerExecuteRumble(player, rumbleEffect); } bool MpLevelHandler::SerializeResumableToStream(Stream& dest) { // Online multiplayer sessions cannot be resumed return false; } void MpLevelHandler::OnAdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount) { if (_isServer) { MemoryStream packet(12); packet.WriteVariableInt32(tx); packet.WriteVariableInt32(ty); packet.WriteVariableInt32(amount); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::AdvanceTileAnimation, packet); } } StringView MpLevelHandler::GetLevelDisplayName() const { return _levelDisplayName; } void MpLevelHandler::AttachComponents(LevelDescriptor&& descriptor) { LevelHandler::AttachComponents(std::move(descriptor)); if (_isServer) { // Cache all possible multiplayer spawn points (if it's not coop level) and race checkpoints _multiplayerSpawnPoints.clear(); _raceCheckpoints.clear(); _totalTreasureCount = 0; _eventMap->ForEachEvent([this](Events::EventMap::EventTile& e, std::int32_t x, std::int32_t y) { if (e.Event == EventType::LevelStartMultiplayer) { _multiplayerSpawnPoints.emplace_back(Vector2f(x * Tiles::TileSet::DefaultTileSize, y * Tiles::TileSet::DefaultTileSize - 8), e.EventParams[0]); } else if (e.Event == EventType::WarpOrigin) { if (e.EventParams[2] != 0) { _raceCheckpoints.emplace_back(Vector2i(x, y)); } } else if (e.Event == EventType::AreaEndOfLevel) { _raceCheckpoints.emplace_back(Vector2i(x, y)); } else if (e.Event == EventType::Gem) { // GemStomp event is not counted, because it's difficult to find std::uint8_t gemType = (std::uint8_t)(e.EventParams[0] & 0x03); std::int32_t count = GetTreasureWeight(gemType); _totalTreasureCount += count; } else if (e.Event == EventType::GemGiant) { // Giant gem randowly spawns between 5 and 12 red gems, so use 8 as average _totalTreasureCount += 8 * GetTreasureWeight(0); } else if (e.Event == EventType::GemRing) { // Gems in the ring are always red std::int32_t count = (e.EventParams[0] > 0 ? e.EventParams[0] : 8) * GetTreasureWeight(0); _totalTreasureCount += count; } else if (e.Event == EventType::CrateGem || e.Event == EventType::BarrelGem) { std::int32_t count = e.EventParams[0] * GetTreasureWeight(0) + e.EventParams[1] * GetTreasureWeight(1) + e.EventParams[2] * GetTreasureWeight(2) + e.EventParams[3] * GetTreasureWeight(3); _totalTreasureCount += count; } return true; }); ConsolidateRaceCheckpoints(); const auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode == MpGameMode::Cooperation) { _eventMap->ForEachEvent([this](Events::EventMap::EventTile& e, std::int32_t x, std::int32_t y) { if (e.Event == EventType::OneUp) { // Replace all 1ups with max carrots // TODO: Reset it back to 1ups when starting a different mode with limited lives e.Event = EventType::Carrot; e.EventParams[0] = 1; } else if (e.Event == EventType::AirboardGenerator) { // Shorten delay time of all airboard generators // TODO: Reset it back when starting a different mode if (e.EventParams[0] > 4) { e.EventParams[0] = 4; } } return true; }); } } else { // Change InstantDeathPit to FallForever, because player health is managed by the server if (_eventMap->GetPitType() == PitType::InstantDeathPit) { _eventMap->SetPitType(PitType::FallForever); } _eventMap->ForEachEvent([this](Events::EventMap::EventTile& e, std::int32_t x, std::int32_t y) { switch (e.Event) { case EventType::WarpOrigin: case EventType::ModifierHurt: case EventType::ModifierDeath: case EventType::ModifierLimitCameraView: case EventType::AreaEndOfLevel: case EventType::AreaCallback: case EventType::AreaActivateBoss: case EventType::AreaFlyOff: case EventType::AreaRevertMorph: case EventType::AreaMorphToFrog: case EventType::AreaNoFire: case EventType::TriggerZone: case EventType::RollingRockTrigger: // These events are handled on server-side only e.Event = EventType::Empty; break; } return true; }); } } std::unique_ptr MpLevelHandler::CreateHUD() { return std::make_unique(this); } void MpLevelHandler::SpawnPlayers(const LevelInitialization& levelInit) { if (!_isServer) { // Player spawning is delayed/controlled by server _hud = CreateHUD(); return; } const auto& serverConfig = _networkManager->GetServerConfiguration(); for (std::int32_t i = 0; i < std::int32_t(arraySize(levelInit.PlayerCarryOvers)); i++) { if (levelInit.PlayerCarryOvers[i].Type == PlayerType::None) { continue; } Vector2f spawnPosition = GetSpawnPoint(levelInit.PlayerCarryOvers[i].Type); auto peerDesc = _networkManager->GetPeerDescriptor(LocalPeer); std::shared_ptr player = std::make_shared(peerDesc); std::uint8_t playerParams[2] = { (std::uint8_t)levelInit.PlayerCarryOvers[i].Type, (std::uint8_t)i }; player->OnActivated(Actors::ActorActivationDetails( this, Vector3i((std::int32_t)spawnPosition.X + (i * 30), (std::int32_t)spawnPosition.Y - (i * 30), PlayerZ - i), playerParams )); player->_controllableExternal = _controllableExternal; player->_health = (serverConfig.InitialPlayerHealth > 0 ? serverConfig.InitialPlayerHealth : (PlayerShouldHaveUnlimitedHealth(serverConfig.GameMode) ? INT32_MAX : 5)); peerDesc->LevelState = PeerLevelState::PlayerSpawned; peerDesc->LapsElapsedFrames = _elapsedFrames; peerDesc->LapStarted = TimeStamp::now(); peerDesc->PlayerName = PreferencesCache::GetEffectivePlayerName(); Actors::Multiplayer::LocalPlayerOnServer* ptr = player.get(); _players.push_back(ptr); AddActor(player); AssignViewport(ptr); ptr->ReceiveLevelCarryOver(levelInit.LastExitType, levelInit.PlayerCarryOvers[i]); // The player is invulnerable for a short time after spawning ptr->SetInvulnerability(serverConfig.SpawnInvulnerableSecs * FrameTimer::FramesPerSecond, Actors::Player::InvulnerableType::Blinking); } ApplyGameModeToAllPlayers(serverConfig.GameMode); _hud = CreateHUD(); _hud->BeginFadeIn((levelInit.LastExitType & ExitType::FastTransition) == ExitType::FastTransition); } bool MpLevelHandler::IsCheatingAllowed() { if (!_isServer) { return false; } auto& serverConfig = _networkManager->GetServerConfiguration(); return (PreferencesCache::AllowCheats && serverConfig.GameMode == MpGameMode::Cooperation); } MpGameMode MpLevelHandler::GetGameMode() const { const auto& serverConfig = _networkManager->GetServerConfiguration(); return serverConfig.GameMode; } bool MpLevelHandler::SetGameMode(MpGameMode value) { if (!_isServer) { return false; } LOGI("[MP] Game mode set to {}", NetworkManager::GameModeToString(value)); auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.GameMode = value; ApplyGameModeToAllPlayers(serverConfig.GameMode); SynchronizeGameMode(); if (serverConfig.GameMode != MpGameMode::Cooperation) { _levelState = LevelState::Countdown3; _gameTimeLeft = FrameTimer::FramesPerSecond; SetControllableToAllPlayers(false); WarpAllPlayersToStart(); ResetAllPlayerStats(); static_cast(_hud.get())->ShowCountdown(3); SendLevelStateToAllPlayers(); } return true; } bool MpLevelHandler::SynchronizeGameMode() { if (!_isServer) { return false; } auto& serverConfig = _networkManager->GetServerConfiguration(); if (_autoWeightTreasure && (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt)) { std::int32_t playerCount = (std::int32_t)_players.size(); if (playerCount > 0) { // Take 80% of all the treasure and divide it by clamped number of players serverConfig.TotalTreasureCollected = (_totalTreasureCount * 8) / (10 * std::max(playerCount, 3)); // Round to multiples of 5 serverConfig.TotalTreasureCollected = (std::int32_t)roundf(serverConfig.TotalTreasureCollected / 5.0f) * 5; if (serverConfig.TotalTreasureCollected < 20) { serverConfig.TotalTreasureCollected = std::min(20, _totalTreasureCount); } } } std::uint8_t flags = 0; if (_isReforged) { flags |= 0x01; } if (PreferencesCache::EnableLedgeClimb) { flags |= 0x02; } if (serverConfig.Elimination) { flags |= 0x04; } for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->LevelState < PeerLevelState::LevelSynchronized) { continue; } MemoryStream packet(24); packet.WriteValue((std::uint8_t)LevelPropertyType::GameMode); packet.WriteValue(flags); packet.WriteValue((std::uint8_t)serverConfig.GameMode); packet.WriteValue(peerDesc->Team); packet.WriteVariableInt32(serverConfig.InitialPlayerHealth); packet.WriteVariableUint32(serverConfig.MaxGameTimeSecs); packet.WriteVariableUint32(serverConfig.TotalKills); packet.WriteVariableUint32(serverConfig.TotalLaps); packet.WriteVariableUint32(serverConfig.TotalTreasureCollected); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LevelSetProperty, packet); } return true; } MpPlayer* MpLevelHandler::GetWeaponOwner(Actors::ActorBase* actor) { if (auto* player = runtime_cast(actor)) { return player; } else if (auto* shotBase = runtime_cast(actor)) { return static_cast(shotBase->GetOwner()); } else if (auto* tnt = runtime_cast(actor)) { return static_cast(tnt->GetOwner()); } else { return nullptr; } } bool MpLevelHandler::ProcessCommand(const Peer& peer, StringView line, bool isAdmin) { char infoBuffer[256]; if (line == "/endpoints"_s) { if (isAdmin) { auto endpoints = _networkManager->GetServerEndpoints(); for (const auto& endpoint : endpoints) { SendMessage(peer, UI::MessageLevel::Confirm, endpoint); } auto& serverConfig = _networkManager->GetServerConfiguration(); StringView address; std::uint16_t port; if (NetworkManagerBase::TrySplitAddressAndPort(serverConfig.ServerAddressOverride, address, port)) { if (port == 0) { port = serverConfig.ServerPort; } std::size_t length = formatInto(infoBuffer, "{}:{} (Override)", address, port); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); } return true; } } else if (line == "/info"_s) { auto& serverConfig = _networkManager->GetServerConfiguration(); std::size_t length = formatInto(infoBuffer, "Server: {}{}", serverConfig.ServerName, serverConfig.IsPrivate ? " (Private)" : ""); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); length = formatInto(infoBuffer, "Current level: \"{}\" ({}{}{})", _levelName, NetworkManager::GameModeToString(serverConfig.GameMode), serverConfig.Elimination ? "/Elimination" : "", _isReforged ? "/Reforged" : ""); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); length = formatInto(infoBuffer, "Players: {}/{}", (std::uint32_t)_networkManager->GetPeerCount(), serverConfig.MaxPlayerCount); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); if (!_players.empty()) { length = formatInto(infoBuffer, "Server load: {:.1f} ms ({:.1f})", (theApplication().GetFrameTimer().GetLastFrameDuration() * 1000.0f), theApplication().GetFrameTimer().GetAverageFps()); } else { length = formatInto(infoBuffer, "Server load: - ms"); } SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); auto uptimeSecs = (DateTime::UtcNow().ToUnixMilliseconds() / 1000) - (std::int64_t)serverConfig.StartUnixTimestamp; auto hours = (std::int32_t)(uptimeSecs / 3600); auto minutes = (std::int32_t)(uptimeSecs % 3600) / 60; auto seconds = (std::int32_t)(uptimeSecs % 60); length = formatInto(infoBuffer, "Uptime: {}:{:.2}:{:.2}", hours, minutes, seconds); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); if (isAdmin) { length = formatInto(infoBuffer, "Config Path: \"{}\"", serverConfig.FilePath); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); } if (!serverConfig.Playlist.empty()) { length = formatInto(infoBuffer, "Playlist: {}/{}{}", (std::uint32_t)(serverConfig.PlaylistIndex + 1), (std::uint32_t)serverConfig.Playlist.size(), serverConfig.RandomizePlaylist ? " (Random)" : ""); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); } return true; } else if (line == "/players"_s) { auto& serverConfig = _networkManager->GetServerConfiguration(); SendMessage(peer, UI::MessageLevel::Confirm, "List of connected players:"_s); StringView playerName; char playerNameBuffer[64]; for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { if (!peerDesc->RemotePeer && !peerDesc->Player) { continue; } if (peerDesc->Player) { std::size_t length = formatInto(playerNameBuffer, "{}#{}", peerDesc->PlayerName, peerDesc->Player->_playerIndex); playerName = { playerNameBuffer, length }; } else { playerName = peerDesc->PlayerName; } std::size_t length; if (_levelState < LevelState::Running) { length = formatInto(infoBuffer, "{}.\t{}\t │ {} ms\t │ P: {}\t │ I: {}\f[w:50] s\f[/w]", peerDesc->PositionInRound, playerName, peerDesc->RemotePeer ? peerDesc->RemotePeer._enet->roundTripTime : 0, peerDesc->Points, peerDesc->Player ? (std::int32_t)(peerDesc->IdleElapsedFrames * FrameTimer::SecondsPerFrame) : -1); } else if (serverConfig.GameMode == MpGameMode::Race || serverConfig.GameMode == MpGameMode::Race) { length = formatInto(infoBuffer, "{}.\t{}\t │ {} ms\t │ P: {}\t │ K: {}\t │ D: {}\t │ I: {}\f[w:50] s\f[/w]\t│ Laps: {}/{}", peerDesc->PositionInRound, playerName, peerDesc->RemotePeer ? peerDesc->RemotePeer._enet->roundTripTime : 0, peerDesc->Points, peerDesc->Kills, peerDesc->Deaths, peerDesc->Player ? (std::int32_t)(peerDesc->IdleElapsedFrames * FrameTimer::SecondsPerFrame) : -1, peerDesc->Laps + 1, serverConfig.TotalLaps); } else if (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt) { length = formatInto(infoBuffer, "{}.\t{}\t │ {} ms\t │ P: {}\t │ K: {}\t │ D: {}\t │ I: {}\f[w:50] s\f[/w]\t│ Treasure: {}", peerDesc->PositionInRound, playerName, peerDesc->RemotePeer ? peerDesc->RemotePeer._enet->roundTripTime : 0, peerDesc->Points, peerDesc->Kills, peerDesc->Deaths, peerDesc->Player ? (std::int32_t)(peerDesc->IdleElapsedFrames * FrameTimer::SecondsPerFrame) : -1, peerDesc->TreasureCollected); } else { length = formatInto(infoBuffer, "{}.\t{}\t │ {} ms\t │ P: {}\t │ K: {}\t │ D: {}\t │ I: {}\f[w:50] s\f[/w]", peerDesc->PositionInRound, playerName, peerDesc->RemotePeer ? peerDesc->RemotePeer._enet->roundTripTime : 0, peerDesc->Points, peerDesc->Kills, peerDesc->Deaths, peerDesc->Player ? (std::int32_t)(peerDesc->IdleElapsedFrames * FrameTimer::SecondsPerFrame) : -1); } SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); } return true; } else if (line.hasPrefix("/set "_s)) { if (isAdmin) { auto [variableName, sep, value] = line.exceptPrefix("/set "_s).trimmedPrefix().partition(' '); if (variableName == "mode"_s) { auto gameModeString = StringUtils::lowercase(value.trimmed()); MpGameMode gameMode; if (gameModeString == "battle"_s || gameModeString == "b"_s) { gameMode = MpGameMode::Battle; } else if (gameModeString == "teambattle"_s || gameModeString == "tb"_s) { gameMode = MpGameMode::TeamBattle; } else if (gameModeString == "race"_s || gameModeString == "r"_s) { gameMode = MpGameMode::Race; } else if (gameModeString == "teamrace"_s || gameModeString == "tr"_s) { gameMode = MpGameMode::TeamRace; } else if (gameModeString == "treasurehunt"_s || gameModeString == "th"_s) { gameMode = MpGameMode::TreasureHunt; } else if (gameModeString == "teamtreasurehunt"_s || gameModeString == "tth"_s) { gameMode = MpGameMode::TeamTreasureHunt; } else if (gameModeString == "capturetheflag"_s || gameModeString == "ctf"_s) { gameMode = MpGameMode::CaptureTheFlag; } else if (gameModeString == "cooperation"_s || gameModeString == "coop"_s || gameModeString == "c"_s) { gameMode = MpGameMode::Cooperation; } else { return false; } if (SetGameMode(gameMode)) { std::size_t length = formatInto(infoBuffer, "Game mode set to \f[w:80]\f[c:#707070]{}\f[/c]\f[/w]", NetworkManager::GameModeToString(gameMode)); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); return true; } } else if (variableName == "level"_s) { LevelInitialization levelInit; PrepareNextLevelInitialization(levelInit); auto level = value.partition('/'); if (value.contains('/')) { levelInit.LevelName = value; } else { levelInit.LevelName = "unknown/"_s + value; } if (ContentResolver::Get().LevelExists(levelInit.LevelName)) { LOGD("[MP] Changing level to \"{}\"", levelInit.LevelName); auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.PlaylistIndex = -1; levelInit.LastExitType = ExitType::Normal; HandleLevelChange(std::move(levelInit)); } else { SendMessage(peer, UI::MessageLevel::Confirm, "Level doesn't exist"); } return true; } else if (variableName == "welcome"_s) { SetWelcomeMessage(StringUtils::replaceAll(value.trimmed(), "\\n"_s, "\n"_s)); SendMessage(peer, UI::MessageLevel::Confirm, "Lobby message changed"); return true; } else if (variableName == "name"_s) { auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.ServerName = value.trimmed(); std::size_t length; if (!serverConfig.ServerName.empty()) { length = formatInto(infoBuffer, "Server name set to \f[w:80]\f[c:#707070]{}\f[/c]\f[/w]", serverConfig.ServerName); } else if (!serverConfig.IsPrivate) { length = formatInto(infoBuffer, "Server visibility to \f[w:80]\f[c:#707070]hidden\f[/c]\f[/w]"); } SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); return true; } else if (variableName == "spawning"_s) { auto boolValue = StringUtils::lowercase(value.trimmed()); if (boolValue == "false"_s || boolValue == "off"_s || boolValue == "0"_s) { _enableSpawning = false; } else if (boolValue == "true"_s || boolValue == "on"_s || boolValue == "1"_s) { _enableSpawning = true; } else { return false; } std::size_t length = formatInto(infoBuffer, "Spawning set to \f[w:80]\f[c:#707070]{}\f[/c]\f[/w]", _enableSpawning ? "Enabled"_s : "Disabled"_s); SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); return true; } else if (variableName == "kills"_s) { value = value.trimmed(); auto intValue = stou32(value.data(), value.size()); if (intValue <= 0 || intValue > INT32_MAX) { SendMessage(peer, UI::MessageLevel::Confirm, "Value out of range"_s); return true; } auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.TotalKills = intValue; SynchronizeGameMode(); SendMessage(peer, UI::MessageLevel::Confirm, "Value changed successfully"_s); return true; } else if (variableName == "laps"_s) { value = value.trimmed(); auto intValue = stou32(value.data(), value.size()); if (intValue <= 0 || intValue > INT32_MAX) { SendMessage(peer, UI::MessageLevel::Confirm, "Value out of range"_s); return true; } auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.TotalLaps = intValue; SynchronizeGameMode(); SendMessage(peer, UI::MessageLevel::Confirm, "Value changed successfully"_s); return true; } else if (variableName == "treasure"_s) { value = value.trimmed(); auto intValue = stou32(value.data(), value.size()); if (intValue < 0 || intValue > INT32_MAX) { SendMessage(peer, UI::MessageLevel::Confirm, "Value out of range"_s); return true; } auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.TotalTreasureCollected = intValue; _autoWeightTreasure = (serverConfig.TotalTreasureCollected == 0); SynchronizeGameMode(); SendMessage(peer, UI::MessageLevel::Confirm, "Value changed successfully"_s); return true; } } } else if (line == "/refresh"_s) { if (isAdmin) { auto& serverConfig = _networkManager->GetServerConfiguration(); auto prevGameMode = serverConfig.GameMode; _networkManager->RefreshServerConfiguration(); // Refresh all affected properties SetWelcomeMessage(serverConfig.WelcomeMessage); if (serverConfig.PlaylistIndex >= 0 && serverConfig.PlaylistIndex < serverConfig.Playlist.size()) { ApplyFromPlaylist(); } else if (serverConfig.GameMode != prevGameMode) { SetGameMode(serverConfig.GameMode); } SendMessage(peer, UI::MessageLevel::Info, "Server configuration reloaded"_s); } return true; } else if (line == "/reset points"_s) { if (isAdmin) { ResetPeerPoints(); SendMessage(peer, UI::MessageLevel::Confirm, "All points reset"_s); } return true; } else if (line.hasPrefix("/alert "_s)) { if (isAdmin) { StringView message = line.exceptPrefix("/alert "_s).trimmed(); ShowAlertToAllPlayers(message); } return true; } else if (line == "/skip"_s) { if (isAdmin) { auto& serverConfig = _networkManager->GetServerConfiguration(); if (_levelState == LevelState::PreGame && _players.size() >= serverConfig.MinPlayerCount) { _gameTimeLeft = 0.0f; } } return true; } else if (line.hasPrefix("/playlist"_s)) { if (isAdmin) { auto& serverConfig = _networkManager->GetServerConfiguration(); StringView value = (line.size() > 10 ? line.exceptPrefix(10).trimmed() : StringView()); if (!value.empty()) { std::uint32_t idx = stou32(value.data(), value.size()); if (idx < serverConfig.Playlist.size()) { serverConfig.PlaylistIndex = idx; ApplyFromPlaylist(); } } else { SkipInPlaylist(); } } return true; } else if (line.hasPrefix("/ip "_s)) { if (isAdmin) { if (auto playerName = line.exceptPrefix("/ip "_s)) { std::int32_t playerIndex = (playerName.hasPrefix('#') ? stou32(&playerName[1], playerName.size() - 1) : -1); for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { if (playerIndex >= 0 ? (peerDesc->Player && peerDesc->Player->_playerIndex == playerIndex) : (peerDesc->PlayerName == playerName)) { std::size_t length; if (peerDesc->RemotePeer) { length = formatInto(infoBuffer, "{} ({}) is connected from {}", peerDesc->PlayerName, NetworkManager::UuidToString(peerDesc->UniquePlayerID), NetworkManagerBase::AddressToString(peerDesc->RemotePeer)); } else { length = formatInto(infoBuffer, "{} is connected locally", peerDesc->PlayerName); } SendMessage(peer, UI::MessageLevel::Confirm, { infoBuffer, length }); break; } } } } return true; } else if (line.hasPrefix("/kick "_s)) { if (isAdmin) { if (auto playerName = line.exceptPrefix("/kick "_s)) { std::int32_t playerIndex = (playerName.hasPrefix('#') ? stou32(&playerName[1], playerName.size() - 1) : -1); for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->RemotePeer) { if (playerIndex >= 0 ? (peerDesc->Player && peerDesc->Player->_playerIndex == playerIndex) : (peerDesc->PlayerName == playerName)) { _networkManager->Kick(peerDesc->RemotePeer, Reason::Kicked); break; } } } } } return true; } else if (line == "/kill"_s) { auto peers = _networkManager->GetPeers(); auto it = peers->find(peer); if (it != peers->end()) { InvokeAsync([peer = it->second]() { if (peer->Player) { peer->Player->TakeDamage(INT32_MAX, 0.0f, true); } }); } return true; } else if (line.hasPrefix("/kill "_s)) { if (isAdmin) { if (auto playerName = line.exceptPrefix("/kill "_s)) { std::int32_t playerIndex = (playerName.hasPrefix('#') ? stou32(&playerName[1], playerName.size() - 1) : -1); for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->Player) { if (playerIndex >= 0 ? (peerDesc->Player->_playerIndex == playerIndex) : (peerDesc->PlayerName == playerName)) { // TODO: Don't count it as a death std::uint32_t prevDeaths = peerDesc->Deaths; peerDesc->Player->TakeDamage(INT32_MAX, 0.0f, true); peerDesc->Deaths = prevDeaths; break; } } } } } return true; } else if (line.hasPrefix("/vote "_s)) { if (_activePoll != VoteType::None) { if (isAdmin && line == "/vote cancel"_s) { _activePoll = VoteType::None; SendMessageToAll("Active poll has been cancelled"); return true; } else { std::size_t length = formatInto(infoBuffer, "Another poll is already active, please wait {} seconds", (std::int32_t)(_activePollTimeLeft * FrameTimer::SecondsPerFrame)); SendMessage(peer, UI::MessageLevel::Error, { infoBuffer, length }); return true; } } if (auto typeStr = line.exceptPrefix("/vote "_s)) { VoteType type; if (typeStr == "restart"_s) { // TODO: Support also non-playlist mode type = VoteType::Restart; SendMessageToAll("Poll started to \f[c:#777777]restart playlist\f[/c], type \f[c:#587050]/yes\f[/c] to vote"_s); } else if (typeStr == "reset points"_s) { type = VoteType::ResetPoints; SendMessageToAll("Poll started to \f[c:#777777]reset points\f[/c], type \f[c:#587050]/yes\f[/c] to vote"_s); } else if (typeStr == "skip"_s) { // TODO: Support also non-playlist mode type = VoteType::Skip; SendMessageToAll("Poll started to \f[c:#777777]skip current level\f[/c], type \f[c:#587050]/yes\f[/c] to vote"_s); } else if (typeStr == "kick"_s) { // TODO: Allow to vote kick for a specific player //type = VoteType::Kick; //formatString(infoBuffer, "Poll started to \f[c:#777777]kick player ...\f[/c], type \f[c:#608050]/yes\f[/c] to vote", typeStr.data()); //SendServerMessageToAll(infoBuffer); return true; } else { std::size_t length = formatInto(infoBuffer, "Unsupported poll type \"{}\"", typeStr); SendMessage(peer, UI::MessageLevel::Error, { infoBuffer, length }); return true; } _activePoll = type; _activePollTimeLeft = 60.0f * FrameTimer::FramesPerSecond; for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { peerDesc->VotedYes = (playerPeer == peer); } return true; } else { return false; } } else if (line == "/yes"_s) { if (_activePoll != VoteType::None) { auto peers = _networkManager->GetPeers(); auto it = peers->find(peer); if (it != peers->end()) { if (!it->second->VotedYes) { it->second->VotedYes = true; SendMessage(peer, UI::MessageLevel::Confirm, "Successfully voted"_s); } else { SendMessage(peer, UI::MessageLevel::Confirm, "Already voted"_s); } } } else { SendMessage(peer, UI::MessageLevel::Error, "No poll is active"_s); } return true; } return false; } void MpLevelHandler::SendMessage(const Peer& peer, UI::MessageLevel level, StringView message) { if (!peer.IsValid()) { if (ContentResolver::Get().IsHeadless()) { switch (level) { default: __DEATH_TRACE(TraceLevel::Info, {}, "< │ {}", message); break; case UI::MessageLevel::Echo: __DEATH_TRACE(TraceLevel::Info, {}, "> │ {}", message); break; case UI::MessageLevel::Warning: __DEATH_TRACE(TraceLevel::Warning, {}, "< │ {}", message); break; case UI::MessageLevel::Error: __DEATH_TRACE(TraceLevel::Error, {}, "< │ {}", message); break; case UI::MessageLevel::Assert: __DEATH_TRACE(TraceLevel::Assert, {}, "< │ {}", message); break; case UI::MessageLevel::Fatal: __DEATH_TRACE(TraceLevel::Fatal, {}, "< │ {}", message); break; } } else { _console->WriteLine(UI::MessageLevel::Info, message); } return; } MemoryStream packetOut(9 + message.size()); packetOut.WriteVariableUint32(0); // Local player ID packetOut.WriteValue((std::uint8_t)level); packetOut.WriteVariableUint32((std::uint32_t)message.size()); packetOut.Write(message.data(), (std::uint32_t)message.size()); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ChatMessage, packetOut); } void MpLevelHandler::SendMessageToAll(StringView message, bool asChatFromServer) { String prefixedMessage = message; if (asChatFromServer) { prefixedMessage = "\f[c:#907060]Server:\f[/c] "_s + prefixedMessage; } MemoryStream packetOut(9 + message.size()); packetOut.WriteVariableUint32(0); // Local player ID packetOut.WriteValue((std::uint8_t)UI::MessageLevel::Chat); packetOut.WriteVariableUint32((std::uint32_t)prefixedMessage.size()); packetOut.Write(prefixedMessage.data(), (std::uint32_t)prefixedMessage.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->IsAuthenticated); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ChatMessage, packetOut); InvokeAsync([this, message = std::move(prefixedMessage)]() mutable { _console->WriteLine(UI::MessageLevel::Info, message); }); } bool MpLevelHandler::OnPeerDisconnected(const Peer& peer) { constexpr Vector2f OutOfBounds = Vector2f(-1000000.0f, -1000000.0f); if (_isServer) { if (auto peerDesc = _networkManager->GetPeerDescriptor(peer)) { peerDesc->IsAuthenticated = false; peerDesc->LevelState = PeerLevelState::Unknown; InvokeAsync([this, peerDesc]() mutable { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] disconnected", peerDesc->PlayerName)); }); MemoryStream packet(10 + peerDesc->PlayerName.size()); packet.WriteValue((std::uint8_t)PeerPropertyType::Disconnected); packet.WriteVariableUint64((std::uint64_t)peer._enet); packet.WriteValue((std::uint8_t)peerDesc->PlayerName.size()); packet.Write(peerDesc->PlayerName.data(), (std::uint32_t)peerDesc->PlayerName.size()); _networkManager->SendTo([otherPeer = peer](const Peer& peer) { return (peer != otherPeer); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PeerSetProperty, packet); if (MpPlayer* player = peerDesc->Player) { std::int32_t playerIndex = player->_playerIndex; Vector2f pos = player->_pos; for (std::size_t i = 0; i < _players.size(); i++) { if (_players[i] == player) { _players.eraseUnordered(i); break; } } // Move the player out of the bounds to avoid triggering events player->_pos = OutOfBounds; player->SetState(Actors::ActorState::IsDestroyed, true); MemoryStream packet(4); packet.WriteVariableUint32(playerIndex); _networkManager->SendTo([otherPeer = peer](const Peer& peer) { return (peer != otherPeer); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::DestroyRemoteActor, packet); const auto& serverConfig = _networkManager->GetServerConfiguration(); if (_levelState == LevelState::WaitingForMinPlayers) { _waitingForPlayerCount = (std::int32_t)serverConfig.MinPlayerCount - (std::int32_t)_players.size(); InvokeAsync([this]() { SendLevelStateToAllPlayers(); }); } else if (_levelState == LevelState::Running && (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt)) { // Drop all collected treasure if (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt) { std::uint32_t treasureLost = peerDesc->TreasureCollected; if (treasureLost > 0) { peerDesc->TreasureCollected = 0; InvokeAsync([this, treasureLost, pos]() { for (std::uint32_t i = 0; i < treasureLost; i++) { float dir = (Random().NextBool() ? -1.0f : 1.0f); float force = Random().Next(10.0f, 20.0f); Vector3f spawnPos = Vector3f(pos.X, pos.Y, MainPlaneZ); std::uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] = { 0, 0x04 }; auto actor = _eventSpawner.SpawnEvent(EventType::Gem, spawnParams, Actors::ActorState::None, spawnPos.As()); if (actor != nullptr) { actor->AddExternalForce(dir * force, force); AddActor(actor); } } }); } } if (_autoWeightTreasure) { SynchronizeGameMode(); } } if (_activeBoss != nullptr && _nextLevelType == ExitType::None) { // If a boss is active and the last player left, roll everything back bool anyoneAlive = false; for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->Player && peerDesc->Player != player && peerDesc->Player->_health > 0) { anyoneAlive = true; break; } } if (!anyoneAlive) { Vector2f checkpointPos = player->_checkpointPos; InvokeAsync([this, player, checkpointPos]() { if (_activeBoss->OnPlayerDied()) { _activeBoss = nullptr; } LimitCameraView(nullptr, checkpointPos, 0, 0); LOGI("Rolling back to checkpoint"); for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->Player && peerDesc->Player != player) { peerDesc->Player->Respawn(checkpointPos); if (peerDesc->RemotePeer) { peerDesc->LastUpdated = UINT64_MAX; static_cast(peerDesc->Player)->_canTakeDamage = false; MemoryStream packet2(12); packet2.WriteVariableUint32(peerDesc->Player->_playerIndex); packet2.WriteValue((std::int32_t)(checkpointPos.X * 512.0f)); packet2.WriteValue((std::int32_t)(checkpointPos.Y * 512.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerRespawn, packet2); } } } RollbackLevelState(); BeginPlayMusic(_musicDefaultPath); }); } } } CalculatePositionInRound(); } return true; } return false; } bool MpLevelHandler::OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t packetType, ArrayView data) { if DEATH_UNLIKELY(_ignorePackets) { // IRootController is probably going to load a new level in a moment, so ignore all packets now return false; } if (_isServer) { switch ((ClientPacketType)packetType) { case ClientPacketType::Rpc: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); // TODO: remotingActor-> OnPacketReceived() is also locked here, which is not good std::unique_lock lock(_lock); for (const auto& [remotingActor, remotingActorInfo] : _remotingActors) { if (remotingActorInfo.ActorID == actorId) { LOGD("[MP] ClientPacketType::Rpc [{:.8x}] - id: {}, {} bytes", std::uint64_t(peer._enet), actorId, data.size() - packet.GetPosition()); remotingActor->OnPacketReceived(packet); return true; } } LOGW("[MP] ClientPacketType::Rpc [{:.8x}] - id: {}, {} bytes - ACTOR NOT FOUND", std::uint64_t(peer._enet), actorId, data.size() - packet.GetPosition()); return true; } case ClientPacketType::Auth: { if (auto peerDesc = _networkManager->GetPeerDescriptor(peer)) { peerDesc->LevelState = PeerLevelState::ValidatingAssets; InvokeAsync([this, peerDesc]() mutable { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] connected", peerDesc->PlayerName)); }); MemoryStream packet(10 + peerDesc->PlayerName.size()); packet.WriteValue((std::uint8_t)PeerPropertyType::Connected); packet.WriteVariableUint64((std::uint64_t)peer._enet); packet.WriteValue((std::uint8_t)peerDesc->PlayerName.size()); packet.Write(peerDesc->PlayerName.data(), (std::uint32_t)peerDesc->PlayerName.size()); _networkManager->SendTo([otherPeer = peer](const Peer& peer) { return (peer != otherPeer); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PeerSetProperty, packet); } MemoryStream packet; InitializeValidateAssetsPacket(packet); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ValidateAssets, packet); return true; } case ClientPacketType::LevelReady: { MemoryStream packet(data); std::uint8_t flags = packet.ReadValue(); LOGD("[MP] ClientPacketType::LevelReady [{:.8x}] - flags: 0x{:.2x}", std::uint64_t(peer._enet), flags); if (auto peerDesc = _networkManager->GetPeerDescriptor(peer)) { bool enableLedgeClimb = (flags & 0x02) != 0; peerDesc->EnableLedgeClimb = enableLedgeClimb; if (peerDesc->LevelState < PeerLevelState::LevelLoaded) { peerDesc->LevelState = PeerLevelState::LevelLoaded; } if (peerDesc->PreferredPlayerType == PlayerType::None) { auto& serverConfig = _networkManager->GetServerConfiguration(); // Show in-game lobby only to newly connected players std::uint8_t flags = 0x01 | 0x02 | 0x04; // Set Visibility | Show | SetWelcomeMessage std::uint8_t allowedCharacters = serverConfig.AllowedPlayerTypes; MemoryStream packet(6 + serverConfig.WelcomeMessage.size()); packet.WriteValue(flags); packet.WriteValue(allowedCharacters); packet.WriteVariableUint32(serverConfig.WelcomeMessage.size()); packet.Write(serverConfig.WelcomeMessage.data(), serverConfig.WelcomeMessage.size()); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ShowInGameLobby, packet); } } return true; } case ClientPacketType::ChatMessage: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->Player == nullptr || peerDesc->Player->_playerIndex != playerIndex) { LOGD("[MP] ClientPacketType::ChatMessage [{:.8x}] - invalid playerIndex ({})", std::uint64_t(peer._enet), playerIndex); return true; } /*std::uint8_t reserved =*/ packet.ReadValue(); std::uint32_t lineLength = packet.ReadVariableUint32(); if (lineLength == 0 || lineLength > 1024) { LOGD("[MP] ClientPacketType::ChatMessage [{:.8x}] - length out of bounds ({})", std::uint64_t(peer._enet), lineLength); return true; } String line{NoInit, lineLength}; packet.Read(line.data(), lineLength); if (line.hasPrefix('/')) { SendMessage(peer, UI::MessageLevel::Echo, line); ProcessCommand(peer, line, peerDesc->IsAdmin); return true; } String prefixedMessage; if (peerDesc->IsAdmin) { prefixedMessage = "\f[c:#907060]"_s + peerDesc->PlayerName + ":\f[/c] "_s + line; } else { prefixedMessage = "\f[c:#709060]"_s + peerDesc->PlayerName + ":\f[/c] "_s + line; } MemoryStream packetOut(9 + prefixedMessage.size()); packetOut.WriteVariableUint32(playerIndex); packetOut.WriteValue((std::uint8_t)UI::MessageLevel::Chat); packetOut.WriteVariableUint32((std::uint32_t)prefixedMessage.size()); packetOut.Write(prefixedMessage.data(), (std::uint32_t)prefixedMessage.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState != PeerLevelState::Unknown); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ChatMessage, packetOut); InvokeAsync([this, line = std::move(prefixedMessage)]() mutable { _console->WriteLine(UI::MessageLevel::Chat, std::move(line)); }); return true; } case ClientPacketType::ValidateAssetsResponse: { auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->LevelState != PeerLevelState::ValidatingAssets) { // Packet received when the peer is in different state LOGW("[MP] ClientPacketType::ValidateAssetsResponse [{:.8x}] - invalid state ({})", std::uint64_t(peer._enet), peerDesc->LevelState); return true; } peerDesc->LevelState = PeerLevelState::StreamingMissingAssets; bool success = true; SmallVector missingAssets; MemoryStream packet(data); std::uint32_t assetCount = packet.ReadVariableUint32(); LOGD("[MP] ClientPacketType::ValidateAssetsResponse [{:.8x}] - {}/{} assets", std::uint64_t(peer._enet), assetCount, _requiredAssets.size()); if (assetCount == _requiredAssets.size()) { for (std::uint32_t i = 0; i < assetCount; i++) { AssetType type = (AssetType)packet.ReadValue(); std::uint32_t pathLength = packet.ReadVariableUint32(); String path{NoInit, pathLength}; packet.Read(path.data(), pathLength); std::int64_t size = packet.ReadVariableInt64(); std::uint32_t crc32 = packet.ReadValue(); bool found = false; for (std::size_t j = 0; j < _requiredAssets.size(); j++) { if (type == _requiredAssets[j].Type && path == _requiredAssets[j].Path) { found = true; if (size != _requiredAssets[j].Size || crc32 != _requiredAssets[j].Crc32) { LOGD("[MP] ClientPacketType::ValidateAssetsResponse [{:.8x}] - \"{}\":{:.8x} is missing", std::uint64_t(peer._enet), _requiredAssets[j].Path, _requiredAssets[j].Crc32); missingAssets.push_back(&_requiredAssets[j]); } break; } } if (!found) { // This asset wasn't requested, something went wrong success = false; } } } else { // Asset count mismatch, something went wrong success = false; } if (!success) { // Peer response is corrupted LOGW("[MP] ClientPacketType::ValidateAssetsResponse [{:.8x}] - malformed packet", std::uint64_t(peer._enet)); _networkManager->Kick(peer, Reason::InvalidParameter); return true; } LOGI("[MP] ClientPacketType::ValidateAssetsResponse [{:.8x}] - {} missing assets", std::uint64_t(peer._enet), missingAssets.size()); if (missingAssets.empty()) { // All assets are already ready MemoryStream packet; InitializeLoadLevelPacket(packet); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LoadLevel, packet); return true; } const auto& serverConfig = _networkManager->GetServerConfiguration(); if (!serverConfig.AllowAssetStreaming) { // Server doesn't allow downloads, kick the client instead _networkManager->Kick(peer, Reason::AssetStreamingNotAllowed); return true; } Thread streamingThread([_this = runtime_cast(shared_from_this()), peer, peerDesc = std::move(peerDesc), missingAssets = std::move(missingAssets)]() { LOGI("[MP] Started streaming {} assets to peer [{:.8x}]", missingAssets.size(), std::uint64_t(peer._enet)); TimeStamp begin = TimeStamp::now(); for (std::uint32_t i = 0; i < (std::uint32_t)missingAssets.size(); i++) { if (!peerDesc->IsAuthenticated || _this->_ignorePackets) { // Stop streaming if peer disconnects or handler has changed break; } const RequiredAsset& asset = *missingAssets[i]; MemoryStream packetBegin(14 + asset.Path.size()); packetBegin.WriteValue(1); // Begin packetBegin.WriteValue((std::uint8_t)asset.Type); packetBegin.WriteVariableUint32((std::uint32_t)asset.Path.size()); packetBegin.Write(asset.Path.data(), (std::int64_t)asset.Path.size()); packetBegin.WriteVariableInt64(asset.Size); _this->_networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::StreamAsset, packetBegin); auto s = fs::Open(asset.FullPath, FileAccess::Read); if (s->IsValid()) { char buffer[8192]; while (true) { if (!peerDesc->IsAuthenticated || _this->_ignorePackets) { // Stop streaming if peer disconnects or handler has changed break; } std::int64_t bytesRead = s->Read(buffer, sizeof(buffer)); if (bytesRead <= 0) { break; } MemoryStream packetChunk(9 + bytesRead); packetChunk.WriteValue(2); // Chunk packetChunk.WriteVariableInt64(bytesRead); packetChunk.Write(buffer, bytesRead); _this->_networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::StreamAsset, packetChunk); } } MemoryStream packetEnd(1); packetEnd.WriteValue(3); // End _this->_networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::StreamAsset, packetEnd); } LOGI("[MP] Finished streaming {} assets to peer [{:.8x}] - took {:.1f} ms", missingAssets.size(), std::uint64_t(peer._enet), begin.millisecondsSince()); if (peerDesc->IsAuthenticated && !_this->_ignorePackets) { MemoryStream packet; _this->InitializeLoadLevelPacket(packet); _this->_networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LoadLevel, packet); } }); return true; } case ClientPacketType::PlayerReady: { MemoryStream packet(data); PlayerType preferredPlayerType = (PlayerType)packet.ReadValue(); std::uint8_t preferredTeam = packet.ReadValue(); auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->LevelState != PeerLevelState::LevelLoaded && peerDesc->LevelState != PeerLevelState::LevelSynchronized) { LOGW("[MP] ClientPacketType::PlayerReady [{:.8x}] - invalid state", std::uint64_t(peer._enet)); return true; } if (preferredPlayerType != PlayerType::Jazz && preferredPlayerType != PlayerType::Spaz && preferredPlayerType != PlayerType::Lori) { LOGW("[MP] ClientPacketType::PlayerReady [{:.8x}] - invalid preferred player type", std::uint64_t(peer._enet)); return true; } peerDesc->PreferredPlayerType = preferredPlayerType; LOGI("[MP] ClientPacketType::PlayerReady [{:.8x}] - type: {}, team: {}", std::uint64_t(peer._enet), preferredPlayerType, preferredTeam); InvokeAsync([this, peer]() { auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (!peerDesc || peerDesc->LevelState < PeerLevelState::LevelLoaded || peerDesc->LevelState > PeerLevelState::LevelSynchronized) { LOGW("[MP] ClientPacketType::PlayerReady [{:.8x}] - invalid state", std::uint64_t(peer._enet)); return; } if (peerDesc->LevelState == PeerLevelState::LevelSynchronized) { peerDesc->LevelState = PeerLevelState::PlayerReady; } }); return true; } case ClientPacketType::ForceResyncActors: { LOGD("[MP] ClientPacketType::ForceResyncActors [{:.8x}] - update: {}", std::uint64_t(peer._enet), _lastUpdated); _forceResyncPending = true; return true; } case ClientPacketType::PlayerUpdate: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->Player == nullptr || peerDesc->Player->_playerIndex != playerIndex) { LOGD("[MP] ClientPacketType::PlayerUpdate [{:.8x}] - invalid playerIndex ({})", std::uint64_t(peer._enet), playerIndex); return true; } std::uint64_t now = packet.ReadVariableUint64(); if DEATH_UNLIKELY(peerDesc->LastUpdated >= now) { return true; } peerDesc->LastUpdated = now; float posX = packet.ReadValue() / 512.0f; float posY = packet.ReadValue() / 512.0f; float speedX = packet.ReadValue() / 512.0f; float speedY = packet.ReadValue() / 512.0f; RemotePlayerOnServer::PlayerFlags flags = (RemotePlayerOnServer::PlayerFlags)packet.ReadVariableUint32(); /*bool justWarped = (flags & PlayerFlags::JustWarped) == PlayerFlags::JustWarped; if (justWarped) { std::uint64_t seqNumWarped = packet.ReadVariableUint64(); if (seqNumWarped == it2->second.WarpSeqNum) { justWarped = false; } else { LOGD("Acknowledged player {} warp for sequence #{}", playerIndex, seqNumWarped); it2->second.WarpSeqNum = seqNumWarped; MemoryStream packet2(13); packet2.WriteValue((std::uint8_t)ServerPacketType::PlayerAckWarped); packet2.WriteVariableUint32(playerIndex); packet2.WriteVariableUint64(seqNumWarped); _networkManager->SendTo(peer, NetworkChannel::Main, packet2); } } if ((it2->second.Flags & PlayerFlags::JustWarped) == PlayerFlags::JustWarped) { // Player already warped locally, mark the request as handled if (it2->second.WarpTimeLeft > 0.0f) { LOGD("Granted permission to player {} to warp asynchronously", playerIndex); it2->second.WarpTimeLeft = 0.0f; } else if (!justWarped) { LOGD("Granted permission to player {} to warp before client", playerIndex); it2->second.WarpTimeLeft = -90.0f; } } else if (justWarped) { // Player warped remotely but not locally, keep some time to resync with local state if (it2->second.WarpTimeLeft < 0.0f) { // Server is faster than client (probably due to higher latency) LOGD("Player already warped locally"); it2->second.WarpTimeLeft = 0.0f; } else { // Client is faster than server LOGD("Player warped remotely (local permission pending)"); it2->second.WarpTimeLeft = 90.0f; } } else if (it2->second.WarpTimeLeft <= 0.0f) { constexpr float MaxDeviation = 256.0f; float posDiffSqr = (Vector2f(posX, posY) - player->_pos).SqrLength(); float speedSqr = std::max(player->_speed.SqrLength(), Vector2f(speedX, speedY).SqrLength()); if (posDiffSqr > speedSqr + (MaxDeviation * MaxDeviation)) { LOGW("Player {} position mismatch by {} pixels (speed: {:.2f})", playerIndex, (std::int32_t)sqrt(posDiffSqr), sqrt(speedSqr)); posX = player->_pos.X; posY = player->_pos.Y; MemoryStream packet2(13); packet2.WriteValue((std::uint8_t)ServerPacketType::PlayerMoveInstantly); packet2.WriteVariableUint32(player->_playerIndex); packet2.WriteValue((std::int32_t)(posX * 512.0f)); packet2.WriteValue((std::int32_t)(posY * 512.0f)); packet2.WriteValue((std::int16_t)(player->_speed.X * 512.0f)); packet2.WriteValue((std::int16_t)(player->_speed.Y * 512.0f)); _networkManager->SendTo(peer, NetworkChannel::Main, packet2); } }*/ // TODO: Special move if (auto* remotePlayerOnServer = runtime_cast(peerDesc->Player)) { remotePlayerOnServer->SyncWithServer(Vector2f(posX, posY), Vector2f(speedX, speedY), flags); } return true; } case ClientPacketType::PlayerKeyPress: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->Player == nullptr || peerDesc->Player->_playerIndex != playerIndex) { return true; } if (auto* remotePlayerOnServer = runtime_cast(peerDesc->Player)) { std::uint32_t frameCount = theApplication().GetFrameCount(); if (remotePlayerOnServer->UpdatedFrame != frameCount) { remotePlayerOnServer->UpdatedFrame = frameCount; remotePlayerOnServer->PressedKeysLast = remotePlayerOnServer->PressedKeys; } remotePlayerOnServer->PressedKeys = packet.ReadVariableUint64(); } //LOGD("Player {} pressed 0x{:.8x}, last state was 0x{:.8x}", playerIndex, it->second.PressedKeys & 0xffffffffu, prevState); return true; } case ClientPacketType::PlayerChangeWeaponRequest: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->Player == nullptr || peerDesc->Player->_playerIndex != playerIndex) { LOGD("[MP] ClientPacketType::PlayerChangeWeaponRequest [{:.8x}] - invalid playerIndex ({})", std::uint64_t(peer._enet), playerIndex); return true; } std::uint8_t weaponType = packet.ReadValue(); LOGD("[MP] ClientPacketType::PlayerChangeWeaponRequest [{:.8x}] - playerIndex: {}, weaponType: {}", std::uint64_t(peer._enet), playerIndex, weaponType); const auto& playerAmmo = peerDesc->Player->GetWeaponAmmo(); if (weaponType >= playerAmmo.size() || playerAmmo[weaponType] == 0) { LOGD("[MP] ClientPacketType::PlayerChangeWeaponRequest [{:.8x}] - playerIndex: {}, no ammo in selected weapon", std::uint64_t(peer._enet), playerIndex); // Request is denied, send the current weapon back to the client HandlePlayerWeaponChanged(peerDesc->Player, Actors::Player::SetCurrentWeaponReason::Rollback); return true; } peerDesc->Player->SetCurrentWeapon((WeaponType)weaponType, Actors::Player::SetCurrentWeaponReason::User); return true; } case ClientPacketType::PlayerAckWarped: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); std::uint64_t seqNum = packet.ReadVariableUint64(); float posX = packet.ReadValue() / 512.0f; float posY = packet.ReadValue() / 512.0f; float speedX = packet.ReadValue() / 512.0f; float speedY = packet.ReadValue() / 512.0f; auto peerDesc = _networkManager->GetPeerDescriptor(peer); if (peerDesc->Player == nullptr || peerDesc->Player->_playerIndex != playerIndex) { LOGD("[MP] ClientPacketType::PlayerAckWarped [{:.8x}] - invalid playerIndex ({})", std::uint64_t(peer._enet), playerIndex); return true; } LOGD("[MP] ClientPacketType::PlayerAckWarped [{:.8x}] - playerIndex: {}, seqNum: {}, x: {}, y: {}", std::uint64_t(peer._enet), playerIndex, seqNum, posX, posY); peerDesc->LastUpdated = seqNum; if (auto* mpPlayer = static_cast(peerDesc->Player)) { mpPlayer->ForceResyncWithServer(Vector2f(posX, posY), Vector2f(speedX, speedY)); mpPlayer->_canTakeDamage = true; mpPlayer->_justWarped = true; } break; } } } else { switch ((ServerPacketType)packetType) { case ServerPacketType::Rpc: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); std::shared_ptr actor; { std::unique_lock lock(_lock); auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { actor = it->second; } } if (actor) { LOGD("[MP] ServerPacketType::Rpc - id: {}, {} bytes", actorId, data.size() - packet.GetPosition()); actor->OnPacketReceived(packet); } else { LOGW("[MP] ServerPacketType::Rpc - id: {}, {} bytes - ACTOR NOT FOUND", actorId,data.size() - packet.GetPosition()); } return true; } case ServerPacketType::PeerSetProperty: { MemoryStream packet(data); PeerPropertyType type = (PeerPropertyType)packet.ReadValue(); DEATH_UNUSED std::uint64_t peerId = packet.ReadVariableUint64(); switch (type) { case PeerPropertyType::Connected: case PeerPropertyType::Disconnected: { std::uint8_t playerNameLength = packet.ReadValue(); String playerName{NoInit, playerNameLength}; packet.Read(playerName.data(), playerNameLength); LOGD("[MP] ServerPacketType::PeerSetProperty - type: {}, peer: 0x{:.8x}, name: \"{}\"", type, peerId, playerName); InvokeAsync([this, type, playerName = std::move(playerName)]() mutable { if (type == PeerPropertyType::Connected) { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] connected", playerName)); } else { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] disconnected", playerName)); } }); break; } case PeerPropertyType::Roasted: { std::uint8_t victimNameLength = packet.ReadValue(); String victimName{NoInit, victimNameLength}; packet.Read(victimName.data(), victimNameLength); DEATH_UNUSED std::uint64_t attackerPeerId = packet.ReadVariableUint64(); std::uint8_t attackerNameLength = packet.ReadValue(); String attackerName{NoInit, attackerNameLength}; packet.Read(attackerName.data(), attackerNameLength); LOGD("[MP] ServerPacketType::PeerSetProperty - type: {}, victim: 0x{:.8x}, victim-name: \"{}\", attacker: 0x{:.8x}, attacker-name: \"{}\"", type, peerId, victimName, attackerPeerId, attackerName); InvokeAsync([this, victimName = std::move(victimName), attackerName = std::move(attackerName)]() mutable { if (!attackerName.empty()) { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] was roasted by \f[c:#d0705d]{}\f[/c]", victimName, attackerName)); } else { _console->WriteLine(UI::MessageLevel::Info, _f("\f[c:#d0705d]{}\f[/c] was roasted by environment", victimName)); } }); break; } } break; } case ServerPacketType::LoadLevel: { // Start to ignore all incoming packets, because they no longer belong to this handler _ignorePackets = true; LOGD("[MP] ServerPacketType::LoadLevel"); break; } case ServerPacketType::LevelSetProperty: { MemoryStream packet(data); LevelPropertyType propertyType = (LevelPropertyType)packet.ReadValue(); switch (propertyType) { case LevelPropertyType::State: { LevelState state = (LevelState)packet.ReadValue(); std::int32_t gameTimeLeft = packet.ReadVariableInt32(); LOGD("[MP] ServerPacketType::LevelSetProperty::State - state: {}, timeLeft: {:.1f}", state, (float)gameTimeLeft * 0.01f); InvokeAsync([this, state, gameTimeLeft]() { _levelState = state; _gameTimeLeft = (float)gameTimeLeft * 0.01f; switch (_levelState) { case LevelState::WaitingForMinPlayers: { // gameTimeLeft is reused for waitingForPlayerCount in this state _waitingForPlayerCount = gameTimeLeft; break; } case LevelState::Countdown3: { static_cast(_hud.get())->ShowCountdown(3); LOGI("Starting round..."); break; } case LevelState::Countdown2: { static_cast(_hud.get())->ShowCountdown(2); break; } case LevelState::Countdown1: { static_cast(_hud.get())->ShowCountdown(1); break; } case LevelState::Running: { const auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode != MpGameMode::Cooperation) { static_cast(_hud.get())->ShowCountdown(0); } break; } } }); break; } case LevelPropertyType::GameMode: { std::uint8_t flags = packet.ReadValue(); MpGameMode gameMode = (MpGameMode)packet.ReadValue(); std::uint8_t teamId = packet.ReadValue(); std::int32_t initialPlayerHealth = packet.ReadVariableInt32(); std::uint32_t maxGameTimeSecs = packet.ReadVariableUint32(); std::uint32_t totalKills = packet.ReadVariableUint32(); std::uint32_t totalLaps = packet.ReadVariableUint32(); std::uint32_t totalTreasureCollected = packet.ReadVariableUint32(); LOGD("[MP] ServerPacketType::LevelSetProperty::GameMode - mode: {}", gameMode); auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.GameMode = gameMode; serverConfig.ReforgedGameplay = (flags & 0x01) != 0; serverConfig.Elimination = (flags & 0x04) != 0; serverConfig.InitialPlayerHealth = initialPlayerHealth; serverConfig.MaxGameTimeSecs = maxGameTimeSecs; serverConfig.TotalKills = totalKills; serverConfig.TotalLaps = totalLaps; serverConfig.TotalTreasureCollected = totalTreasureCollected; _isReforged = serverConfig.ReforgedGameplay; if (auto peerDesc = _networkManager->GetPeerDescriptor(LocalPeer)) { peerDesc->Team = teamId; } break; } case LevelPropertyType::Music: { std::uint8_t flags = packet.ReadValue(); std::uint32_t pathLength = packet.ReadVariableUint32(); String path{NoInit, pathLength}; packet.Read(path.data(), pathLength); LOGD("[MP] ServerPacketType::LevelSetProperty::Music - path: \"{}\", flags: {}", path, flags); InvokeAsync([this, flags, path = std::move(path)]() { bool setDefault = (flags & 0x01) != 0; bool forceReload = (flags & 0x02) != 0; BeginPlayMusic(path, setDefault, forceReload); }); break; } default: { LOGD("[MP] ServerPacketType::LevelSetProperty - received unknown property {}", (std::uint32_t)propertyType); break; } } return true; } case ServerPacketType::ShowInGameLobby: { auto& serverConfig = _networkManager->GetServerConfiguration(); MemoryStream packet(data); std::uint8_t flags = packet.ReadValue(); std::uint8_t allowedPlayerTypes = packet.ReadValue(); if (flags & 0x04) { // Welcome message std::uint32_t welcomeMessageLength = packet.ReadVariableUint32(); serverConfig.WelcomeMessage = String(NoInit, welcomeMessageLength); packet.Read(serverConfig.WelcomeMessage.data(), welcomeMessageLength); } if (flags & 0x08) { // TODO: Welcome server logo std::uint32_t serverLogoLength = packet.ReadVariableUint32(); Array serverLogo{NoInit, serverLogoLength}; packet.Read(serverLogo.data(), serverLogoLength); } LOGD("[MP] ServerPacketType::ShowInGameLobby - flags: 0x{:.2x}, allowedPlayerTypes: 0x{:.2x}, message: \"{}\"", flags, allowedPlayerTypes, serverConfig.WelcomeMessage); if (flags & 0x01) { InvokeAsync([this, flags, allowedPlayerTypes]() { _inGameLobby->SetAllowedPlayerTypes(allowedPlayerTypes); if (flags & 0x02) { _inGameLobby->Show(); } else { _inGameLobby->Hide(); } }); } return true; } case ServerPacketType::FadeOut: { MemoryStream packet(data); std::int32_t fadeOutDelay = packet.ReadVariableInt32(); LOGD("[MP] ServerPacketType::FadeOut - delay: {}", fadeOutDelay); InvokeAsync([this, fadeOutDelay]() { if (_hud != nullptr) { _hud->BeginFadeOut((float)fadeOutDelay); } #if defined(WITH_AUDIO) if (_sugarRushMusic != nullptr) { _sugarRushMusic->stop(); _sugarRushMusic = nullptr; } if (_music != nullptr) { _music->stop(); _music = nullptr; } #endif }); return true; } case ServerPacketType::PlaySfx: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); float gain = halfToFloat(packet.ReadValue()); float pitch = halfToFloat(packet.ReadValue()); std::uint32_t identifierLength = packet.ReadVariableUint32(); String identifier = String(NoInit, identifierLength); packet.Read(identifier.data(), identifierLength); // TODO: Use only lock here InvokeAsync([this, actorId, gain, pitch, identifier = std::move(identifier)]() { if (_lastSpawnedActorId == actorId) { if (!_players.empty()) { _players[0]->PlaySfx(identifier, gain, pitch); } } else { std::unique_lock lock(_lock); auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { // TODO: gain, pitch, ... it->second->PlaySfx(identifier, gain, pitch); } } }); return true; } case ServerPacketType::PlayCommonSfx: { MemoryStream packet(data); std::int32_t posX = packet.ReadVariableInt32(); std::int32_t posY = packet.ReadVariableInt32(); float gain = halfToFloat(packet.ReadValue()); float pitch = halfToFloat(packet.ReadValue()); std::uint32_t identifierLength = packet.ReadVariableUint32(); String identifier(NoInit, identifierLength); packet.Read(identifier.data(), identifierLength); // TODO: Use only lock here InvokeAsync([this, posX, posY, gain, pitch, identifier = std::move(identifier)]() { std::unique_lock lock(_lock); PlayCommonSfx(identifier, Vector3f((float)posX, (float)posY, 0.0f), gain, pitch); }); return true; } case ServerPacketType::ShowAlert: { MemoryStream packet(data); /*std::uint8_t flags =*/ packet.ReadValue(); std::uint32_t textLength = packet.ReadVariableUint32(); String text = String(NoInit, textLength); packet.Read(text.data(), textLength); LOGD("[MP] ServerPacketType::ShowAlert - text: \"{}\"", text); _hud->ShowLevelText(text); return true; } case ServerPacketType::ChatMessage: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); UI::MessageLevel level = (UI::MessageLevel)packet.ReadValue(); std::uint32_t messageLength = packet.ReadVariableUint32(); if (messageLength == 0 || messageLength > 1024) { LOGD("[MP] ServerPacketType::ChatMessage - length out of bounds ({})", messageLength); return true; } String message{NoInit, messageLength}; packet.Read(message.data(), messageLength); if (level == UI::MessageLevel::Chat && playerIndex == _lastSpawnedActorId) { level = UI::MessageLevel::Echo; } InvokeAsync([this, level, message = std::move(message)]() mutable { _console->WriteLine(level, std::move(message)); }); return true; } case ServerPacketType::SyncTileMap: { MemoryStream packet(data); LOGD("[MP] ServerPacketType::SyncTileMap"); // TODO: No lock here ??? TileMap()->InitializeFromStream(packet); return true; } case ServerPacketType::SetTrigger: { MemoryStream packet(data); std::uint8_t triggerId = packet.ReadValue(); bool newState = (bool)packet.ReadValue(); LOGD("[MP] ServerPacketType::SetTrigger - id: {}, state: {}", triggerId, newState); InvokeAsync([this, triggerId, newState]() { TileMap()->SetTrigger(triggerId, newState); }); return true; } case ServerPacketType::AdvanceTileAnimation: { MemoryStream packet(data); std::int32_t tx = packet.ReadVariableInt32(); std::int32_t ty = packet.ReadVariableInt32(); std::int32_t amount = packet.ReadVariableInt32(); LOGD("[MP] ServerPacketType::AdvanceTileAnimation - tx: {}, ty: {}, amount: {}", tx, ty, amount); InvokeAsync([this, tx, ty, amount]() { TileMap()->AdvanceDestructibleTileAnimation(tx, ty, amount); }); return true; } case ServerPacketType::CreateDebris: { MemoryStream packet(data); std::uint8_t effect = packet.ReadValue(); std::uint32_t actorId = packet.ReadVariableUint32(); LOGD("[MP] ServerPacketType::CreateDebris - effect: {}, actorId: {}", effect, actorId); if (effect == UINT8_MAX) { AnimState state = (AnimState)packet.ReadVariableUint32(); std::int32_t count = packet.ReadVariableInt32(); InvokeAsync([this, actorId, state, count]() { std::unique_lock lock(_lock); auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { it->second->CreateSpriteDebris(state, count); } else { LOGW("[MP] ServerPacketType::CreateDebris - NOT FOUND - actorId: {}", actorId); } }); } else { float x = packet.ReadVariableInt32() * 0.01f; float y = packet.ReadVariableInt32() * 0.01f; InvokeAsync([this, actorId, effect, x, y]() { std::unique_lock lock(_lock); auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { it->second->CreateParticleDebrisOnPerish((Actors::ParticleDebrisEffect)effect, Vector2f(x, y)); } else { LOGW("[MP] ServerPacketType::CreateDebris - NOT FOUND - actorId: {}", actorId); } }); } return true; } case ServerPacketType::CreateControllablePlayer: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); PlayerType playerType = (PlayerType)packet.ReadValue(); std::int32_t health = packet.ReadVariableInt32(); std::uint8_t flags = packet.ReadValue(); std::uint8_t teamId = packet.ReadValue(); std::int32_t posX = packet.ReadVariableInt32(); std::int32_t posY = packet.ReadVariableInt32(); LOGI("[MP] ServerPacketType::CreateControllablePlayer - playerIndex: {}, playerType: {}, health: {}, flags: {}, team: {}, x: {}, y: {}", playerIndex, playerType, health, flags, teamId, posX, posY); _lastSpawnedActorId = playerIndex; InvokeAsync([this, playerType, health, flags, teamId, posX, posY]() { auto peerDesc = _networkManager->GetPeerDescriptor(LocalPeer); std::shared_ptr player = std::make_shared(peerDesc); std::uint8_t playerParams[2] = { (std::uint8_t)playerType, 0 }; player->OnActivated(Actors::ActorActivationDetails( this, Vector3i(posX, posY, PlayerZ), playerParams )); player->SetHealth(health); player->_controllable = (flags & 0x01) != 0; player->_controllableExternal = (flags & 0x02) != 0; peerDesc->Team = teamId; peerDesc->LapStarted = TimeStamp::now(); Actors::Multiplayer::RemotablePlayer* ptr = player.get(); _players.push_back(ptr); AddActor(player); AssignViewport(ptr); // TODO: Needed to drop and reinitialize newly assigned viewport, because it's called asynchronously, not from handler initialization Viewport::GetChain().clear(); Vector2i res = theApplication().GetResolution(); OnInitializeViewport(res.X, res.Y); // TODO: Fade in should be skipped sometimes _hud->BeginFadeIn(false); }); return true; } case ServerPacketType::CreateRemoteActor: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); std::uint8_t flags = packet.ReadValue(); std::int32_t posX = packet.ReadVariableInt32(); std::int32_t posY = packet.ReadVariableInt32(); std::int32_t posZ = packet.ReadVariableInt32(); Actors::ActorState state = (Actors::ActorState)packet.ReadVariableUint32(); std::uint32_t metadataLength = packet.ReadVariableUint32(); String metadataPath = String(NoInit, metadataLength); packet.Read(metadataPath.data(), metadataLength); AnimState anim = (AnimState)packet.ReadVariableUint32(); float rotation = packet.ReadValue() * fRadAngle360 / UINT16_MAX; float scaleX = (float)Half{packet.ReadValue()}; float scaleY = (float)Half{packet.ReadValue()}; Actors::ActorRendererType rendererType = (Actors::ActorRendererType)packet.ReadValue(); //LOGD("Remote actor {} created on [{};{}] with metadata \"{}\"", actorId, posX, posY, metadataPath); LOGD("[MP] ServerPacketType::CreateRemoteActor - actorId: {}, metadata: \"{}\", x: {}, y: {}", actorId, metadataPath, posX, posY); InvokeAsync([this, actorId, flags, posX, posY, posZ, state, metadataPath = std::move(metadataPath), anim, rotation, scaleX, scaleY, rendererType]() { { std::unique_lock lock(_lock); if (_remoteActors.contains(actorId)) { LOGW("[MP] ServerPacketType::CreateRemoteActor - actor ({}) already exists", actorId); return; } } std::shared_ptr remoteActor = std::make_shared(); remoteActor->OnActivated(Actors::ActorActivationDetails(this, Vector3i(posX, posY, posZ))); remoteActor->AssignMetadata(flags, state, metadataPath, anim, rotation, scaleX, scaleY, rendererType); { std::unique_lock lock(_lock); _remoteActors[actorId] = remoteActor; } AddActor(remoteActor); }); return true; } case ServerPacketType::CreateMirroredActor: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); EventType eventType = (EventType)packet.ReadVariableUint32(); StaticArray eventParams(NoInit); packet.Read(eventParams, Events::EventSpawner::SpawnParamsSize); Actors::ActorState actorFlags = (Actors::ActorState)packet.ReadVariableUint32(); std::int32_t tileX = packet.ReadVariableInt32(); std::int32_t tileY = packet.ReadVariableInt32(); std::int32_t posZ = packet.ReadVariableInt32(); LOGD("[MP] ServerPacketType::CreateMirroredActor - actorId: {}, event: {}, x: {}, y: {}", actorId, eventType, tileX * 32 + 16, tileY * 32 + 16); InvokeAsync([this, actorId, eventType, eventParams = std::move(eventParams), actorFlags, tileX, tileY, posZ]() { { std::unique_lock lock(_lock); if (_remoteActors.contains(actorId)) { LOGW("[MP] ServerPacketType::CreateMirroredActor - actor ({}) already exists", actorId); return; } } // TODO: Remove const_cast std::shared_ptr actor =_eventSpawner.SpawnEvent(eventType, const_cast(eventParams.data()), actorFlags, tileX, tileY, posZ); if (actor != nullptr) { { std::unique_lock lock(_lock); _remoteActors[actorId] = actor; } AddActor(actor); } else { LOGD("[MP] ServerPacketType::CreateMirroredActor - CANNOT CREATE - actorId: {}", actorId); } }); return true; } case ServerPacketType::DestroyRemoteActor: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); LOGD("[MP] ServerPacketType::DestroyRemoteActor - actorId: {}", actorId); InvokeAsync([this, actorId]() { std::unique_lock lock(_lock); auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { it->second->SetState(Actors::ActorState::IsDestroyed, true); _remoteActors.erase(it); _playerNames.erase(actorId); } else { LOGW("[MP] ServerPacketType::DestroyRemoteActor - NOT FOUND - actorId: {}", actorId); } }); return true; } case ServerPacketType::UpdateAllActors: { MemoryStream packetCompressed(data); DeflateStream packet(packetCompressed); std::uint32_t now = packet.ReadVariableUint32(); float elapsedFrames = (float)packet.ReadVariableUint64(); std::uint32_t actorCount = packet.ReadVariableUint32(); bool forceResyncInvoked = (actorCount & 1) == 1; if DEATH_UNLIKELY(!forceResyncInvoked && _lastUpdated >= now) { return true; } bool forceResyncRequired = (!forceResyncInvoked && _lastUpdated + 1 < now); if (forceResyncRequired) { LOGD("[MP] ServerPacketType::UpdateAllActors - FORCE RESYNC REQUIRED ({} -> {})", _lastUpdated, now); } else if (forceResyncInvoked) { LOGD("[MP] ServerPacketType::UpdateAllActors - FORCE RESYNC INVOKED ({} -> {})", _lastUpdated, now); } std::unique_lock lock(_lock); _lastUpdated = now; _elapsedFrames = lerp(_elapsedFrames, elapsedFrames + _networkManager->GetRoundTripTimeMs() * FrameTimer::FramesPerSecond * 0.002f, 0.05f); actorCount >>= 1; for (std::uint32_t i = 0; i < actorCount; i++) { std::uint32_t actorId = packet.ReadVariableUint32(); std::uint8_t flags = packet.ReadValue(); bool positionChanged = (flags & 0x01) != 0; bool animationChanged = (flags & 0x02) != 0; float posX, posY, rotation, scaleX, scaleY; AnimState anim; Actors::ActorRendererType rendererType; if (positionChanged) { posX = packet.ReadValue() / 512.0f; posY = packet.ReadValue() / 512.0f; } else { posX = 0.0f; posY = 0.0f; } if (animationChanged) { anim = (AnimState)packet.ReadVariableUint32(); rotation = packet.ReadValue() * fRadAngle360 / UINT16_MAX; scaleX = (float)Half{packet.ReadValue()}; scaleY = (float)Half{packet.ReadValue()}; rendererType = (Actors::ActorRendererType)packet.ReadValue(); } else { anim = AnimState::Idle; rotation = 0.0f; scaleX = 0.0f; scaleY = 0.0f; rendererType = Actors::ActorRendererType::Default; } auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { if (auto* remoteActor = runtime_cast(it->second.get())) { if (positionChanged) { remoteActor->SyncPositionWithServer(Vector2f(posX, posY)); } if (animationChanged) { remoteActor->SyncAnimationWithServer(anim, rotation, scaleX, scaleY, rendererType); } remoteActor->SyncMiscWithServer(flags); } } } if (forceResyncRequired) { _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::ForceResyncActors, {}); } return true; } case ServerPacketType::ChangeRemoteActorMetadata: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); std::uint8_t flags = packet.ReadValue(); std::uint32_t metadataLength = packet.ReadVariableUint32(); String metadataPath = String(NoInit, metadataLength); packet.Read(metadataPath.data(), metadataLength); LOGD("[MP] ServerPacketType::ChangeRemoteActorMetadata - id: {}, metadata: \"{}\"", actorId, metadataPath); InvokeAsync([this, actorId, metadataPath = std::move(metadataPath)]() mutable { std::unique_lock lock(_lock); auto it = _remoteActors.find(actorId); if (it != _remoteActors.end()) { if (auto* remoteActor = runtime_cast(it->second.get())) { remoteActor->RequestMetadata(metadataPath); } } }); return true; break; } case ServerPacketType::MarkRemoteActorAsPlayer: { MemoryStream packet(data); std::uint32_t actorId = packet.ReadVariableUint32(); std::uint32_t playerNameLength = packet.ReadVariableUint32(); String playerName = String(NoInit, playerNameLength); packet.Read(playerName.data(), playerNameLength); LOGD("[MP] ServerPacketType::MarkRemoteActorAsPlayer - id: {}, name: \"{}\"", actorId, playerName); InvokeAsync([this, actorId, playerName = std::move(playerName)]() mutable { if (actorId == _lastSpawnedActorId) { auto peerDesc = _networkManager->GetPeerDescriptor(LocalPeer); peerDesc->PlayerName = std::move(playerName); } else { _playerNames[actorId] = std::move(playerName); } }); return true; } case ServerPacketType::UpdatePositionsInRound: { MemoryStream packet(data); std::uint32_t count = packet.ReadVariableUint32(); _positionsInRound.resize_for_overwrite(count); for (std::uint32_t i = 0; i < count; i++) { std::uint32_t playerIdx = packet.ReadVariableUint32(); std::uint32_t positionInRound = packet.ReadVariableUint32(); std::uint32_t pointsInRound = packet.ReadVariableUint32(); _positionsInRound[i] = { playerIdx, positionInRound, pointsInRound }; } return true; } case ServerPacketType::PlayerSetProperty: { MemoryStream packet(data); PlayerPropertyType propertyType = (PlayerPropertyType)packet.ReadValue(); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerSetProperty - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } switch (propertyType) { case PlayerPropertyType::PlayerType: { PlayerType type = (PlayerType)packet.ReadValue(); InvokeAsync([this, type]() { if (!_players.empty()) { _players[0]->MorphTo(type); } }); break; } case PlayerPropertyType::Lives: { std::int32_t lives = packet.ReadVariableInt32(); InvokeAsync([this, lives]() { if (!_players.empty()) { _players[0]->_lives = lives; } }); break; } case PlayerPropertyType::Health: { std::int32_t health = packet.ReadVariableInt32(); InvokeAsync([this, health]() { if (!_players.empty()) { _players[0]->SetHealth(health); } }); break; } case PlayerPropertyType::Controllable: { std::uint8_t enable = packet.ReadValue(); InvokeAsync([this, enable]() { if (!_players.empty()) { _players[0]->_controllableExternal = (enable != 0); } }); break; } case PlayerPropertyType::Invulnerable: { std::int32_t timeLeft = packet.ReadVariableInt32(); Actors::Player::InvulnerableType type = (Actors::Player::InvulnerableType)packet.ReadValue(); InvokeAsync([this, timeLeft, type]() { if (!_players.empty()) { _players[0]->SetInvulnerability(float(timeLeft), type); } }); break; } case PlayerPropertyType::Modifier: { Actors::Player::Modifier modifier = (Actors::Player::Modifier)packet.ReadValue(); std::uint32_t decorActorId = packet.ReadVariableUint32(); InvokeAsync([this, modifier, decorActorId]() { std::unique_lock lock(_lock); if (!_players.empty()) { auto it = _remoteActors.find(decorActorId); _players[0]->SetModifier(modifier, it != _remoteActors.end() ? it->second : nullptr); } }); break; } case PlayerPropertyType::Dizzy: { std::int32_t timeLeft = packet.ReadVariableInt32(); InvokeAsync([this, timeLeft]() { if (!_players.empty()) { _players[0]->SetDizzy(float(timeLeft)); } }); break; } case PlayerPropertyType::Freeze: { std::int32_t timeLeft = packet.ReadVariableInt32(); InvokeAsync([this, timeLeft]() { if (!_players.empty()) { _players[0]->Freeze(float(timeLeft)); } }); break; } case PlayerPropertyType::Shield: { ShieldType shieldType = (ShieldType)packet.ReadValue(); std::int32_t timeLeft = packet.ReadVariableInt32(); LOGD("[MP] ServerPacketType::PlayerSetProperty::Shield - shieldType: {}, timeLeft: {}", shieldType, timeLeft); InvokeAsync([this, shieldType, timeLeft]() { if (!_players.empty()) { _players[0]->SetShield(shieldType, float(timeLeft)); } }); break; } case PlayerPropertyType::LimitCameraView: { std::int32_t left = packet.ReadVariableInt32(); std::int32_t width = packet.ReadVariableInt32(); std::int32_t playerPosX = packet.ReadVariableInt32(); std::int32_t playerPosY = packet.ReadVariableInt32(); LOGD("[MP] ServerPacketType::PlayerSetProperty::LimitCameraView - left: {}, width: {}, playerPos: [{}, {}]", left, width, playerPosX, playerPosY); InvokeAsync([this, left, width, playerPosX, playerPosY]() { LimitCameraView(nullptr, Vector2f(playerPosX, playerPosY), left, width); }); break; } case PlayerPropertyType::OverrideCameraView: { float x = packet.ReadVariableInt32() * 0.01f; float y = packet.ReadVariableInt32() * 0.01f; bool topLeft = packet.ReadValue() != 0; LOGD("[MP] ServerPacketType::PlayerSetProperty::OverrideCameraView - x: {}, y: {}, topLeft: {}", x, y, topLeft); InvokeAsync([this, x, y, topLeft]() { if (!_players.empty()) { OverrideCameraView(_players[0], x, y, topLeft); } }); break; } case PlayerPropertyType::ShakeCameraView: { float duration = packet.ReadVariableInt32() * 0.01f; LOGD("[MP] ServerPacketType::PlayerSetProperty::ShakeCameraView - duration: {}", duration); InvokeAsync([this, duration]() { if (!_players.empty()) { ShakeCameraView(_players[0], duration); } }); break; } case PlayerPropertyType::WeaponAmmo: { std::uint8_t weaponType = packet.ReadValue(); std::uint16_t weaponAmmo = packet.ReadValue(); InvokeAsync([this, weaponType, weaponAmmo]() { if (!_players.empty() && weaponType < arraySize(_players[0]->_weaponAmmo)) { _players[0]->_weaponAmmo[weaponType] = weaponAmmo; } }); break; } case PlayerPropertyType::WeaponUpgrades: { std::uint8_t weaponType = packet.ReadValue(); std::uint8_t weaponUpgrades = packet.ReadValue(); InvokeAsync([this, weaponType, weaponUpgrades]() { if (!_players.empty() && weaponType < arraySize(_players[0]->_weaponUpgrades)) { _players[0]->_weaponUpgrades[weaponType] = weaponUpgrades; } }); break; } case PlayerPropertyType::Coins: { std::int32_t newCount = packet.ReadVariableInt32(); InvokeAsync([this, newCount]() { if (!_players.empty()) { _players[0]->_coins = newCount; _hud->ShowCoins(newCount); } }); break; } case PlayerPropertyType::Gems: { std::uint8_t gemType = packet.ReadValue(); std::int32_t newCount = packet.ReadVariableInt32(); InvokeAsync([this, gemType, newCount]() { if (!_players.empty() && gemType < arraySize(_players[0]->_gems)) { _players[0]->_gems[gemType] = newCount; _hud->ShowGems(gemType, newCount); } }); break; } case PlayerPropertyType::Score: { std::int32_t score = packet.ReadVariableInt32(); InvokeAsync([this, score]() { if (!_players.empty()) { _players[0]->_score = score; } }); break; } case PlayerPropertyType::Points: { std::uint32_t points = packet.ReadVariableUint32(); std::uint32_t totalPoints = packet.ReadVariableUint32(); if (!_players.empty()) { auto* player = static_cast(_players[0]); player->GetPeerDescriptor()->Points = points; } auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.TotalPlayerPoints = totalPoints; break; } /*case PlayerPropertyType::PositionInRound: { std::uint32_t positionInRound = packet.ReadVariableUint32(); if (!_players.empty()) { auto* player = static_cast(_players[0]); player->GetPeerDescriptor()->PositionInRound = positionInRound; } break; }*/ case PlayerPropertyType::Deaths: { std::uint32_t deaths = packet.ReadVariableUint32(); if (!_players.empty()) { auto* player = static_cast(_players[0]); player->GetPeerDescriptor()->Deaths = deaths; } break; } case PlayerPropertyType::Kills: { std::uint32_t kills = packet.ReadVariableUint32(); if (!_players.empty()) { auto* player = static_cast(_players[0]); player->GetPeerDescriptor()->Kills = kills; } break; } case PlayerPropertyType::Laps: { std::uint32_t currentLaps = packet.ReadVariableUint32(); //std::uint32_t totalLaps = packet.ReadVariableUint32(); if (!_players.empty()) { auto* player = static_cast(_players[0]); auto peerDesc = player->GetPeerDescriptor(); peerDesc->Laps = currentLaps; peerDesc->LapStarted = TimeStamp::now(); } break; } case PlayerPropertyType::TreasureCollected: { std::uint32_t treasureCollected = packet.ReadVariableUint32(); if (!_players.empty()) { auto* player = static_cast(_players[0]); player->GetPeerDescriptor()->TreasureCollected = treasureCollected; } break; } default: { LOGD("[MP] ServerPacketType::PlayerSetProperty - received unknown property {}", propertyType); break; } } return true; } case ServerPacketType::PlayerResetProperties: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerSetProperty - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } if (!_players.empty()) { auto* player = static_cast(_players[0]); auto peerDesc = player->GetPeerDescriptor(); peerDesc->PositionInRound = 0; peerDesc->Deaths = 0; peerDesc->Kills = 0; peerDesc->Laps = 0; peerDesc->LapStarted = TimeStamp::now(); peerDesc->TreasureCollected = 0; peerDesc->DeathElapsedFrames = FLT_MAX; player->_coins = 0; std::memset(player->_gems, 0, sizeof(player->_gems)); std::memset(player->_weaponAmmo, 0, sizeof(player->_weaponAmmo)); std::memset(player->_weaponAmmoCheckpoint, 0, sizeof(player->_weaponAmmoCheckpoint)); player->_coinsCheckpoint = 0; std::memset(player->_gemsCheckpoint, 0, sizeof(player->_gemsCheckpoint)); std::memset(player->_weaponUpgrades, 0, sizeof(player->_weaponUpgrades)); std::memset(player->_weaponUpgradesCheckpoint, 0, sizeof(player->_weaponUpgradesCheckpoint)); player->_weaponAmmo[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; player->_weaponAmmoCheckpoint[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; player->_currentWeapon = WeaponType::Blaster; } return true; } case ServerPacketType::PlayerRespawn: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerRespawn - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } float posX = packet.ReadValue() / 512.0f; float posY = packet.ReadValue() / 512.0f; LOGD("[MP] ServerPacketType::PlayerRespawn - playerIndex: {}, x: {}, y: {}", playerIndex, posX, posY); InvokeAsync([this, posX, posY]() { if (!_players.empty()) { auto* player = _players[0]; if (!player->Respawn(Vector2f(posX, posY))) { return; } Clock& c = nCine::clock(); std::uint64_t now = c.now() * 1000 / c.frequency(); MemoryStream packetAck(24); packetAck.WriteVariableUint32(_lastSpawnedActorId); packetAck.WriteVariableUint64(now); packetAck.WriteValue((std::int32_t)(player->_pos.X * 512.0f)); packetAck.WriteValue((std::int32_t)(player->_pos.Y * 512.0f)); packetAck.WriteValue((std::int16_t)(player->_speed.X * 512.0f)); packetAck.WriteValue((std::int16_t)(player->_speed.Y * 512.0f)); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::PlayerAckWarped, packetAck); } }); return true; } case ServerPacketType::PlayerMoveInstantly: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { return true; } float posX = packet.ReadValue() / 512.0f; float posY = packet.ReadValue() / 512.0f; float speedX = packet.ReadValue() / 512.0f; float speedY = packet.ReadValue() / 512.0f; LOGD("[MP] ServerPacketType::PlayerMoveInstantly - playerIndex: {}, x: {}, y: {}, sx: {}, sy: {}", playerIndex, posX, posY, speedX, speedY); InvokeAsync([this, posX, posY, speedX, speedY]() { if (!_players.empty()) { auto* player = static_cast(_players[0]); player->MoveRemotely(Vector2f(posX, posY), Vector2f(speedX, speedY)); } }); return true; } case ServerPacketType::PlayerAckWarped: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); std::uint64_t seqNum = packet.ReadVariableUint64(); LOGD("[MP] ServerPacketType::PlayerAckWarped - playerIndex: {}, seqNum: {}", playerIndex, seqNum); if (_lastSpawnedActorId == playerIndex && _seqNumWarped == seqNum) { _seqNumWarped = 0; } return true; } case ServerPacketType::PlayerEmitWeaponFlare: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerEmitWeaponFlare - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } LOGD("[MP] ServerPacketType::PlayerEmitWeaponFlare - playerIndex: {}", playerIndex); InvokeAsync([this]() { if (!_players.empty()) { _players[0]->EmitWeaponFlare(); } }); return true; } case ServerPacketType::PlayerChangeWeapon: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerChangeWeapon - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } WeaponType weaponType = (WeaponType)packet.ReadValue(); Actors::Player::SetCurrentWeaponReason reason = (Actors::Player::SetCurrentWeaponReason)packet.ReadValue(); LOGD("[MP] ServerPacketType::PlayerChangeWeapon - playerIndex: {}, weaponType: {}, reason: {}", playerIndex, weaponType, reason); if (!_players.empty()) { auto* remotablePlayer = static_cast(_players[0]); if (reason == Actors::Player::SetCurrentWeaponReason::AddAmmo && !PreferencesCache::SwitchToNewWeapon) { HandlePlayerWeaponChanged(remotablePlayer, Actors::Player::SetCurrentWeaponReason::Rollback); return true; } remotablePlayer->ChangingWeaponFromServer = true; static_cast(remotablePlayer)->SetCurrentWeapon(weaponType, reason); remotablePlayer->ChangingWeaponFromServer = false; } return true; } case ServerPacketType::PlayerTakeDamage: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerTakeDamage - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } std::int32_t health = packet.ReadVariableInt32(); float pushForce = packet.ReadValue() / 512.0f; LOGD("[MP] ServerPacketType::PlayerTakeDamage - playerIndex: {}, health: {}, pushForce: {}", playerIndex, health, pushForce); InvokeAsync([this, health, pushForce]() { if (!_players.empty()) { _players[0]->TakeDamage(health == 0 ? INT32_MAX : (_players[0]->_health - health), pushForce, true); } }); return true; } case ServerPacketType::PlayerActivateSpring: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerActivateSpring - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } float posX = packet.ReadValue() / 512.0f; float posY = packet.ReadValue() / 512.0f; float forceX = packet.ReadValue() / 512.0f; float forceY = packet.ReadValue() / 512.0f; std::uint8_t flags = packet.ReadValue(); InvokeAsync([this, posX, posY, forceX, forceY, flags]() { if (!_players.empty()) { bool removeSpecialMove = false; _players[0]->OnHitSpring(Vector2f(posX, posY), Vector2f(forceX, forceY), (flags & 0x01) == 0x01, (flags & 0x02) == 0x02, removeSpecialMove); if (removeSpecialMove) { _players[0]->_controllable = true; _players[0]->EndDamagingMove(); } } }); return true; } case ServerPacketType::PlayerWarpIn: { MemoryStream packet(data); std::uint32_t playerIndex = packet.ReadVariableUint32(); if (_lastSpawnedActorId != playerIndex) { LOGD("[MP] ServerPacketType::PlayerWarpIn - received playerIndex {} instead of {}", playerIndex, _lastSpawnedActorId); return true; } ExitType exitType = (ExitType)packet.ReadValue(); LOGD("[MP] ServerPacketType::PlayerWarpIn - playerIndex: {}, exitType: 0x{:.2x}", playerIndex, exitType); InvokeAsync([this, exitType]() { if (!_players.empty()) { auto* player = static_cast(_players[0]); player->WarpIn(exitType); } }); return true; } } } return false; } void MpLevelHandler::LimitCameraView(Actors::Player* player, Vector2f playerPos, std::int32_t left, std::int32_t width) { std::int32_t prevLeft = _limitCameraLeft; std::int32_t prevWidth = _limitCameraWidth; _limitCameraLeft = left; _limitCameraWidth = width; LevelHandler::LimitCameraView(player, playerPos, left, width); if (_isServer && (prevLeft != _limitCameraLeft || prevWidth != _limitCameraWidth)) { for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->RemotePeer && peerDesc->Player) { MemoryStream packet(21); packet.WriteValue((std::uint8_t)PlayerPropertyType::LimitCameraView); packet.WriteVariableUint32(peerDesc->Player->_playerIndex); packet.WriteVariableInt32(left); packet.WriteVariableInt32(width); packet.WriteVariableInt32((std::int32_t)playerPos.X); packet.WriteVariableInt32((std::int32_t)playerPos.Y); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } } void MpLevelHandler::OverrideCameraView(Actors::Player* player, float x, float y, bool topLeft) { // TODO: Include this state in initial sync LevelHandler::OverrideCameraView(player, x, y, topLeft); if (_isServer) { if (auto* mpPlayer = runtime_cast(player)) { MemoryStream packet(14); packet.WriteValue((std::uint8_t)PlayerPropertyType::OverrideCameraView); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)(x * 100.0f)); packet.WriteVariableInt32((std::int32_t)(y * 100.0f)); packet.WriteValue(topLeft ? 1 : 0); _networkManager->SendTo(mpPlayer->GetPeerDescriptor()->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::ShakeCameraView(Actors::Player* player, float duration) { LevelHandler::ShakeCameraView(player, duration); if (_isServer) { if (auto* mpPlayer = runtime_cast(player)) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::ShakeCameraView); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableInt32((std::int32_t)(duration * 100.0f)); _networkManager->SendTo(mpPlayer->GetPeerDescriptor()->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::ShakeCameraViewNear(Vector2f pos, float duration) { // TODO: This should probably be client local LevelHandler::ShakeCameraViewNear(pos, duration); constexpr float MaxDistance = 800.0f; if (_isServer) { for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->RemotePeer && peerDesc->Player && (peerDesc->Player->_pos - pos).Length() <= MaxDistance) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::ShakeCameraView); packet.WriteVariableUint32(peerDesc->Player->_playerIndex); packet.WriteVariableInt32((std::int32_t)(duration * 100.0f)); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } } void MpLevelHandler::SetTrigger(std::uint8_t triggerId, bool newState) { LevelHandler::SetTrigger(triggerId, newState); if (_isServer) { MemoryStream packet(2); packet.WriteValue(triggerId); packet.WriteValue(newState); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::SetTrigger, packet); } } void MpLevelHandler::SetWeather(WeatherType type, std::uint8_t intensity) { // TODO: This should probably be client local LevelHandler::SetWeather(type, intensity); } bool MpLevelHandler::BeginPlayMusic(StringView path, bool setDefault, bool forceReload) { // TODO: Include this state in initial sync bool success = LevelHandler::BeginPlayMusic(path, setDefault, forceReload); if (_isServer) { std::uint8_t flags = 0; if (setDefault) flags |= 0x01; if (forceReload) flags |= 0x02; MemoryStream packet(6 + path.size()); packet.WriteValue((std::uint8_t)LevelPropertyType::Music); packet.WriteValue(flags); packet.WriteVariableUint32((std::uint32_t)path.size()); packet.Write(path.data(), (std::uint32_t)path.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LevelSetProperty, packet); } return success; } void MpLevelHandler::BeforeActorDestroyed(Actors::ActorBase* actor) { if (!_isServer) { return; } std::uint32_t actorId; { std::unique_lock lock(_lock); auto it = _remotingActors.find(actor); if (it == _remotingActors.end()) { return; } actorId = it->second.ActorID; _remotingActors.erase(it); _remoteActors.erase(actorId); } MemoryStream packet(4); packet.WriteVariableUint32(actorId); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::DestroyRemoteActor, packet); } void MpLevelHandler::ProcessEvents(float timeMult) { // Process events only by server if (_isServer) { LevelHandler::ProcessEvents(timeMult); } } void MpLevelHandler::InitializeRequiredAssets() { _requiredAssets.clear(); auto levelFullPath = GetAssetFullPath(AssetType::Level, _levelName); auto s = fs::Open(levelFullPath, FileAccess::Read); if (s->IsValid()) { _requiredAssets.emplace_back(AssetType::Level, _levelName, levelFullPath, s->GetSize(), nCine::crc32(*s)); } auto usedTileSetPaths = _tileMap->GetUsedTileSetPaths(); for (const auto& tileSetPath : usedTileSetPaths) { auto tileSetFullPath = GetAssetFullPath(AssetType::TileSet, tileSetPath); auto s = fs::Open(tileSetFullPath, FileAccess::Read); if (s->IsValid()) { _requiredAssets.emplace_back(AssetType::TileSet, tileSetPath, tileSetFullPath, s->GetSize(), nCine::crc32(*s)); } } if (!_musicDefaultPath.empty()) { auto musicFullPath = GetAssetFullPath(AssetType::Music, _musicDefaultPath); auto s = fs::Open(musicFullPath, FileAccess::Read); if (s->IsValid()) { _requiredAssets.emplace_back(AssetType::Music, _musicDefaultPath, musicFullPath, s->GetSize(), nCine::crc32(*s)); } } } void MpLevelHandler::SynchronizePeers() { for (auto& [peer, peerDesc] : *_networkManager->GetPeers()) { if (peerDesc->LevelState == PeerLevelState::LevelLoaded) { if DEATH_LIKELY(peerDesc != nullptr && peerDesc->PreferredPlayerType != PlayerType::None) { peerDesc->LevelState = PeerLevelState::PlayerReady; } else { peerDesc->LevelState = PeerLevelState::LevelSynchronized; } LOGI("[MP] Syncing peer [{:.8x}]", std::uint64_t(peer._enet)); // TODO: Send positions only to the newly connected player CalculatePositionInRound(true); // Synchronize level state { MemoryStream packet(6); packet.WriteValue((std::uint8_t)LevelPropertyType::State); packet.WriteValue((std::uint8_t)_levelState); packet.WriteVariableInt32(_levelState == LevelState::WaitingForMinPlayers ? _waitingForPlayerCount : (std::int32_t)(_gameTimeLeft * 100.0f)); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LevelSetProperty, packet); } // Synchronize tilemap { // TODO: Use deflate compression here? MemoryStream packet(40 * 1024); _tileMap->SerializeResumableToStream(packet); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::SyncTileMap, packet); } // Synchronize music if (_musicCurrentPath != _musicDefaultPath) { MemoryStream packet(6 + _musicCurrentPath.size()); packet.WriteValue((std::uint8_t)LevelPropertyType::Music); packet.WriteValue(0); packet.WriteVariableUint32((std::uint32_t)_musicCurrentPath.size()); packet.Write(_musicCurrentPath.data(), (std::uint32_t)_musicCurrentPath.size()); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LevelSetProperty, packet); } // Synchronize actors for (Actors::Player* otherPlayer : _players) { auto* mpOtherPlayer = static_cast(otherPlayer); auto otherPeerDesc = mpOtherPlayer->GetPeerDescriptor(); String metadataPath = fs::FromNativeSeparators(mpOtherPlayer->_metadata->Path); MemoryStream packet; InitializeCreateRemoteActorPacket(packet, mpOtherPlayer->_playerIndex, mpOtherPlayer); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateRemoteActor, packet); MemoryStream packet2(5 + otherPeerDesc->PlayerName.size()); packet2.WriteVariableUint32(mpOtherPlayer->_playerIndex); packet2.WriteValue((std::uint8_t)otherPeerDesc->PlayerName.size()); packet2.Write(otherPeerDesc->PlayerName.data(), (std::uint32_t)otherPeerDesc->PlayerName.size()); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::MarkRemoteActorAsPlayer, packet2); } // TODO: Does this need to be locked? std::unique_lock lock(_lock); for (const auto& [remotingActor, remotingActorInfo] : _remotingActors) { if DEATH_UNLIKELY(ActorShouldBeMirrored(remotingActor)) { Vector2i originTile = remotingActor->_originTile; const auto& eventTile = _eventMap->GetEventTile(originTile.X, originTile.Y); if (eventTile.Event != EventType::Empty) { MemoryStream packet(24 + Events::EventSpawner::SpawnParamsSize); packet.WriteVariableUint32(remotingActorInfo.ActorID); packet.WriteVariableUint32((std::uint32_t)eventTile.Event); packet.Write(eventTile.EventParams, Events::EventSpawner::SpawnParamsSize); packet.WriteVariableUint32((std::uint32_t)eventTile.EventFlags); packet.WriteVariableInt32((std::int32_t)originTile.X); packet.WriteVariableInt32((std::int32_t)originTile.Y); packet.WriteVariableInt32((std::int32_t)remotingActor->_renderer.layer()); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateMirroredActor, packet); } } else { MemoryStream packet; InitializeCreateRemoteActorPacket(packet, remotingActorInfo.ActorID, remotingActor); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateRemoteActor, packet); } } } else if (peerDesc->LevelState == PeerLevelState::PlayerReady) { if (_enableSpawning && _activeBoss == nullptr) { peerDesc->LevelState = PeerLevelState::PlayerSpawned; const auto& serverConfig = _networkManager->GetServerConfiguration(); Vector2f spawnPosition = (serverConfig.GameMode == MpGameMode::Cooperation && _lastCheckpointPos != Vector2f::Zero ? _lastCheckpointPos : GetSpawnPoint(peerDesc->PreferredPlayerType)); // TODO: Send ambient light (_lastCheckpointLight) std::uint8_t playerIndex = FindFreePlayerId(); LOGI("[MP] Spawning player {} [{:.8x}]", playerIndex, std::uint64_t(peer._enet)); std::shared_ptr player = std::make_shared(peerDesc); std::uint8_t playerParams[2] = { (std::uint8_t)peerDesc->PreferredPlayerType, (std::uint8_t)playerIndex }; player->OnActivated(Actors::ActorActivationDetails( this, Vector3i((std::int32_t)spawnPosition.X, (std::int32_t)spawnPosition.Y, PlayerZ - playerIndex), playerParams )); player->_controllableExternal = _controllableExternal; player->_health = (serverConfig.InitialPlayerHealth > 0 ? serverConfig.InitialPlayerHealth : (PlayerShouldHaveUnlimitedHealth(serverConfig.GameMode) ? INT32_MAX : 5)); peerDesc->LapsElapsedFrames = _elapsedFrames; peerDesc->LapStarted = TimeStamp::now(); Actors::Multiplayer::RemotePlayerOnServer* ptr = player.get(); _players.push_back(ptr); _suppressRemoting = true; AddActor(player); _suppressRemoting = false; ApplyGameModeToPlayer(serverConfig.GameMode, ptr); if (_autoWeightTreasure && (serverConfig.GameMode == MpGameMode::TreasureHunt || serverConfig.GameMode == MpGameMode::TeamTreasureHunt)) { SynchronizeGameMode(); } // Spawn the player also on the remote side { std::uint8_t flags = 0; if (player->_controllable) { flags |= 0x01; } if (player->_controllableExternal) { flags |= 0x02; } MemoryStream packet(16); packet.WriteVariableUint32(playerIndex); packet.WriteValue((std::uint8_t)player->_playerType); packet.WriteVariableInt32(player->_health); packet.WriteValue(flags); packet.WriteValue(peerDesc->Team); packet.WriteVariableInt32((std::int32_t)player->_pos.X); packet.WriteVariableInt32((std::int32_t)player->_pos.Y); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateControllablePlayer, packet); } // The player is invulnerable for a short time after spawning ptr->SetInvulnerability(serverConfig.SpawnInvulnerableSecs * FrameTimer::FramesPerSecond, Actors::Player::InvulnerableType::Blinking); if (_limitCameraLeft != 0 || _limitCameraWidth != 0) { Vector2f otherPlayerPos; for (auto* otherPlayer : _players) { if (otherPlayer != ptr) { otherPlayerPos = otherPlayer->_pos; break; } } MemoryStream packet(21); packet.WriteValue((std::uint8_t)PlayerPropertyType::LimitCameraView); packet.WriteVariableUint32(playerIndex); packet.WriteVariableInt32(_limitCameraLeft); packet.WriteVariableInt32(_limitCameraWidth); packet.WriteVariableInt32((std::int32_t)otherPlayerPos.X); packet.WriteVariableInt32((std::int32_t)otherPlayerPos.Y); _networkManager->SendTo(peer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } // Create the player also on all other clients { MemoryStream packet; InitializeCreateRemoteActorPacket(packet, playerIndex, player.get()); _networkManager->SendTo([this, self = peer](const Peer& peer) { if (peer == self) { return false; } auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::CreateRemoteActor, packet); } { MemoryStream packet(5 + peerDesc->PlayerName.size()); packet.WriteVariableUint32(playerIndex); packet.WriteValue((std::uint8_t)peerDesc->PlayerName.size()); packet.Write(peerDesc->PlayerName.data(), (std::uint32_t)peerDesc->PlayerName.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::MarkRemoteActorAsPlayer, packet); } if DEATH_UNLIKELY(_levelState == LevelState::WaitingForMinPlayers) { _waitingForPlayerCount = (std::int32_t)serverConfig.MinPlayerCount - (std::int32_t)_players.size(); SendLevelStateToAllPlayers(); } } } } } std::uint32_t MpLevelHandler::FindFreeActorId() { for (std::uint32_t i = UINT8_MAX + 1; i < UINT32_MAX - 1; i++) { if (!_remoteActors.contains(i)) { return i; } } return UINT32_MAX; } std::uint8_t MpLevelHandler::FindFreePlayerId() { // Reserve ID 0 for the local player std::size_t count = _players.size(); for (std::uint8_t i = 1; i < UINT8_MAX - 1; i++) { bool found = false; for (std::size_t j = 0; j < count; j++) { if (_players[j]->_playerIndex == i) { found = true; break; } } if (!found) { return i; } } return UINT8_MAX; } bool MpLevelHandler::IsLocalPlayer(Actors::ActorBase* actor) { return (runtime_cast(actor) || runtime_cast(actor)); } void MpLevelHandler::ApplyGameModeToAllPlayers(MpGameMode gameMode) { if (_levelState <= LevelState::Countdown1 || gameMode == MpGameMode::Cooperation) { // Everyone in single team for (auto* player : _players) { auto* mpPlayer = static_cast(player); mpPlayer->GetPeerDescriptor()->Team = 0; } } else if (gameMode == MpGameMode::TeamBattle || gameMode == MpGameMode::CaptureTheFlag || gameMode == MpGameMode::TeamRace) { // Create two teams std::int32_t playerCount = (std::int32_t)_players.size(); std::int32_t splitIdx = (playerCount + 1) / 2; SmallVector teamIds(playerCount); for (std::int32_t i = 0; i < playerCount; i++) { teamIds.push_back(i); } Random().Shuffle(arrayView(teamIds)); for (std::int32_t i = 0; i < playerCount; i++) { auto* mpPlayer = static_cast(_players[i]); mpPlayer->GetPeerDescriptor()->Team = (teamIds[i] < splitIdx ? 0 : 1); } } else { // Each player is in their own team std::int32_t playerCount = (std::int32_t)_players.size(); for (std::int32_t i = 0; i < playerCount; i++) { std::int32_t playerIdx = _players[i]->_playerIndex; DEATH_DEBUG_ASSERT(0 <= playerIdx && playerIdx < UINT8_MAX); auto* mpPlayer = static_cast(_players[i]); mpPlayer->GetPeerDescriptor()->Team = (std::uint8_t)playerIdx; } } } void MpLevelHandler::ApplyGameModeToPlayer(MpGameMode gameMode, Actors::Player* player) { auto* mpPlayer = static_cast(player); if (gameMode == MpGameMode::Cooperation) { // Everyone in single team mpPlayer->GetPeerDescriptor()->Team = 0; } else if (gameMode == MpGameMode::TeamBattle || gameMode == MpGameMode::TeamRace || gameMode == MpGameMode::TeamTreasureHunt || gameMode == MpGameMode::CaptureTheFlag) { // Create two teams std::int32_t playerCountsInTeam[2] = {}; std::int32_t playerCount = (std::int32_t)_players.size(); for (std::int32_t i = 0; i < playerCount; i++) { if (_players[i] != player) { auto* otherPlayer = static_cast(_players[i]); std::uint8_t teamId = otherPlayer->GetPeerDescriptor()->Team; if (teamId < arraySize(playerCountsInTeam)) { playerCountsInTeam[teamId]++; } } } std::uint8_t teamId = (playerCountsInTeam[0] < playerCountsInTeam[1] ? 0 : (playerCountsInTeam[0] > playerCountsInTeam[1] ? 1 : Random().Next(0, 2))); mpPlayer->GetPeerDescriptor()->Team = teamId; } else { // Each player is in their own team std::int32_t playerIdx = mpPlayer->_playerIndex; DEATH_DEBUG_ASSERT(0 <= playerIdx && playerIdx < UINT8_MAX); mpPlayer->GetPeerDescriptor()->Team = (std::uint8_t)playerIdx; } } void MpLevelHandler::ShowAlertToAllPlayers(StringView text) { if (text.empty()) { return; } ShowLevelText(text); std::uint8_t flags = 0; MemoryStream packet(5 + text.size()); packet.WriteValue(flags); packet.WriteVariableUint32((std::uint32_t)text.size()); packet.Write(text.data(), (std::uint32_t)text.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState != PeerLevelState::Unknown); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ShowAlert, packet); } void MpLevelHandler::SetControllableToAllPlayers(bool enable) { _controllableExternal = enable; for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); mpPlayer->_controllableExternal = enable; if (peerDesc->RemotePeer) { MemoryStream packet(6); packet.WriteValue((std::uint8_t)PlayerPropertyType::Controllable); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteValue(enable ? 1 : 0); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::SendLevelStateToAllPlayers() { MemoryStream packet(6); packet.WriteValue((std::uint8_t)LevelPropertyType::State); packet.WriteValue((std::uint8_t)_levelState); packet.WriteVariableInt32(_levelState == LevelState::WaitingForMinPlayers ? _waitingForPlayerCount : (std::int32_t)(_gameTimeLeft * 100.0f)); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelLoaded); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::LevelSetProperty, packet); } void MpLevelHandler::ResetAllPlayerStats() { auto& serverConfig = _networkManager->GetServerConfiguration(); for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { peerDesc->PositionInRound = 0; peerDesc->Deaths = 0; peerDesc->Kills = 0; peerDesc->Laps = 0; peerDesc->TreasureCollected = 0; peerDesc->DeathElapsedFrames = FLT_MAX; if (peerDesc->Player) { peerDesc->Player->_coins = 0; std::memset(peerDesc->Player->_gems, 0, sizeof(peerDesc->Player->_gems)); std::memset(peerDesc->Player->_weaponAmmo, 0, sizeof(peerDesc->Player->_weaponAmmo)); std::memset(peerDesc->Player->_weaponAmmoCheckpoint, 0, sizeof(peerDesc->Player->_weaponAmmoCheckpoint)); peerDesc->Player->_coinsCheckpoint = 0; std::memset(peerDesc->Player->_gemsCheckpoint, 0, sizeof(peerDesc->Player->_gemsCheckpoint)); std::memset(peerDesc->Player->_weaponUpgrades, 0, sizeof(peerDesc->Player->_weaponUpgrades)); std::memset(peerDesc->Player->_weaponUpgradesCheckpoint, 0, sizeof(peerDesc->Player->_weaponUpgradesCheckpoint)); peerDesc->Player->_weaponAmmo[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; peerDesc->Player->_weaponAmmoCheckpoint[(std::int32_t)WeaponType::Blaster] = UINT16_MAX; peerDesc->Player->_currentWeapon = WeaponType::Blaster; peerDesc->Player->_health = (serverConfig.InitialPlayerHealth > 0 ? serverConfig.InitialPlayerHealth : (PlayerShouldHaveUnlimitedHealth(serverConfig.GameMode) ? INT32_MAX : 5)); if (peerDesc->RemotePeer) { // TODO: Send it also to peers without assigned player MemoryStream packet1(4); packet1.WriteVariableUint32(peerDesc->Player->_playerIndex); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerResetProperties, packet1); MemoryStream packet2(9); packet2.WriteValue((std::uint8_t)PlayerPropertyType::Health); packet2.WriteVariableUint32(peerDesc->Player->_playerIndex); packet2.WriteVariableInt32(peerDesc->Player->_health); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet2); } } } } Vector2f MpLevelHandler::GetSpawnPoint(PlayerType playerType) { if (!_multiplayerSpawnPoints.empty()) { // TODO: Select spawn according to team return _multiplayerSpawnPoints[Random().Next(0, _multiplayerSpawnPoints.size())].Pos; } else { Vector2f spawnPosition = EventMap()->GetSpawnPosition(playerType); if (spawnPosition.X >= 0.0f && spawnPosition.Y >= 0.0f) { return spawnPosition; } return EventMap()->GetSpawnPosition(PlayerType::Jazz); } } struct TileCoordHash { #if defined(DEATH_TARGET_32BIT) using IntHash = xxHash32Func; #else using IntHash = xxHash64Func; #endif std::size_t operator()(const Vector2i& coord) const { return IntHash()(coord.X) ^ (IntHash()(coord.Y) << 1); } }; void MpLevelHandler::ConsolidateRaceCheckpoints() { static const Vector2i Directions[] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} }; HashMap tiles; HashMap visited; SmallVector group; std::queue q; tiles.reserve(_raceCheckpoints.size()); for (const auto& tile : _raceCheckpoints) { tiles.emplace(tile, 1); } _raceCheckpoints.clear(); for (const auto& [tile, priority] : tiles) { if (!visited.try_emplace(tile, 1).second) { continue; } q.push(tile); group.clear(); while (!q.empty()) { Vector2i current = q.front(); q.pop(); group.push_back(current); for (const auto& dir : Directions) { Vector2i neighbor = Vector2i(current.X + dir.X, current.Y + dir.Y); if (tiles.contains(neighbor) && !visited.contains(neighbor)) { q.push(neighbor); visited.emplace(neighbor, 1); } } } if (group.size() == 1) { _raceCheckpoints.push_back(group[0]); } else { // Calculate center std::int32_t sumX = 0, sumY = 0; for (const auto& coord : group) { sumX += coord.X; sumY += coord.Y; } _raceCheckpoints.emplace_back(sumX / group.size(), sumY / group.size()); } } } void MpLevelHandler::WarpAllPlayersToStart() { // TODO: Reset ambient lighting for (auto* player : _players) { Vector2f spawnPosition = GetSpawnPoint(player->_playerTypeOriginal); player->SetModifier(Actors::Player::Modifier::None); player->SetShield(ShieldType::None, 0.0f); player->SetDizzy(0.0f); player->WarpToPosition(spawnPosition, WarpFlags::Default); if (auto mpPlayer = runtime_cast(player)) { mpPlayer->_canTakeDamage = true; } } } void MpLevelHandler::RollbackLevelState() { _eventMap->RollbackToCheckpoint(); _tileMap->RollbackToCheckpoint(); // Synchronize tilemap { // TODO: Use deflate compression here? MemoryStream packet(40 * 1024); _tileMap->SerializeResumableToStream(packet); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::SyncTileMap, packet); } for (auto& actor : _actors) { // Despawn all actors that were created after the last checkpoint if (actor->_spawnFrames > _checkpointFrames && !actor->GetState(Actors::ActorState::PreserveOnRollback)) { if ((actor->_state & (Actors::ActorState::IsCreatedFromEventMap | Actors::ActorState::IsFromGenerator)) != Actors::ActorState::None) { Vector2i originTile = actor->_originTile; if ((actor->_state & Actors::ActorState::IsFromGenerator) == Actors::ActorState::IsFromGenerator) { _eventMap->ResetGenerator(originTile.X, originTile.Y); } _eventMap->Deactivate(originTile.X, originTile.Y); } actor->_state |= Actors::ActorState::IsDestroyed; } } } void MpLevelHandler::CalculatePositionInRound(bool forceSend) { SmallVector, 128> sortedPlayers; SmallVector, 128> sortedDeadPlayers; auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode == MpGameMode::Unknown || serverConfig.GameMode == MpGameMode::Cooperation) { return; } for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); std::uint32_t roundPoints = 0; switch (serverConfig.GameMode) { case MpGameMode::Battle: { roundPoints = peerDesc->Kills; break; } case MpGameMode::Race: { // 1 hour penalty for every unfinished lap //roundPoints = (std::uint32_t)(peerDesc->LapsElapsedFrames + (serverConfig.TotalLaps - peerDesc->Laps) * 3600.0f * FrameTimer::FramesPerSecond); Vector2f pos = mpPlayer->_pos; float nearestCheckpoint = FLT_MAX; for (auto checkpointPos : _raceCheckpoints) { float length = (pos - Vector2f(checkpointPos.X * Tiles::TileSet::DefaultTileSize, checkpointPos.Y * Tiles::TileSet::DefaultTileSize)).Length(); if (nearestCheckpoint > length) { nearestCheckpoint = length; } } roundPoints = nearestCheckpoint + (serverConfig.TotalLaps - peerDesc->Laps) * (UINT32_MAX / 100); break; } case MpGameMode::TreasureHunt: { roundPoints = peerDesc->TreasureCollected; break; } } bool isDead = (serverConfig.Elimination && peerDesc->Deaths >= serverConfig.TotalKills); if (isDead) { sortedDeadPlayers.push_back(pair(mpPlayer, roundPoints)); } else { sortedPlayers.push_back(pair(mpPlayer, roundPoints)); } } bool lessWins = (serverConfig.GameMode == MpGameMode::Race || serverConfig.GameMode == MpGameMode::TeamRace); if (lessWins) { const auto comparator = [](const auto& a, const auto& b) -> bool { return a.second() < b.second(); }; nCine::sort(sortedPlayers.begin(), sortedPlayers.end(), comparator); nCine::sort(sortedDeadPlayers.begin(), sortedDeadPlayers.end(), comparator); } else { const auto comparator = [](const auto& a, const auto& b) -> bool { return a.second() > b.second(); }; nCine::sort(sortedPlayers.begin(), sortedPlayers.end(), comparator); nCine::sort(sortedDeadPlayers.begin(), sortedDeadPlayers.end(), comparator); } bool positionsChanged = false; std::uint32_t currentPos = 1; std::uint32_t prevPos = 0; for (std::int32_t i = 0; i < sortedPlayers.size(); i++) { std::uint32_t pos, points = sortedPlayers[i].second(); if (sortedPlayers[i].second() == 0) { pos = 0; // Don't assign valid position if player has no points } else if (i > 0 && points == sortedPlayers[i - 1].second()) { pos = prevPos; } else { pos = currentPos; prevPos = currentPos; } currentPos++; auto peerDesc = sortedPlayers[i].first()->GetPeerDescriptor(); if (peerDesc->PointsInRound != points || peerDesc->PositionInRound != pos) { peerDesc->PointsInRound = points; peerDesc->PositionInRound = pos; positionsChanged = true; /*if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::PositionInRound); packet.WriteVariableUint32(sortedPlayers[i].first()->_playerIndex); packet.WriteVariableUint32(peerDesc->PositionInRound); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); }*/ } } for (std::int32_t i = 0; i < sortedDeadPlayers.size(); i++) { std::uint32_t pos, points = sortedDeadPlayers[i].second(); if (points == 0) { pos = 0; // Don't assign valid position if player has no points } else if (i > 0 && points == sortedDeadPlayers[i - 1].second()) { pos = prevPos; } else { pos = currentPos; prevPos = currentPos; } currentPos++; auto peerDesc = sortedDeadPlayers[i].first()->GetPeerDescriptor(); if (peerDesc->PointsInRound != points || peerDesc->PositionInRound != pos) { peerDesc->PointsInRound = points; peerDesc->PositionInRound = pos; positionsChanged = true; /*if (peerDesc->RemotePeer) { MemoryStream packet(9); packet.WriteValue((std::uint8_t)PlayerPropertyType::PositionInRound); packet.WriteVariableUint32(sortedDeadPlayers[i].first()->_playerIndex); packet.WriteVariableUint32(peerDesc->PositionInRound); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); }*/ } } if (positionsChanged || forceSend) { MemoryStream packet(4 + (sortedPlayers.size() + sortedDeadPlayers.size()) * 12); packet.WriteVariableUint32(sortedPlayers.size() + sortedDeadPlayers.size()); for (std::int32_t i = 0; i < sortedPlayers.size(); i++) { auto peerDesc = sortedPlayers[i].first()->GetPeerDescriptor(); packet.WriteVariableUint32(sortedPlayers[i].first()->_playerIndex); packet.WriteVariableUint32(peerDesc->PositionInRound); packet.WriteVariableUint32(peerDesc->PointsInRound); } for (std::int32_t i = 0; i < sortedDeadPlayers.size(); i++) { auto peerDesc = sortedDeadPlayers[i].first()->GetPeerDescriptor(); packet.WriteVariableUint32(sortedDeadPlayers[i].first()->_playerIndex); packet.WriteVariableUint32(peerDesc->PositionInRound); packet.WriteVariableUint32(peerDesc->PointsInRound); } _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState != PeerLevelState::Unknown); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::UpdatePositionsInRound, packet); } } void MpLevelHandler::CheckGameEnds() { if DEATH_UNLIKELY(_levelState != LevelState::Running) { return; } CalculatePositionInRound(); auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.GameMode != MpGameMode::Cooperation && serverConfig.Elimination) { std::int32_t eliminationCount = 0; MpPlayer* eliminationWinner = nullptr; for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->Deaths < serverConfig.TotalKills) { eliminationCount++; } else { eliminationWinner = mpPlayer; } } if (eliminationCount >= _players.size() - 1) { EndGame(eliminationWinner); return; } } switch (serverConfig.GameMode) { case MpGameMode::Battle: case MpGameMode::TeamBattle: { if (!serverConfig.Elimination) { for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->Kills >= serverConfig.TotalKills) { EndGame(mpPlayer); break; } } } break; } case MpGameMode::Race: case MpGameMode::TeamRace: { for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->Laps >= serverConfig.TotalLaps) { EndGame(mpPlayer); break; } } break; } // Player has to stand on EndOfLevel event with required treasure amount /*case MpGameMode::TreasureHunt: case MpGameMode::TeamTreasureHunt: { for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->TreasureCollected >= serverConfig.TotalTreasureCollected) { EndGame(mpPlayer); break; } } break; }*/ } } void MpLevelHandler::EndGame(MpPlayer* winner) { _levelState = LevelState::Ending; _gameTimeLeft = EndingDuration; SetControllableToAllPlayers(false); SendLevelStateToAllPlayers(); // Fade out { float fadeOutDelay = EndingDuration - 2 * FrameTimer::FramesPerSecond; _hud->BeginFadeOut(fadeOutDelay); MemoryStream packet(4); packet.WriteVariableInt32((std::int32_t)fadeOutDelay); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelLoaded); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::FadeOut, packet); } if (winner != nullptr) { auto peerDesc = winner->GetPeerDescriptor(); ShowAlertToAllPlayers(_f("\n\nWinner is {}", peerDesc->PlayerName)); LOGI("[MP] Winner is {}", peerDesc->PlayerName); } for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (peerDesc->PositionInRound > 0 && peerDesc->PositionInRound - 1 < arraySize(PointsPerPosition)) { peerDesc->Points += PointsPerPosition[peerDesc->PositionInRound - 1]; if (peerDesc->RemotePeer) { auto& serverConfig = _networkManager->GetServerConfiguration(); MemoryStream packet(13); packet.WriteValue((std::uint8_t)PlayerPropertyType::Points); packet.WriteVariableUint32(mpPlayer->_playerIndex); packet.WriteVariableUint32(peerDesc->Points); packet.WriteVariableUint32(serverConfig.TotalPlayerPoints); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } } void MpLevelHandler::EndGameOnTimeOut() { MpPlayer* winner = nullptr; auto& serverConfig = _networkManager->GetServerConfiguration(); switch (serverConfig.GameMode) { case MpGameMode::Battle: case MpGameMode::TeamBattle: { std::uint32_t mostKills = 0; for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (mostKills < peerDesc->Kills) { mostKills = peerDesc->Kills; winner = mpPlayer; } } break; } case MpGameMode::Race: case MpGameMode::TeamRace: { std::uint32_t mostLaps = 0; for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (mostLaps < peerDesc->Laps) { mostLaps = peerDesc->Laps; winner = mpPlayer; } } break; } case MpGameMode::TreasureHunt: case MpGameMode::TeamTreasureHunt: { std::uint32_t mostTreasureCollected = 0; for (auto* player : _players) { auto* mpPlayer = static_cast(player); auto peerDesc = mpPlayer->GetPeerDescriptor(); if (mostTreasureCollected < peerDesc->TreasureCollected) { mostTreasureCollected = peerDesc->TreasureCollected; winner = mpPlayer; } } break; } } EndGame(winner); } bool MpLevelHandler::ApplyFromPlaylist() { if (!_isServer) { return false; } auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.PlaylistIndex >= serverConfig.Playlist.size()) { return false; } auto& playlistEntry = serverConfig.Playlist[serverConfig.PlaylistIndex]; // Override properties serverConfig.ReforgedGameplay = playlistEntry.ReforgedGameplay; serverConfig.Elimination = playlistEntry.Elimination; serverConfig.InitialPlayerHealth = playlistEntry.InitialPlayerHealth; serverConfig.MaxGameTimeSecs = playlistEntry.MaxGameTimeSecs; serverConfig.PreGameSecs = playlistEntry.PreGameSecs; serverConfig.TotalKills = playlistEntry.TotalKills; serverConfig.TotalLaps = playlistEntry.TotalLaps; serverConfig.TotalTreasureCollected = playlistEntry.TotalTreasureCollected; _autoWeightTreasure = (serverConfig.TotalTreasureCollected == 0); if (_levelName == playlistEntry.LevelName) { // Level is the same, just change game mode SetGameMode(playlistEntry.GameMode); } else { // Level is different, new level handler needs to be created LevelInitialization levelInit; PrepareNextLevelInitialization(levelInit); auto level = playlistEntry.LevelName.partition('/'); if (playlistEntry.LevelName.contains('/')) { levelInit.LevelName = playlistEntry.LevelName; } else { levelInit.LevelName = "unknown/"_s + playlistEntry.LevelName; } if (!ContentResolver::Get().LevelExists(levelInit.LevelName)) { return false; } serverConfig.GameMode = playlistEntry.GameMode; levelInit.IsReforged = serverConfig.ReforgedGameplay; levelInit.LastExitType = ExitType::Normal; HandleLevelChange(std::move(levelInit)); } return true; } void MpLevelHandler::RestartPlaylist() { auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.Playlist.size() > 1) { serverConfig.PlaylistIndex = 0; if (serverConfig.RandomizePlaylist) { Random().Shuffle(serverConfig.Playlist); } ApplyFromPlaylist(); } } void MpLevelHandler::SkipInPlaylist() { auto& serverConfig = _networkManager->GetServerConfiguration(); if (serverConfig.Playlist.size() > 1) { serverConfig.PlaylistIndex++; if (serverConfig.PlaylistIndex >= serverConfig.Playlist.size()) { serverConfig.PlaylistIndex = 0; if (serverConfig.RandomizePlaylist) { Random().Shuffle(serverConfig.Playlist); } } ApplyFromPlaylist(); } } void MpLevelHandler::ResetPeerPoints() { for (auto& [playerPeer, peerDesc] : *_networkManager->GetPeers()) { peerDesc->Points = 0; if (peerDesc->RemotePeer) { auto& serverConfig = _networkManager->GetServerConfiguration(); MemoryStream packet(13); packet.WriteValue((std::uint8_t)PlayerPropertyType::Points); packet.WriteVariableUint32(peerDesc->Player->_playerIndex); packet.WriteVariableUint32(peerDesc->Points); packet.WriteVariableUint32(serverConfig.TotalPlayerPoints); _networkManager->SendTo(peerDesc->RemotePeer, NetworkChannel::Main, (std::uint8_t)ServerPacketType::PlayerSetProperty, packet); } } } void MpLevelHandler::SetWelcomeMessage(StringView message) { if (!_isServer) { return; } auto& serverConfig = _networkManager->GetServerConfiguration(); serverConfig.WelcomeMessage = message; std::uint8_t flags = 0x04; // SetLobbyMessage MemoryStream packet(6 + serverConfig.WelcomeMessage.size()); packet.WriteValue(flags); packet.WriteValue(0x00); packet.WriteVariableUint32(serverConfig.WelcomeMessage.size()); packet.Write(serverConfig.WelcomeMessage.data(), serverConfig.WelcomeMessage.size()); _networkManager->SendTo([this](const Peer& peer) { auto peerDesc = _networkManager->GetPeerDescriptor(peer); return (peerDesc && peerDesc->LevelState >= PeerLevelState::LevelSynchronized); }, NetworkChannel::Main, (std::uint8_t)ServerPacketType::ShowInGameLobby, packet); } void MpLevelHandler::SetPlayerReady(PlayerType playerType) { if (_isServer) { return; } _inGameLobby->Hide(); MemoryStream packet(2); packet.WriteValue((std::uint8_t)playerType); // TODO: Preferred team packet.WriteValue(0); _networkManager->SendTo(AllPeers, NetworkChannel::Main, (std::uint8_t)ClientPacketType::PlayerReady, packet); } void MpLevelHandler::EndActivePoll() { std::int32_t total, votedYes = 0; { auto peers = _networkManager->GetPeers(); total = (std::int32_t)peers->size(); votedYes = 0; for (auto& [playerPeer, peerDesc] : *peers) { if (peerDesc->VotedYes) { votedYes++; } } } if (votedYes >= total * 2 / 3 && total >= 3) { SendMessageToAll("Poll accepted, the majority of players voted yes"); switch (_activePoll) { case VoteType::Restart: { RestartPlaylist(); break; } case VoteType::ResetPoints: { ResetPeerPoints(); break; } case VoteType::Skip: { SkipInPlaylist(); break; } case VoteType::Kick: { // TODO break; } } } else { SendMessageToAll("Not enough votes to pass the poll"); } _activePoll = VoteType::None; } bool MpLevelHandler::ActorShouldBeMirrored(Actors::ActorBase* actor) { // If actor has no animation, it's probably some special object (usually lights and ambient sounds) if (actor->_currentAnimation == nullptr && actor->_currentTransition == nullptr) { return true; } // List of objects that needs to be recreated on client-side instead of remoting return (runtime_cast(actor) || runtime_cast(actor) || runtime_cast(actor) || runtime_cast(actor) || runtime_cast(actor) || runtime_cast(actor) || runtime_cast(actor) || runtime_cast(actor)); } std::int32_t MpLevelHandler::GetTreasureWeight(std::uint8_t gemType) { switch (gemType) { default: case 0: // Red (+1) return 1; case 1: // Green (+5) return 5; case 2: // Blue (+10) return 10; case 3: // Purple return 1; } } bool MpLevelHandler::PlayerShouldHaveUnlimitedHealth(MpGameMode gameMode) { return (gameMode == MpGameMode::Race || gameMode == MpGameMode::TeamRace || gameMode == MpGameMode::TreasureHunt || gameMode == MpGameMode::TeamTreasureHunt); } void MpLevelHandler::InitializeValidateAssetsPacket(MemoryStream& packet) { packet.ReserveCapacity(4 + _requiredAssets.size() * 64); packet.WriteVariableUint32((std::uint32_t)_requiredAssets.size()); for (std::uint32_t i = 0; i < (std::uint32_t)_requiredAssets.size(); i++) { const auto& asset = _requiredAssets[i]; packet.WriteValue((std::uint8_t)asset.Type); packet.WriteVariableUint32((std::uint32_t)asset.Path.size()); packet.Write(asset.Path.data(), (std::int64_t)asset.Path.size()); } } void MpLevelHandler::InitializeLoadLevelPacket(MemoryStream& packet) { auto& serverConfig = _networkManager->GetServerConfiguration(); std::uint32_t flags = 0; if (_isReforged) { flags |= 0x01; } if (PreferencesCache::EnableLedgeClimb) { flags |= 0x02; } if (serverConfig.Elimination) { flags |= 0x04; } packet.ReserveCapacity(28 + _levelName.size()); packet.WriteVariableUint32(flags); packet.WriteValue((std::uint8_t)_levelState); packet.WriteValue((std::uint8_t)serverConfig.GameMode); packet.WriteValue((std::uint8_t)/*levelInit.LastExitType*/0); // TODO: LastExitType packet.WriteVariableUint32(_levelName.size()); packet.Write(_levelName.data(), _levelName.size()); packet.WriteVariableInt32(serverConfig.InitialPlayerHealth); packet.WriteVariableUint32(serverConfig.MaxGameTimeSecs); packet.WriteVariableUint32(serverConfig.TotalKills); packet.WriteVariableUint32(serverConfig.TotalLaps); packet.WriteVariableUint32(serverConfig.TotalTreasureCollected); } void MpLevelHandler::InitializeCreateRemoteActorPacket(MemoryStream& packet, std::uint32_t actorId, const Actors::ActorBase* actor) { String metadataPath = fs::FromNativeSeparators(actor->_metadata->Path); std::uint8_t flags = 0; if (actor->_renderer.isDrawEnabled()) { flags |= 0x04; } if (actor->_renderer.AnimPaused) { flags |= 0x08; } if (actor->_renderer.isFlippedX()) { flags |= 0x10; } if (actor->_renderer.isFlippedY()) { flags |= 0x20; } packet.ReserveCapacity(36 + metadataPath.size()); packet.WriteVariableUint32(actorId); packet.WriteValue(flags); packet.WriteVariableInt32((std::int32_t)actor->_pos.X); packet.WriteVariableInt32((std::int32_t)actor->_pos.Y); packet.WriteVariableInt32((std::int32_t)actor->_renderer.layer()); packet.WriteVariableUint32((std::uint32_t)actor->_state); packet.WriteVariableUint32((std::uint32_t)metadataPath.size()); packet.Write(metadataPath.data(), (std::uint32_t)metadataPath.size()); packet.WriteVariableUint32((std::uint32_t)(actor->_currentTransition != nullptr ? actor->_currentTransition->State : actor->_currentAnimation->State)); float rotation = actor->_renderer.rotation(); if (rotation < 0.0f) rotation += fRadAngle360; packet.WriteValue((std::uint16_t)(rotation * UINT16_MAX / fRadAngle360)); Vector2f scale = actor->_renderer.scale(); packet.WriteValue((std::uint16_t)Half{scale.X}); packet.WriteValue((std::uint16_t)Half{scale.Y}); packet.WriteValue((std::uint8_t)actor->_renderer.GetRendererType()); } String MpLevelHandler::GetAssetFullPath(AssetType type, StringView path, StaticArrayView remoteServerId, bool forWrite) { const auto& resolver = ContentResolver::Get(); auto pathNormalized = fs::ToNativeSeparators(path); char uuidStr[33]; std::size_t uuidStrLength; if (remoteServerId) { uuidStrLength = formatInto(uuidStr, "{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}", remoteServerId[0], remoteServerId[1], remoteServerId[2], remoteServerId[3], remoteServerId[4], remoteServerId[5], remoteServerId[6], remoteServerId[7], remoteServerId[8], remoteServerId[9], remoteServerId[10], remoteServerId[11], remoteServerId[12], remoteServerId[13], remoteServerId[14], remoteServerId[15]); } switch (type) { case AssetType::Level: { String fullPath; if (remoteServerId) { fullPath = fs::CombinePath({ resolver.GetCachePath(), "Downloads"_s, { uuidStr, uuidStrLength }, fs::ToNativeSeparators(String(path + ".j2l"_s)) }); } if (!forWrite && !fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ resolver.GetContentPath(), "Episodes"_s, String(pathNormalized + ".j2l"_s) }); if (!fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ resolver.GetCachePath(), "Episodes"_s, String(pathNormalized + ".j2l"_s) }); if (!fs::IsReadableFile(fullPath)) { fullPath = {}; } } } return fullPath; } case AssetType::TileSet: { String fullPath; if (remoteServerId) { fullPath = fs::CombinePath({ resolver.GetCachePath(), "Downloads"_s, { uuidStr, uuidStrLength }, fs::ToNativeSeparators(String(path + ".j2t"_s)) }); } if (!forWrite && !fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ resolver.GetContentPath(), "Tilesets"_s, String(path + ".j2t"_s) }); if (!fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ resolver.GetCachePath(), "Tilesets"_s, String(path + ".j2t"_s) }); if (!fs::IsReadableFile(fullPath)) { fullPath = {}; } } } return fullPath; } case AssetType::Music: { String fullPath; if (remoteServerId) { fullPath = fs::CombinePath({ resolver.GetCachePath(), "Downloads"_s, { uuidStr, uuidStrLength }, fs::ToNativeSeparators(path) }); } if (!forWrite && !fs::IsReadableFile(fullPath)) { fullPath = fs::CombinePath({ resolver.GetContentPath(), "Music"_s, path }); if (!fs::IsReadableFile(fullPath)) { // "Source" directory must be case in-sensitive fullPath = fs::FindPathCaseInsensitive(fs::CombinePath(resolver.GetSourcePath(), path)); if (!fs::IsReadableFile(fullPath)) { fullPath = {}; } } } return fullPath; } default: { return {}; } } } /*void MpLevelHandler::UpdatePlayerLocalPos(Actors::Player* player, PlayerState& playerState, float timeMult) { if (playerState.WarpTimeLeft > 0.0f || !player->_controllable || !player->GetState(Actors::ActorState::CollideWithTileset)) { // Don't interpolate if warping is in progress or if collisions with tileset are disabled (when climbing or in tube) return; } Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); std::int64_t renderTime = now - ServerDelay; std::int32_t nextIdx = playerState.StateBufferPos - 1; if (nextIdx < 0) { nextIdx += arraySize(playerState.StateBuffer); } if (renderTime <= playerState.StateBuffer[nextIdx].Time) { std::int32_t prevIdx; while (true) { prevIdx = nextIdx - 1; if (prevIdx < 0) { prevIdx += arraySize(playerState.StateBuffer); } if (prevIdx == playerState.StateBufferPos || playerState.StateBuffer[prevIdx].Time <= renderTime) { break; } nextIdx = prevIdx; } Vector2f pos; Vector2f speed; std::int64_t timeRange = (playerState.StateBuffer[nextIdx].Time - playerState.StateBuffer[prevIdx].Time); if (timeRange > 0) { float lerp = (float)(renderTime - playerState.StateBuffer[prevIdx].Time) / timeRange; pos = playerState.StateBuffer[prevIdx].Pos + (playerState.StateBuffer[nextIdx].Pos - playerState.StateBuffer[prevIdx].Pos) * lerp; speed = playerState.StateBuffer[prevIdx].Speed + (playerState.StateBuffer[nextIdx].Speed - playerState.StateBuffer[prevIdx].Speed) * lerp; } else { pos = playerState.StateBuffer[nextIdx].Pos; speed = playerState.StateBuffer[nextIdx].Speed; } constexpr float BaseDeviation = 2.0f; constexpr float DeviationTimeMax = 90.0f; constexpr float DeviationTimeCutoff = 40.0f; float devSqr = (player->_pos - pos).SqrLength(); float speedSqr = std::max(player->_speed.SqrLength(), speed.SqrLength()); if (devSqr > speedSqr + (BaseDeviation * BaseDeviation)) { playerState.DeviationTime += timeMult; if (playerState.DeviationTime > DeviationTimeMax) { LOGW("Deviation of player {} was high for too long (deviation: {:.2f}px, speed: {:.2f})", player->_playerIndex, sqrt(devSqr), sqrt(speedSqr)); playerState.DeviationTime = 0.0f; player->MoveInstantly(pos, Actors::MoveType::Absolute); return; } } else { playerState.DeviationTime = 0.0f; } float alpha = std::clamp(playerState.DeviationTime - DeviationTimeCutoff, 0.0f, DeviationTimeMax - DeviationTimeCutoff) / (DeviationTimeMax - DeviationTimeCutoff); if (alpha > 0.01f) { player->MoveInstantly(Vector2f(lerp(player->_pos.X, pos.X, alpha), lerp(player->_pos.Y, pos.Y, alpha)), Actors::MoveType::Absolute); player->_speed = Vector2f(lerp(player->_speed.X, speed.X, alpha), lerp(player->_speed.Y, speed.Y, alpha)); LOGW("Deviation of player {} is high (alpha: {:.1f}, deviation: {:.2f}px, speed: {:.2f})", player->_playerIndex, alpha, sqrt(devSqr), sqrt(speedSqr)); } } }*/ /*void MpLevelHandler::OnRemotePlayerPosReceived(PlayerState& playerState, Vector2f pos, const Vector2f speed, PlayerFlags flags) { Clock& c = nCine::clock(); std::int64_t now = c.now() * 1000 / c.frequency(); if ((flags & PlayerFlags::JustWarped) != PlayerFlags::JustWarped) { // Player is still visible, enable interpolation playerState.StateBuffer[playerState.StateBufferPos].Time = now; playerState.StateBuffer[playerState.StateBufferPos].Pos = pos; playerState.StateBuffer[playerState.StateBufferPos].Speed = speed; } else { // Player just warped, reset state buffer to disable interpolation std::int32_t stateBufferPrevPos = playerState.StateBufferPos - 1; if (stateBufferPrevPos < 0) { stateBufferPrevPos += arraySize(playerState.StateBuffer); } std::int64_t renderTime = now - ServerDelay; playerState.StateBuffer[stateBufferPrevPos].Time = renderTime; playerState.StateBuffer[stateBufferPrevPos].Pos = pos; playerState.StateBuffer[stateBufferPrevPos].Speed = speed; playerState.StateBuffer[playerState.StateBufferPos].Time = renderTime; playerState.StateBuffer[playerState.StateBufferPos].Pos = pos; playerState.StateBuffer[playerState.StateBufferPos].Speed = speed; } playerState.StateBufferPos++; if (playerState.StateBufferPos >= std::int32_t(arraySize(playerState.StateBuffer))) { playerState.StateBufferPos = 0; } playerState.Flags = (flags & ~PlayerFlags::JustWarped); }*/ #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) void MpLevelHandler::ShowDebugWindow() { const float appWidth = theApplication().GetWidth(); //const ImVec2 windowPos = ImVec2(appWidth * 0.5f, ImGui::GetIO().DisplaySize.y - Margin); //const ImVec2 windowPosPivot = ImVec2(0.5f, 1.0f); //ImGui::SetNextWindowPos(windowPos, ImGuiCond_FirstUseEver, windowPosPivot); ImGui::Begin("Multiplayer Debug", nullptr); ImGui::PlotLines("Update Packet Size", _updatePacketSize, PlotValueCount, _plotIndex, nullptr, 0.0f, _updatePacketMaxSize, ImVec2(appWidth * 0.2f, 40.0f)); ImGui::SameLine(600.0f); ImGui::Text("%.0f bytes", _updatePacketSize[_plotIndex]); ImGui::PlotLines("Update Packet Size Compressed", _compressedUpdatePacketSize, PlotValueCount, _plotIndex, nullptr, 0.0f, _updatePacketMaxSize, ImVec2(appWidth * 0.2f, 40.0f)); ImGui::SameLine(600.0f); ImGui::Text("%.0f bytes", _compressedUpdatePacketSize[_plotIndex]); ImGui::Separator(); ImGui::PlotLines("Actors", _actorsCount, PlotValueCount, _plotIndex, nullptr, 0.0f, _actorsMaxCount, ImVec2(appWidth * 0.2f, 40.0f)); ImGui::SameLine(600.0f); ImGui::Text("%.0f", _actorsCount[_plotIndex]); ImGui::PlotLines("Remote Actors", _remoteActorsCount, PlotValueCount, _plotIndex, nullptr, 0.0f, _actorsMaxCount, ImVec2(appWidth * 0.2f, 40.0f)); ImGui::SameLine(600.0f); ImGui::Text("%.0f", _remoteActorsCount[_plotIndex]); ImGui::PlotLines("Mirrored Actors", _mirroredActorsCount, PlotValueCount, _plotIndex, nullptr, 0.0f, _actorsMaxCount, ImVec2(appWidth * 0.2f, 40.0f)); ImGui::SameLine(600.0f); ImGui::Text("%.0f", _mirroredActorsCount[_plotIndex]); ImGui::PlotLines("Remoting Actors", _remotingActorsCount, PlotValueCount, _plotIndex, nullptr, 0.0f, _actorsMaxCount, ImVec2(appWidth * 0.2f, 40.0f)); ImGui::SameLine(600.0f); ImGui::Text("%.0f", _remotingActorsCount[_plotIndex]); ImGui::Text("Last spawned ID: %u", _lastSpawnedActorId); ImGui::SeparatorText("Peers"); ImGuiTableFlags flags = ImGuiTableFlags_RowBg | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_BordersInner | ImGuiTableFlags_NoPadOuterX; if (ImGui::BeginTable("peers", 8, flags, ImVec2(0.0f, 0.0f))) { ImGui::TableSetupColumn("Peer"); ImGui::TableSetupColumn("Player Index"); ImGui::TableSetupColumn("State"); ImGui::TableSetupColumn("Last Updated"); ImGui::TableSetupColumn("X"); ImGui::TableSetupColumn("Y"); ImGui::TableSetupColumn("Flags"); ImGui::TableSetupColumn("Pressed"); ImGui::TableHeadersRow(); for (auto& [peer, desc] : *_networkManager->GetPeers()) { ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::Text("0x%08x", peer._enet); ImGui::TableSetColumnIndex(1); ImGui::Text("%u", desc->Player != nullptr ? desc->Player->GetPlayerIndex() : -1); ImGui::TableSetColumnIndex(2); ImGui::Text("0x%x", desc->LevelState); ImGui::TableSetColumnIndex(3); ImGui::Text("%u", desc->LastUpdated); ImGui::TableSetColumnIndex(4); ImGui::Text("%.2f", desc->Player != nullptr ? desc->Player->GetPos().X : -1.0f); ImGui::TableSetColumnIndex(5); ImGui::Text("%.2f", desc->Player != nullptr ? desc->Player->GetPos().Y : -1.0f); if (auto* remotePlayerOnServer = runtime_cast(desc->Player)) { ImGui::TableSetColumnIndex(6); ImGui::Text("0x%02x", remotePlayerOnServer->Flags); ImGui::TableSetColumnIndex(7); ImGui::Text("0x%04x", remotePlayerOnServer->PressedKeys); } } ImGui::EndTable(); } ImGui::End(); } #endif } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/MpLevelHandler.h000066400000000000000000000370221512772601700270610ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../LevelHandler.h" #include "MpGameMode.h" #include "NetworkManager.h" #include "../Actors/Player.h" #include "../UI/InGameConsole.h" #include namespace Jazz2::Actors::Multiplayer { class MpPlayer; class PlayerOnServer; class RemotablePlayer; class RemoteActor; class RemotePlayerOnServer; } namespace Jazz2::UI::Multiplayer { class MpInGameCanvasLayer; class MpInGameLobby; class MpHUD; } namespace Jazz2::Multiplayer { /** @brief Level handler of an online multiplayer game session @experimental */ class MpLevelHandler : public LevelHandler, public IServerStatusProvider { DEATH_RUNTIME_OBJECT(LevelHandler, IServerStatusProvider); #if defined(WITH_ANGELSCRIPT) friend class Scripting::LevelScriptLoader; #endif friend class Actors::Multiplayer::PlayerOnServer; friend class Actors::Multiplayer::RemotablePlayer; friend class Actors::Multiplayer::RemotePlayerOnServer; friend class UI::Multiplayer::MpInGameCanvasLayer; friend class UI::Multiplayer::MpInGameLobby; friend class UI::Multiplayer::MpHUD; public: /** @brief Level state */ enum class LevelState { Unknown, /**< Unknown */ InitialUpdatePending, /**< Level was just created and initial update needs to be sent */ PreGame, /**< Pre-game is active */ WaitingForMinPlayers, /**< Pre-game ended, but not enough players connected yet */ Countdown3, /**< Countdown started (3) */ Countdown2, /**< Countdown continues (2) */ Countdown1, /**< Countdown continues (1) */ Running, /**< Round started */ Ending, /**< Round is ending */ Ended /**< Round ended */ }; /** @brief Asset type */ enum class AssetType { Unknown, Level, TileSet, Music, Script }; MpLevelHandler(IRootController* root, NetworkManager* networkManager, LevelState levelState, bool enableLedgeClimb); ~MpLevelHandler() override; bool Initialize(const LevelInitialization& levelInit) override; bool IsLocalSession() const override; bool IsServer() const override; bool IsPausable() const override; bool CanActivateSugarRush() const override; bool CanEventDisappear(EventType eventType) const override; float GetHurtInvulnerableTime() const override; float GetDefaultAmbientLight() const override; void SetAmbientLight(Actors::Player* player, float value) override; void OnBeginFrame() override; void OnEndFrame() override; void OnInitializeViewport(std::int32_t width, std::int32_t height) override; bool OnConsoleCommand(StringView line) override; void OnTouchEvent(const TouchEvent& event) override; void AddActor(std::shared_ptr actor) override; std::shared_ptr PlaySfx(Actors::ActorBase* self, StringView identifier, AudioBuffer* buffer, const Vector3f& pos, bool sourceRelative, float gain, float pitch) override; std::shared_ptr PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) override; void WarpCameraToTarget(Actors::ActorBase* actor, bool fast = false) override; void BroadcastTriggeredEvent(Actors::ActorBase* initiator, EventType eventType, std::uint8_t* eventParams) override; void BeginLevelChange(Actors::ActorBase* initiator, ExitType exitType, StringView nextLevel = {}) override; void SendPacket(const Actors::ActorBase* self, ArrayView data) override; void HandleBossActivated(Actors::Bosses::BossBase* boss, Actors::ActorBase* initiator) override; void HandleLevelChange(LevelInitialization&& levelInit) override; void HandleGameOver(Actors::Player* player) override; bool HandlePlayerDied(Actors::Player* player) override; void HandlePlayerWarped(Actors::Player* player, Vector2f prevPos, WarpFlags flags) override; void HandlePlayerCoins(Actors::Player* player, std::int32_t prevCount, std::int32_t newCount) override; void HandlePlayerGems(Actors::Player* player, std::uint8_t gemType, std::int32_t prevCount, std::int32_t newCount) override; void SetCheckpoint(Actors::Player* player, Vector2f pos) override; void RollbackToCheckpoint(Actors::Player* player) override; void HandleActivateSugarRush(Actors::Player* player) override; void HandleCreateParticleDebrisOnPerish(const Actors::ActorBase* self, Actors::ParticleDebrisEffect effect, Vector2f speed) override; void HandleCreateSpriteDebris(const Actors::ActorBase* self, AnimState state, std::int32_t count) override; void ShowLevelText(StringView text, Actors::ActorBase* initiator = nullptr) override; StringView GetLevelText(std::uint32_t textId, std::int32_t index = -1, std::uint32_t delimiter = 0) override; void OverrideLevelText(std::uint32_t textId, StringView value) override; void LimitCameraView(Actors::Player* player, Vector2f playerPos, std::int32_t left, std::int32_t width) override; void OverrideCameraView(Actors::Player* player, float x, float y, bool topLeft = false) override; void ShakeCameraView(Actors::Player* player, float duration) override; void ShakeCameraViewNear(Vector2f pos, float duration) override; void SetTrigger(std::uint8_t triggerId, bool newState) override; void SetWeather(WeatherType type, std::uint8_t intensity) override; bool BeginPlayMusic(StringView path, bool setDefault = false, bool forceReload = false) override; bool PlayerActionPressed(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) override; bool PlayerActionHit(Actors::Player* player, PlayerAction action, bool includeGamepads, bool& isGamepad) override; float PlayerHorizontalMovement(Actors::Player* player) override; float PlayerVerticalMovement(Actors::Player* player) override; void PlayerExecuteRumble(Actors::Player* player, StringView rumbleEffect) override; bool SerializeResumableToStream(Stream& dest) override; void OnAdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount) override; StringView GetLevelDisplayName() const override; /** @brief Returns current game mode */ MpGameMode GetGameMode() const; /** @brief Sets current game mode */ bool SetGameMode(MpGameMode value); /** @brief Synchronizes current game mode with all peers without restarting round */ bool SynchronizeGameMode(); /** @brief Returns owner of the specified object or the player itself */ static Actors::Multiplayer::MpPlayer* GetWeaponOwner(Actors::ActorBase* actor); // Server-only methods /** @brief Processes the specified server command */ bool ProcessCommand(const Peer& peer, StringView line, bool isAdmin); /** @brief Sends the message to the specified peer */ void SendMessage(const Peer& peer, UI::MessageLevel level, StringView message); /** @brief Sends the message to all authenticated peers */ void SendMessageToAll(StringView message, bool asChatFromServer = false); /** @brief Called when a peer disconnects from the server, see @ref INetworkHandler */ bool OnPeerDisconnected(const Peer& peer); /** @brief Called when a packet is received, see @ref INetworkHandler */ bool OnPacketReceived(const Peer& peer, std::uint8_t channelId, std::uint8_t packetType, ArrayView data); /** @brief Returns full path of the specified asset */ static String GetAssetFullPath(AssetType type, StringView path, StaticArrayView remoteServerId = {}, bool forWrite = false); protected: void AttachComponents(LevelDescriptor&& descriptor) override; std::unique_ptr CreateHUD() override; void SpawnPlayers(const LevelInitialization& levelInit) override; bool IsCheatingAllowed() override; void BeforeActorDestroyed(Actors::ActorBase* actor) override; void ProcessEvents(float timeMult) override; /** @brief Called when a player entered a transition to change the level */ void HandlePlayerLevelChanging(Actors::Player* player, ExitType exitType); /** @brief Called when a player interacts with a spring */ bool HandlePlayerSpring(Actors::Player* player, Vector2f pos, Vector2f force, bool keepSpeedX, bool keepSpeedY); /** @brief Called when a player is going to warp */ void HandlePlayerBeforeWarp(Actors::Player* player, Vector2f pos, WarpFlags flags); /** @brief Called when a player changed modifier */ void HandlePlayerSetModifier(Actors::Player* player, Actors::Player::Modifier modifier, const std::shared_ptr& decor); /** @brief Called when a player freezes */ void HandlePlayerFreeze(Actors::Player* player, float timeLeft); /** @brief Called when a player sets invulnerability */ void HandlePlayerSetInvulnerability(Actors::Player* player, float timeLeft, Actors::Player::InvulnerableType type); /** @brief Called when a player sets score */ void HandlePlayerSetScore(Actors::Player* player, std::int32_t value); /** @brief Called when a player sets health */ void HandlePlayerSetHealth(Actors::Player* player, std::int32_t count); /** @brief Called when a player sets lives */ void HandlePlayerSetLives(Actors::Player* player, std::int32_t count); /** @brief Called when a player takes a damage */ void HandlePlayerTakeDamage(Actors::Player* player, std::int32_t amount, float pushForce); /** @brief Called when a player requests to synchronize weapon ammo */ void HandlePlayerRefreshAmmo(Actors::Player* player, WeaponType weaponType); /** @brief Called when a player requests to synchronize weapon upgrades */ void HandlePlayerRefreshWeaponUpgrades(Actors::Player* player, WeaponType weaponType); /** @brief Called when a player requests to morph to another type */ void HandlePlayerMorphTo(Actors::Player* player, PlayerType type); /** @brief Called when a player changed duration of dizziness */ void HandlePlayerSetDizzy(Actors::Player* player, float timeLeft); /** @brief Called when a player sets a shield */ void HandlePlayerSetShield(Actors::Player* player, ShieldType shieldType, float timeLeft); /** @brief Called when a player emits a weapon flare */ void HandlePlayerEmitWeaponFlare(Actors::Player* player); /** @brief Called when a player changes their current weapon */ void HandlePlayerWeaponChanged(Actors::Player* player, Actors::Player::SetCurrentWeaponReason reason); private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct RemotingActorInfo { std::uint32_t ActorID; std::int32_t LastPosX; std::int32_t LastPosY; std::uint32_t LastAnimation; std::uint16_t LastRotation; std::uint16_t LastScaleX; std::uint16_t LastScaleY; std::uint8_t LastRendererType; }; struct PlayerPositionInRound { std::uint32_t ActorID; std::uint32_t PositionInRound; std::uint32_t PointsInRound; }; struct MultiplayerSpawnPoint { Vector2f Pos; std::uint8_t Team; MultiplayerSpawnPoint(Vector2f pos, std::uint8_t team) : Pos(pos), Team(team) {} }; struct PendingSfx { Actors::ActorBase* Actor; String Identifier; std::uint16_t Gain; std::uint16_t Pitch; PendingSfx(Actors::ActorBase* actor, String identifier, std::uint16_t gain, std::uint16_t pitch) : Actor(actor), Identifier(std::move(identifier)), Gain(gain), Pitch(pitch) {} }; struct RequiredAsset { AssetType Type; std::uint32_t Crc32; String Path; String FullPath; std::int64_t Size; RequiredAsset(AssetType type, StringView path, StringView fullPath, std::int64_t size, std::uint32_t crc32) : Type(type), Crc32(crc32), FullPath(fullPath), Path(path), Size(size) {} }; enum class VoteType : std::uint8_t { None, Restart, ResetPoints, Skip, Kick }; #endif //static constexpr float UpdatesPerSecond = 16.0f; // ~62 ms interval static constexpr float UpdatesPerSecond = 30.0f; // ~33 ms interval static constexpr std::int64_t ServerDelay = 64; static constexpr float EndingDuration = 10 * FrameTimer::FramesPerSecond; NetworkManager* _networkManager; float _updateTimeLeft; float _gameTimeLeft; LevelState _levelState; bool _isServer; bool _forceResyncPending; bool _enableSpawning; HashMap> _remoteActors; // Client: Actor ID -> Remote Actor created by server HashMap _remotingActors; // Server: Local Actor created by server -> Info HashMap _playerNames; // Client: Actor ID -> Player name SmallVector _positionsInRound; // Client: Actor ID -> Position In Round SmallVector _multiplayerSpawnPoints; SmallVector _raceCheckpoints; SmallVector _pendingSfx; std::uint32_t _lastSpawnedActorId; // Server: last assigned actor/player ID, Client: ID assigned by server std::int32_t _waitingForPlayerCount; // Client: number of players needed to start the game std::uint32_t _lastUpdated; // Server/Client: last update from the server std::uint64_t _seqNumWarped; // Client: set to _seqNum from HandlePlayerWarped() when warped Threading::Spinlock _lock; bool _suppressRemoting; // Server: if true, actor will not be automatically remoted to other players bool _ignorePackets; bool _enableLedgeClimb; bool _controllableExternal; bool _autoWeightTreasure; VoteType _activePoll; float _activePollTimeLeft; float _recalcPositionInRoundTime; std::int32_t _limitCameraLeft; std::int32_t _limitCameraWidth; Vector2f _lastCheckpointPos; float _lastCheckpointLight; std::int32_t _totalTreasureCount; SmallVector _requiredAssets; #if defined(DEATH_DEBUG) std::int32_t _debugAverageUpdatePacketSize; #endif std::unique_ptr _inGameCanvasLayer; std::unique_ptr _inGameLobby; void InitializeRequiredAssets(); void SynchronizePeers(); std::uint32_t FindFreeActorId(); std::uint8_t FindFreePlayerId(); bool IsLocalPlayer(Actors::ActorBase* actor); void ApplyGameModeToAllPlayers(MpGameMode gameMode); void ApplyGameModeToPlayer(MpGameMode gameMode, Actors::Player* player); void ShowAlertToAllPlayers(StringView text); void SetControllableToAllPlayers(bool enable); void SendLevelStateToAllPlayers(); void ResetAllPlayerStats(); Vector2f GetSpawnPoint(PlayerType playerType); void ConsolidateRaceCheckpoints(); void WarpAllPlayersToStart(); void RollbackLevelState(); void CalculatePositionInRound(bool forceSend = false); void CheckGameEnds(); void EndGame(Actors::Multiplayer::MpPlayer* winner); void EndGameOnTimeOut(); bool ApplyFromPlaylist(); void RestartPlaylist(); void SkipInPlaylist(); void ResetPeerPoints(); void SetWelcomeMessage(StringView message); void SetPlayerReady(PlayerType playerType); void EndActivePoll(); static bool ActorShouldBeMirrored(Actors::ActorBase* actor); static std::int32_t GetTreasureWeight(std::uint8_t gemType); static bool PlayerShouldHaveUnlimitedHealth(MpGameMode gameMode); void InitializeValidateAssetsPacket(MemoryStream& packet); void InitializeLoadLevelPacket(MemoryStream& packet); static void InitializeCreateRemoteActorPacket(MemoryStream& packet, std::uint32_t actorId, const Actors::ActorBase* actor); #if defined(DEATH_DEBUG) && defined(WITH_IMGUI) static constexpr std::int32_t PlotValueCount = 512; std::int32_t _plotIndex; float _actorsMaxCount; float _actorsCount[PlotValueCount]; float _remoteActorsCount[PlotValueCount]; float _remotingActorsCount[PlotValueCount]; float _mirroredActorsCount[PlotValueCount]; float _updatePacketMaxSize; float _updatePacketSize[PlotValueCount]; float _compressedUpdatePacketSize[PlotValueCount]; void ShowDebugWindow(); #endif }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/NetworkManager.cpp000066400000000000000000000573441512772601700275070ustar00rootroot00000000000000#include "NetworkManager.h" #if defined(WITH_MULTIPLAYER) #include "ServerDiscovery.h" #include "../ContentResolver.h" #include "../PreferencesCache.h" #include "../../nCine/I18n.h" #include "../../jsoncpp/json.h" #include #include #include #include #include using namespace Death; using namespace Death::Containers::Literals; using namespace std::string_view_literals; namespace Jazz2::Multiplayer { PeerDescriptor::PeerDescriptor() : IsAuthenticated(false), IsAdmin(false), EnableLedgeClimb(false), Team(0), PreferredPlayerType(PlayerType::None), Points(0), PointsInRound(0), PositionInRound(0), LevelState(PeerLevelState::Unknown), Player(nullptr), LastUpdated(0), Deaths(0), Kills(0), Laps(0), LapStarted{}, TreasureCollected(0), IdleElapsedFrames(0.0f), DeathElapsedFrames(FLT_MAX), LapsElapsedFrames(0.0f) { } NetworkManager::NetworkManager() { } NetworkManager::~NetworkManager() { auto& resolver = ContentResolver::Get(); resolver.OverridePathHandler(nullptr); } void NetworkManager::CreateClient(INetworkHandler* handler, StringView endpoint, std::uint16_t defaultPort, std::uint32_t clientData) { _peerDesc.emplace(Peer{}, std::make_shared()); _serverConfig = std::make_unique(); auto& resolver = ContentResolver::Get(); resolver.OverridePathHandler([this](StringView path) { return OnOverrideContentPath(path); }); NetworkManagerBase::CreateClient(handler, endpoint, defaultPort, clientData); } bool NetworkManager::CreateServer(INetworkHandler* handler, ServerConfiguration&& serverConfig) { _peerDesc.emplace(Peer{}, std::make_shared()); _serverConfig = std::make_unique(std::move(serverConfig)); std::uint16_t serverPort = _serverConfig->ServerPort; // Server port is part of Unique Server ID std::memcpy(&_serverConfig->UniqueServerID[0], &PreferencesCache::UniqueServerID[0], arraySize(_serverConfig->UniqueServerID) - sizeof(std::uint16_t)); std::memcpy(&_serverConfig->UniqueServerID[_serverConfig->UniqueServerID.size() - sizeof(std::uint16_t)], &serverPort, sizeof(std::uint16_t)); _serverConfig->StartUnixTimestamp = DateTime::UtcNow().ToUnixMilliseconds() / 1000; bool result = NetworkManagerBase::CreateServer(handler, serverPort); if (result && !_serverConfig->IsPrivate) { _discovery = std::make_unique(this); } return result; } void NetworkManager::Dispose() { _discovery = nullptr; NetworkManagerBase::Dispose(); } ServerConfiguration& NetworkManager::GetServerConfiguration() const { return *_serverConfig; } std::uint32_t NetworkManager::GetPeerCount() { std::uint32_t count = std::uint32_t(_peerDesc.size() - 1); std::unique_lock l(_lock); auto it = _peerDesc.find(Peer{}); if (it != _peerDesc.end() && it->second->Player) { count++; } return count; } LockedPtr>, Spinlock> NetworkManager::GetPeers() { return LockedPtr>, Spinlock>(&_peerDesc, std::unique_lock(_lock)); } std::shared_ptr NetworkManager::GetPeerDescriptor(LocalPeerT) { std::unique_lock l(_lock); auto it = _peerDesc.find(Peer{}); return (it != _peerDesc.end() ? it->second : nullptr); } std::shared_ptr NetworkManager::GetPeerDescriptor(const Peer& peer) { if (!peer) { return nullptr; } std::unique_lock l(_lock); auto it = _peerDesc.find(peer); return (it != _peerDesc.end() ? it->second : nullptr); } bool NetworkManager::HasInboundConnections() const { // Local peer is always present return (_peerDesc.size() > 1); } void NetworkManager::RefreshServerConfiguration() { if (_serverConfig->FilePath.empty()) { return; } HashMap includedFiles; FillServerConfigurationFromFile(_serverConfig->FilePath, *_serverConfig, includedFiles, 0); VerifyServerConfiguration(*_serverConfig); // Check if any newly banned player should be kicked std::unique_lock l(_lock); for (auto& pair : _peerDesc) { if (pair.second->RemotePeer) { auto address = NetworkManagerBase::AddressToString(pair.second->RemotePeer); if (_serverConfig->BannedIPAddresses.contains(address)) { LOGI("[MP] Peer kicked \"{}\" ({}): Banned by IP address", pair.second->PlayerName, address); Kick(pair.second->RemotePeer, Reason::Banned); continue; } auto uniquePlayerId = UuidToString(pair.second->UniquePlayerID); if (_serverConfig->BannedUniquePlayerIDs.contains(uniquePlayerId)) { LOGI("[MP] Peer kicked \"{}\" ({}): Banned by unique player ID", pair.second->PlayerName, address); Kick(pair.second->RemotePeer, Reason::Banned); continue; } } } } void NetworkManager::SetStatusProvider(std::weak_ptr statusProvider) { if (_discovery != nullptr) { _discovery->SetStatusProvider(std::move(statusProvider)); } } ServerConfiguration NetworkManager::CreateDefaultServerConfiguration() { return LoadServerConfigurationFromFile("Jazz2.Server.config"_s); } ServerConfiguration NetworkManager::LoadServerConfigurationFromFile(StringView path) { HashMap includedFiles; ServerConfiguration serverConfig{}; serverConfig.AllowAssetStreaming = true; serverConfig.GameMode = MpGameMode::Cooperation; serverConfig.AllowedPlayerTypes = 0x01 | 0x02 | 0x04; serverConfig.IdleKickTimeSecs = -1; serverConfig.MinPlayerCount = 1; serverConfig.ReforgedGameplay = PreferencesCache::EnableReforgedGameplay; serverConfig.PreGameSecs = 60; serverConfig.SpawnInvulnerableSecs = 4; serverConfig.PlaylistIndex = -1; serverConfig.TotalPlayerPoints = 50; serverConfig.InitialPlayerHealth = -1; serverConfig.TotalKills = 10; serverConfig.TotalLaps = 3; serverConfig.TotalTreasureCollected = 0; FillServerConfigurationFromFile(path, serverConfig, includedFiles, 0); serverConfig.FilePath = path; VerifyServerConfiguration(serverConfig); return serverConfig; } String NetworkManager::OnOverrideContentPath(StringView path) { auto& resolver = ContentResolver::Get(); const auto& remoteServerId = _serverConfig->UniqueServerID; char uuidStr[33]; std::size_t uuidStrLength = formatInto(uuidStr, "{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}{:.2x}", remoteServerId[0], remoteServerId[1], remoteServerId[2], remoteServerId[3], remoteServerId[4], remoteServerId[5], remoteServerId[6], remoteServerId[7], remoteServerId[8], remoteServerId[9], remoteServerId[10], remoteServerId[11], remoteServerId[12], remoteServerId[13], remoteServerId[14], remoteServerId[15]); String fullPath = fs::FindPathCaseInsensitive( fs::CombinePath({ resolver.GetCachePath(), "Downloads"_s, { uuidStr, uuidStrLength }, fs::ToNativeSeparators(path) })); if (!fs::IsReadableFile(fullPath)) { return {}; } LOGW("[MP] Overriding path \"{}\" to \"{}\"", path, fullPath); return fullPath; } void NetworkManager::FillServerConfigurationFromFile(StringView path, ServerConfiguration& serverConfig, HashMap& includedFiles, std::int32_t level) { static constexpr std::int32_t MaxLapCount = 80; auto configPath = fs::CombinePath(PreferencesCache::GetDirectory(), path); // Skip already included files to avoid infinite loops if (!includedFiles.emplace(configPath, true).second) { LOGW("Skipping already included configuration file \"{}\"", configPath); return; } auto s = fs::Open(configPath, FileAccess::Read); auto fileSize = s->GetSize(); if (fileSize >= 4 && fileSize < 64 * 1024 * 1024) { auto buffer = std::make_unique(fileSize); s->Read(buffer.get(), fileSize); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + fileSize, &doc, &errors)) { if (level == 0) { LOGI("Loaded configuration from \"{}\"", configPath); } else { LOGI("Loaded configuration from \"{}\" because of $include directive", configPath); } std::string_view includeFile; if (doc["$include"].get(includeFile) == Json::SUCCESS && !includeFile.empty()) { FillServerConfigurationFromFile(includeFile, serverConfig, includedFiles, level + 1); } std::string_view serverName; if (doc["ServerName"].get(serverName) == Json::SUCCESS) { serverConfig.ServerName = serverName; } std::string_view serverAddressOverride; if (doc["ServerAddressOverride"].get(serverAddressOverride) == Json::SUCCESS) { serverConfig.ServerAddressOverride = StringView(serverAddressOverride).trimmed(); if (!serverConfig.ServerAddressOverride.empty()) { StringView address; std::uint16_t port; if (!TrySplitAddressAndPort(serverConfig.ServerAddressOverride, address, port) || (!IsAddressValid(address) && !IsDomainValid(address))) { LOGW("Specified server address override \"{}\" is invalid, ignoring", serverConfig.ServerAddressOverride); serverConfig.ServerAddressOverride = {}; } else { LOGI("Using server address override \"{}\"", serverConfig.ServerAddressOverride); } } } std::string_view serverPassword; if (doc["ServerPassword"].get(serverPassword) == Json::SUCCESS) { serverConfig.ServerPassword = serverPassword; } std::string_view welcomeMessage; if (doc["WelcomeMessage"].get(welcomeMessage) == Json::SUCCESS) { serverConfig.WelcomeMessage = welcomeMessage; } std::int64_t maxPlayerCount; if (doc["MaxPlayerCount"].get(maxPlayerCount) == Json::SUCCESS && maxPlayerCount > 0 && maxPlayerCount <= UINT32_MAX) { serverConfig.MaxPlayerCount = std::uint32_t(maxPlayerCount); } std::int64_t minPlayerCount; if (doc["MinPlayerCount"].get(minPlayerCount) == Json::SUCCESS && minPlayerCount >= 1 && minPlayerCount <= UINT32_MAX) { serverConfig.MinPlayerCount = std::uint32_t(minPlayerCount); } std::string_view gameMode; if (doc["GameMode"].get(gameMode) == Json::SUCCESS) { serverConfig.GameMode = StringToGameMode(gameMode); } std::int64_t serverPort; if (doc["ServerPort"].get(serverPort) == Json::SUCCESS && serverPort > 0 && serverPort <= UINT16_MAX) { serverConfig.ServerPort = std::uint16_t(serverPort); } bool isPrivate; if (doc["IsPrivate"].get(isPrivate) == Json::SUCCESS) { serverConfig.IsPrivate = isPrivate; } bool allowAssetStreaming; if (doc["AllowAssetStreaming"].get(allowAssetStreaming) == Json::SUCCESS) { serverConfig.AllowAssetStreaming = allowAssetStreaming; } bool requiresDiscordAuth; if (doc["RequiresDiscordAuth"].get(requiresDiscordAuth) == Json::SUCCESS) { serverConfig.RequiresDiscordAuth = requiresDiscordAuth; } std::int64_t allowedPlayerTypes; if (doc["AllowedPlayerTypes"].get(allowedPlayerTypes) == Json::SUCCESS && allowedPlayerTypes > 0 && allowedPlayerTypes <= UINT8_MAX) { serverConfig.AllowedPlayerTypes = std::uint8_t(allowedPlayerTypes); } std::int64_t idleKickTimeSecs; if (doc["IdleKickTimeSecs"].get(idleKickTimeSecs) == Json::SUCCESS && idleKickTimeSecs >= INT32_MIN && idleKickTimeSecs <= INT32_MAX) { serverConfig.IdleKickTimeSecs = std::int16_t(idleKickTimeSecs); } Json::Value& adminUniquePlayerIDs = doc["AdminUniquePlayerIDs"]; for (auto it = adminUniquePlayerIDs.begin(); it != adminUniquePlayerIDs.end(); ++it) { std::string_view key = it.name(); if (!key.empty()) { std::string_view value; it->get(value); serverConfig.AdminUniquePlayerIDs.emplace(key, value); } } Json::Value& whitelistedUniquePlayerIDs = doc["WhitelistedUniquePlayerIDs"]; for (auto it = whitelistedUniquePlayerIDs.begin(); it != whitelistedUniquePlayerIDs.end(); ++it) { std::string_view key = it.name(); if (!key.empty()) { std::string_view value; it->get(value); serverConfig.WhitelistedUniquePlayerIDs.emplace(key, value); } } Json::Value& bannedUniquePlayerIDs = doc["BannedUniquePlayerIDs"]; for (auto it = bannedUniquePlayerIDs.begin(); it != bannedUniquePlayerIDs.end(); ++it) { std::string_view key = it.name(); if (!key.empty()) { std::string_view value; it->get(value); serverConfig.BannedUniquePlayerIDs.emplace(key, value); } } Json::Value& bannedIPAddresses = doc["BannedIPAddresses"]; for (auto it = bannedIPAddresses.begin(); it != bannedIPAddresses.end(); ++it) { std::string_view key = it.name(); if (!key.empty()) { std::string_view value; it->get(value); serverConfig.BannedIPAddresses.emplace(key, value); } } // Game-specific settings bool reforgedGameplay; if (doc["ReforgedGameplay"].get(reforgedGameplay) == Json::SUCCESS) { serverConfig.ReforgedGameplay = reforgedGameplay; } bool randomizePlaylist; if (doc["RandomizePlaylist"].get(randomizePlaylist) == Json::SUCCESS) { serverConfig.RandomizePlaylist = randomizePlaylist; } bool elimination; if (doc["Elimination"].get(elimination) == Json::SUCCESS) { serverConfig.Elimination = elimination; } std::int64_t totalPlayerPoints; if (doc["TotalPlayerPoints"].get(totalPlayerPoints) == Json::SUCCESS && totalPlayerPoints >= 0 && totalPlayerPoints <= INT32_MAX) { serverConfig.TotalPlayerPoints = std::uint32_t(totalPlayerPoints); } std::int64_t initialPlayerHealth; if (doc["InitialPlayerHealth"].get(initialPlayerHealth) == Json::SUCCESS && initialPlayerHealth >= INT32_MIN && initialPlayerHealth <= INT32_MAX) { serverConfig.InitialPlayerHealth = std::int32_t(initialPlayerHealth); } std::int64_t maxGameTimeSecs; if (doc["MaxGameTimeSecs"].get(maxGameTimeSecs) == Json::SUCCESS && maxGameTimeSecs >= 0 && maxGameTimeSecs <= INT32_MAX) { serverConfig.MaxGameTimeSecs = std::uint32_t(maxGameTimeSecs); } std::int64_t preGameSecs; if (doc["PreGameSecs"].get(preGameSecs) == Json::SUCCESS && preGameSecs >= 0 && preGameSecs <= INT32_MAX) { serverConfig.PreGameSecs = std::uint32_t(preGameSecs); } std::int64_t spawnInvulnerableSecs; if (doc["SpawnInvulnerableSecs"].get(spawnInvulnerableSecs) == Json::SUCCESS && spawnInvulnerableSecs >= 0 && spawnInvulnerableSecs <= INT32_MAX) { serverConfig.SpawnInvulnerableSecs = std::uint32_t(spawnInvulnerableSecs); } std::int64_t totalKills; if (doc["TotalKills"].get(totalKills) == Json::SUCCESS && totalKills >= 0 && totalKills <= INT32_MAX) { serverConfig.TotalKills = std::uint32_t(totalKills); } std::int64_t totalLaps; if (doc["TotalLaps"].get(totalLaps) == Json::SUCCESS && totalLaps >= 0 && totalLaps <= INT32_MAX) { serverConfig.TotalLaps = std::uint32_t(totalLaps); if (serverConfig.TotalLaps > MaxLapCount) { serverConfig.TotalLaps = MaxLapCount; } } std::int64_t totalTreasureCollected; if (doc["TotalTreasureCollected"].get(totalTreasureCollected) == Json::SUCCESS && totalTreasureCollected >= 0 && totalTreasureCollected <= INT32_MAX) { serverConfig.TotalTreasureCollected = std::uint32_t(totalTreasureCollected); } // Playlist Json::Value& playlist = doc["Playlist"]; if (playlist.isArray()) { serverConfig.Playlist.clear(); for (auto& entry : playlist) { // Playlist entry inherits all properties from the main server configuration PlaylistEntry playlistEntry{}; playlistEntry.GameMode = serverConfig.GameMode; playlistEntry.ReforgedGameplay = serverConfig.ReforgedGameplay; playlistEntry.Elimination = serverConfig.Elimination; playlistEntry.InitialPlayerHealth = serverConfig.InitialPlayerHealth; playlistEntry.MaxGameTimeSecs = serverConfig.MaxGameTimeSecs; playlistEntry.PreGameSecs = serverConfig.PreGameSecs; playlistEntry.TotalKills = serverConfig.TotalKills; playlistEntry.TotalLaps = serverConfig.TotalLaps; playlistEntry.TotalTreasureCollected = serverConfig.TotalTreasureCollected; std::string_view levelName; if (entry["LevelName"].get(levelName) == Json::SUCCESS) { playlistEntry.LevelName = levelName; } std::string_view gameMode; if (entry["GameMode"].get(gameMode) == Json::SUCCESS) { playlistEntry.GameMode = StringToGameMode(gameMode); } bool reforgedGameplay; if (entry["ReforgedGameplay"].get(reforgedGameplay) == Json::SUCCESS) { playlistEntry.ReforgedGameplay = reforgedGameplay; } bool elimination; if (entry["Elimination"].get(elimination) == Json::SUCCESS) { playlistEntry.Elimination = elimination; } std::int64_t initialPlayerHealth; if (entry["InitialPlayerHealth"].get(initialPlayerHealth) == Json::SUCCESS && initialPlayerHealth >= INT32_MIN && initialPlayerHealth <= INT32_MAX) { playlistEntry.InitialPlayerHealth = std::int32_t(initialPlayerHealth); } std::int64_t maxGameTimeSecs; if (entry["MaxGameTimeSecs"].get(maxGameTimeSecs) == Json::SUCCESS && maxGameTimeSecs >= 0 && maxGameTimeSecs <= INT32_MAX) { playlistEntry.MaxGameTimeSecs = std::uint32_t(maxGameTimeSecs); } std::int64_t preGameSecs; if (entry["PreGameSecs"].get(preGameSecs) == Json::SUCCESS && preGameSecs >= 0 && preGameSecs <= INT32_MAX) { playlistEntry.PreGameSecs = std::uint32_t(preGameSecs); } std::int64_t spawnInvulnerableSecs; if (entry["SpawnInvulnerableSecs"].get(spawnInvulnerableSecs) == Json::SUCCESS && spawnInvulnerableSecs >= 0 && spawnInvulnerableSecs <= INT32_MAX) { playlistEntry.SpawnInvulnerableSecs = std::uint32_t(spawnInvulnerableSecs); } std::int64_t totalKills; if (entry["TotalKills"].get(totalKills) == Json::SUCCESS && totalKills >= 0 && totalKills <= INT32_MAX) { playlistEntry.TotalKills = std::uint32_t(totalKills); } std::int64_t totalLaps; if (entry["TotalLaps"].get(totalLaps) == Json::SUCCESS && totalLaps >= 0 && totalLaps <= INT32_MAX) { playlistEntry.TotalLaps = std::uint32_t(totalLaps); if (playlistEntry.TotalLaps > MaxLapCount) { playlistEntry.TotalLaps = MaxLapCount; } } std::int64_t totalTreasureCollected; if (entry["TotalTreasureCollected"].get(totalTreasureCollected) == Json::SUCCESS && totalTreasureCollected >= 0 && totalTreasureCollected <= INT32_MAX) { playlistEntry.TotalTreasureCollected = std::uint32_t(totalTreasureCollected); } if (!playlistEntry.LevelName.empty() && serverConfig.GameMode != MpGameMode::Unknown) { serverConfig.Playlist.push_back(std::move(playlistEntry)); } } } std::int64_t playlistIndex; if (doc["PlaylistIndex"].get(playlistIndex) == Json::SUCCESS && playlistIndex >= -1 && playlistIndex < (std::int64_t)serverConfig.Playlist.size()) { serverConfig.PlaylistIndex = std::uint32_t(playlistIndex); } } else { LOGE("Configuration from \"{}\" cannot be parsed: {}", configPath, errors.c_str()); } } else { LOGE("Configuration file \"{}\" cannot be opened", configPath); } } void NetworkManager::VerifyServerConfiguration(ServerConfiguration& serverConfig) { // Set default values if (serverConfig.ServerName.empty()) { serverConfig.ServerName = "{PlayerName}'s Server"_s; } if (serverConfig.WelcomeMessage.empty()) { serverConfig.WelcomeMessage = "Welcome to the {ServerName}!"_s; } if (serverConfig.ServerPort == 0) { serverConfig.ServerPort = 7438; } if (serverConfig.MaxPlayerCount == 0) { serverConfig.MaxPlayerCount = MaxPeerCount; } // Replace variables in parameters auto playerName = PreferencesCache::GetEffectivePlayerName(); if (playerName.empty()) { playerName = "Unknown"_s; } serverConfig.ServerName = StringUtils::replaceAll(serverConfig.ServerName, "{PlayerName}"_s, playerName); //serverConfig.ServerName = StringUtils::replaceAll(serverConfig.ServerName, "\\f"_s, "\f"_s); serverConfig.WelcomeMessage = StringUtils::replaceAll(serverConfig.WelcomeMessage, "{PlayerName}"_s, playerName); serverConfig.WelcomeMessage = StringUtils::replaceAll(serverConfig.WelcomeMessage, "{ServerName}"_s, serverConfig.ServerName); //serverConfig.WelcomeMessage = StringUtils::replaceAll(serverConfig.WelcomeMessage, "\\f"_s, "\f"_s); #if defined(DEATH_DEBUG) String uniquePlayerId = NetworkManager::UuidToString(PreferencesCache::UniquePlayerID); serverConfig.AdminUniquePlayerIDs.emplace(uniquePlayerId, "*"_s); #endif } StringView NetworkManager::GameModeToString(MpGameMode mode) { switch (mode) { case MpGameMode::Battle: return "Battle"_s; case MpGameMode::TeamBattle: return "Team Battle"_s; case MpGameMode::Race: return "Race"_s; case MpGameMode::TeamRace: return "Team Race"_s; case MpGameMode::TreasureHunt: return "Treasure Hunt"_s; case MpGameMode::TeamTreasureHunt: return "Team Treasure Hunt"_s; case MpGameMode::CaptureTheFlag: return "Capture The Flag"_s; case MpGameMode::Cooperation: return "Cooperation"_s; default: return "Unknown"_s; } } MpGameMode NetworkManager::StringToGameMode(StringView value) { auto gameModeString = StringUtils::lowercase(value); if (gameModeString == "battle"sv || gameModeString == "b"sv) { return MpGameMode::Battle; } else if (gameModeString == "teambattle"_s || gameModeString == "tb"_s) { return MpGameMode::TeamBattle; } else if (gameModeString == "race"_s || gameModeString == "r"_s) { return MpGameMode::Race; } else if (gameModeString == "teamrace"_s || gameModeString == "tr"_s) { return MpGameMode::TeamRace; } else if (gameModeString == "treasurehunt"_s || gameModeString == "th"_s) { return MpGameMode::TreasureHunt; } else if (gameModeString == "teamtreasurehunt"_s || gameModeString == "tth"_s) { return MpGameMode::TeamTreasureHunt; } else if (gameModeString == "capturetheflag"_s || gameModeString == "ctf"_s) { return MpGameMode::CaptureTheFlag; } else if (gameModeString == "cooperation"_s || gameModeString == "coop"_s || gameModeString == "c"_s) { return MpGameMode::Cooperation; } else { return MpGameMode::Unknown; } } String NetworkManager::UuidToString(StaticArrayView uuid) { String uuidStr{NoInit, 39}; DEATH_UNUSED std::size_t uuidStrLength = formatInto(MutableStringView(uuidStr), "{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}", uuid[0], uuid[1], uuid[2], uuid[3], uuid[4], uuid[5], uuid[6], uuid[7], uuid[8], uuid[9], uuid[10], uuid[11], uuid[12], uuid[13], uuid[14], uuid[15]); DEATH_DEBUG_ASSERT(uuidStr.size() == uuidStrLength); return uuidStr; } ConnectionResult NetworkManager::OnPeerConnected(const Peer& peer, std::uint32_t clientData) { bool isListening = (GetState() == NetworkState::Listening); if (isListening) { auto address = NetworkManagerBase::AddressToString(peer); if (_serverConfig->BannedIPAddresses.contains(address)) { LOGI("[MP] Peer kicked \"\" ({}): Banned by IP address", address); return Reason::Banned; } } ConnectionResult result = NetworkManagerBase::OnPeerConnected(peer, clientData); if (result && isListening) { std::unique_lock l(_lock); auto [peerDesc, inserted] = _peerDesc.emplace(peer, std::make_shared()); peerDesc->second->RemotePeer = peer; } return result; } void NetworkManager::OnPeerDisconnected(const Peer& peer, Reason reason) { NetworkManagerBase::OnPeerDisconnected(peer, reason); if (GetState() == NetworkState::Listening) { std::unique_lock l(_lock); auto it = _peerDesc.find(peer); if (it != _peerDesc.end()) { it->second->RemotePeer = {}; _peerDesc.erase(it); } } } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/NetworkManager.h000066400000000000000000000072341512772601700271450ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "NetworkManagerBase.h" #include "MpGameMode.h" #include "ServerInitialization.h" #include "PeerDescriptor.h" #include "../../nCine/Threading/LockedPtr.h" namespace Jazz2::Actors::Multiplayer { class MpPlayer; } namespace Jazz2::Multiplayer { /** @brief Manages game-specific network connections @experimental */ class NetworkManager : public NetworkManagerBase { public: NetworkManager(); ~NetworkManager(); NetworkManager(const NetworkManager&) = delete; NetworkManager& operator=(const NetworkManager&) = delete; void CreateClient(INetworkHandler* handler, StringView endpoint, std::uint16_t defaultPort, std::uint32_t clientData) override; /** @brief Creates a server that accepts incoming connections */ virtual bool CreateServer(INetworkHandler* handler, ServerConfiguration&& serverConfig); void Dispose() override; /** @brief Returns server configuration */ ServerConfiguration& GetServerConfiguration() const; /** @brief Returns connected peer count */ std::uint32_t GetPeerCount(); /** @brief Returns all connected peers */ LockedPtr>, Spinlock> GetPeers(); /** @brief Returns session peer descriptor for the local peer */ std::shared_ptr GetPeerDescriptor(LocalPeerT); /** @brief Returns session peer descriptor for the specified connected remote peer */ std::shared_ptr GetPeerDescriptor(const Peer& peer); /** @brief Returns `true` if there are any inbound connections */ bool HasInboundConnections() const; /** @brief Reloads server configuration from the source file */ void RefreshServerConfiguration(); /** @brief Sets status provider */ void SetStatusProvider(std::weak_ptr statusProvider); /** * @brief Creates a default server configuration from the default template file * * This method reads a JSON configuration file described in @ref ServerConfiguration. If the JSON * contains a @cpp "$include" @ce directive, it recursively loads the referenced files. */ static ServerConfiguration CreateDefaultServerConfiguration(); /** * @brief Loads a server configuration from the specified file * * This method reads a JSON configuration file described in @ref ServerConfiguration. If the JSON * contains a @cpp "$include" @ce directive, it recursively loads the referenced files. */ static ServerConfiguration LoadServerConfigurationFromFile(StringView path); /** @brief Converts @ref MpGameMode to the string representation */ static StringView GameModeToString(MpGameMode mode); /** @brief Converts the non-localized string representation back to @ref MpGameMode */ static MpGameMode StringToGameMode(StringView value); /** @brief Converts UUID to the string representation */ static String UuidToString(StaticArrayView uuid); protected: #ifndef DOXYGEN_GENERATING_OUTPUT using NetworkManagerBase::CreateServer; #endif ConnectionResult OnPeerConnected(const Peer& peer, std::uint32_t clientData) override; void OnPeerDisconnected(const Peer& peer, Reason reason) override; private: std::unique_ptr _serverConfig; std::unique_ptr _discovery; HashMap> _peerDesc; Spinlock _lock; String OnOverrideContentPath(StringView path); static void FillServerConfigurationFromFile(StringView path, ServerConfiguration& serverConfig, HashMap& includedFiles, std::int32_t level); static void VerifyServerConfiguration(ServerConfiguration& serverConfig); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/NetworkManagerBase.cpp000066400000000000000000000574431512772601700303020ustar00rootroot00000000000000#define ENET_IMPLEMENTATION #include "NetworkManagerBase.h" #if defined(WITH_MULTIPLAYER) #include "INetworkHandler.h" #include "../../nCine/Base/Algorithms.h" #include "../../nCine/Threading/Thread.h" #include #include #include #include #if defined(DEATH_TARGET_ANDROID) # include "Backends/ifaddrs-android.h" #elif defined(DEATH_TARGET_SWITCH) # include #elif defined(DEATH_TARGET_WINDOWS) # include #else # include #endif using namespace Death; using namespace Death::Containers::Literals; namespace Jazz2::Multiplayer { static std::atomic_int32_t _initializeCount{0}; NetworkManagerBase::NetworkManagerBase() : _host(nullptr), _state(NetworkState::None), _handler(nullptr) { InitializeBackend(); } NetworkManagerBase::~NetworkManagerBase() { Dispose(); ReleaseBackend(); } void NetworkManagerBase::CreateClient(INetworkHandler* handler, StringView endpoints, std::uint16_t defaultPort, std::uint32_t clientData) { if (_handler != nullptr) { LOGE("[MP] Client already created"); return; } _state = NetworkState::Connecting; _clientData = clientData; _desiredEndpoints.clear(); #if defined(DEATH_TARGET_ANDROID) std::int32_t ifidx = 0; struct ifaddrs* ifaddr; struct ifaddrs* ifa; if (getifaddrs(&ifaddr) == 0) { for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr != nullptr && (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6) && (ifa->ifa_flags & IFF_UP)) { ifidx = if_nametoindex(ifa->ifa_name); if (ifidx > 0) { LOGI("[MP] Using {} interface \"{}\" ({})", ifa->ifa_addr->sa_family == AF_INET6 ? "IPv6" : "IPv4", ifa->ifa_name, ifidx); break; } } } freeifaddrs(ifaddr); } if (ifidx == 0) { LOGI("[MP] No suitable interface found"); ifidx = if_nametoindex("wlan0"); } #else std::int32_t ifidx = 0; #endif while (endpoints) { auto p = endpoints.partition('|'); if (p[0]) { StringView address; std::uint16_t port; if (TrySplitAddressAndPort(p[0], address, port)) { ENetAddress addr = {}; String nullTerminatedAddress = String::nullTerminatedView(address); std::int32_t r = enet_address_set_host(&addr, nullTerminatedAddress.data()); //std::int32_t r = enet_address_set_host_ip(&addr, nullTerminatedAddress.data()); if (r == 0) { #if ENET_IPV6 if (addr.sin6_scope_id == 0) { addr.sin6_scope_id = (std::uint16_t)ifidx; } #endif addr.port = (port != 0 ? port : defaultPort); _desiredEndpoints.push_back(std::move(addr)); } else { #if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); #else std::int32_t error = errno; #endif LOGW("[MP] Failed to parse specified address \"{}\" with error {}", nullTerminatedAddress, error); } } else { LOGW("[MP] Failed to parse specified endpoint \"{}\"", p[0]); } } endpoints = p[2]; } _handler = handler; _thread = Thread(NetworkManagerBase::OnClientThread, this); } bool NetworkManagerBase::CreateServer(INetworkHandler* handler, std::uint16_t port) { if (_handler != nullptr) { return false; } ENetAddress addr = {}; addr.host = ENET_HOST_ANY; addr.port = port; _host = enet_host_create(&addr, MaxPeerCount, (std::size_t)NetworkChannel::Count, 0, 0); DEATH_ASSERT(_host != nullptr, "Failed to create a server", false); _handler = handler; _state = NetworkState::Listening; _thread = Thread(NetworkManagerBase::OnServerThread, this); return true; } void NetworkManagerBase::Dispose() { if (_host == nullptr) { return; } _state = NetworkState::None; _thread.Join(); _host = nullptr; _handler = nullptr; } NetworkState NetworkManagerBase::GetState() const { return _state; } std::uint32_t NetworkManagerBase::GetRoundTripTimeMs() const { return (_state == NetworkState::Connected && !_peers.empty() ? _peers[0]->roundTripTime : 0); } Array NetworkManagerBase::GetServerEndpoints() const { Array result; if (_state == NetworkState::Listening) { #if defined(DEATH_TARGET_SWITCH) struct ifconf ifc; struct ifreq ifr[8]; ifc.ifc_len = sizeof(ifr); ifc.ifc_req = ifr; if (ioctl(_host->socket, SIOCGIFCONF, &ifc) >= 0) { std::int32_t count = ifc.ifc_len / sizeof(struct ifreq); LOGI("[MP] Found {} interfaces:", count); for (std::int32_t i = 0; i < count; i++) { if (ifr[i].ifr_addr.sa_family == AF_INET) { // IPv4 auto* addrPtr = &((struct sockaddr_in*)&ifr[i].ifr_addr)->sin_addr; String addressString = AddressToString(*addrPtr, _host->address.port); LOGI("[MP] -\t{}: {}", ifr[i].ifr_name, addressString); if (!addressString.empty() && !addressString.hasPrefix("127.0.0.1:"_s)) { arrayAppend(result, std::move(addressString)); } } # if ENET_IPV6 else if (ifr[i].ifr_addr.sa_family == AF_INET6) { // IPv6 auto* addrPtr = &((struct sockaddr_in6*)&ifr[i].ifr_addr)->sin6_addr; //auto scopeId = ((struct sockaddr_in6*)&ifr[i].ifr_addr)->sin6_scope_id; String addressString = AddressToString(*addrPtr, /*scopeId*/0, _host->address.port); LOGI("[MP] -\t{}: {}", ifr[i].ifr_name, addressString); if (!addressString.empty() && !addressString.hasPrefix("[::1]:"_s)) { arrayAppend(result, std::move(addressString)); } } # endif } } else { LOGW("[MP] Failed to get server endpoints"); } #elif defined(DEATH_TARGET_WINDOWS) ULONG bufferSize = 0; ::GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, NULL, &bufferSize); std::unique_ptr buffer = std::make_unique(bufferSize); auto* adapterAddresses = reinterpret_cast(buffer.get()); if (::GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, NULL, adapterAddresses, &bufferSize) == NO_ERROR) { for (auto* adapter = adapterAddresses; adapter != NULL; adapter = adapter->Next) { for (auto* address = adapter->FirstUnicastAddress; address != NULL; address = address->Next) { String addressString; if (address->Address.lpSockaddr->sa_family == AF_INET) { // IPv4 auto* addrPtr = &((struct sockaddr_in*)address->Address.lpSockaddr)->sin_addr; String addressString = AddressToString(*addrPtr, _host->address.port); if (!addressString.empty() && !addressString.hasPrefix("127.0.0.1:"_s)) { arrayAppend(result, std::move(addressString)); } } # if ENET_IPV6 else if (address->Address.lpSockaddr->sa_family == AF_INET6) { // IPv6 auto* addrPtr = &((struct sockaddr_in6*)address->Address.lpSockaddr)->sin6_addr; //auto scopeId = ((struct sockaddr_in6*)address->Address.lpSockaddr)->sin6_scope_id; String addressString = AddressToString(*addrPtr, /*scopeId*/0, _host->address.port); if (!addressString.empty() && !addressString.hasPrefix("[::1]:"_s)) { arrayAppend(result, std::move(addressString)); } } # endif else { // Unsupported address family } } } } else { LOGW("[MP] Failed to get server endpoints"); } #else struct ifaddrs* ifAddrStruct = nullptr; if (getifaddrs(&ifAddrStruct) == 0) { for (struct ifaddrs* ifa = ifAddrStruct; ifa != nullptr; ifa = ifa->ifa_next) { if (ifa->ifa_addr == nullptr) { continue; } if (ifa->ifa_addr->sa_family == AF_INET) { // IPv4 auto* addrPtr = &((struct sockaddr_in*)ifa->ifa_addr)->sin_addr; String addressString = AddressToString(*addrPtr, _host->address.port); if (!addressString.empty() && !addressString.hasPrefix("127.0.0.1:"_s)) { arrayAppend(result, std::move(addressString)); } } # if ENET_IPV6 else if (ifa->ifa_addr->sa_family == AF_INET6) { // IPv6 auto* addrPtr = &((struct sockaddr_in6*)ifa->ifa_addr)->sin6_addr; //auto scopeId = ((struct sockaddr_in6*)ifa->ifa_addr)->sin6_scope_id; String addressString = AddressToString(*addrPtr, /*scopeId*/0, _host->address.port); if (!addressString.empty() && !addressString.hasPrefix("[::1]:"_s)) { arrayAppend(result, std::move(addressString)); } } # endif } freeifaddrs(ifAddrStruct); } else { LOGW("[MP] Failed to get server endpoints"); } #endif } else { if (!_peers.empty()) { String addressString = AddressToString(_peers[0]->address, true); if (!addressString.empty() && !addressString.hasPrefix("[::1]:"_s)) { arrayAppend(result, std::move(addressString)); } } } return result; } std::uint16_t NetworkManagerBase::GetServerPort() const { if (_state == NetworkState::Listening) { return _host->address.port; } else if (!_peers.empty()) { return _peers[0]->address.port; } else { return 0; } } void NetworkManagerBase::SendTo(const Peer& peer, NetworkChannel channel, std::uint8_t packetType, ArrayView data) { ENetPeer* target; if (peer == nullptr) { if (_state != NetworkState::Connected || _peers.empty()) { return; } target = _peers[0]; } else { target = peer._enet; } enet_uint32 flags; if (channel == NetworkChannel::Main) { flags = ENET_PACKET_FLAG_RELIABLE; } else { flags = ENET_PACKET_FLAG_UNSEQUENCED; } ENetPacket* packet = enet_packet_create(packetType, data.data(), data.size(), flags); bool success; { std::unique_lock lock(_lock); success = enet_peer_send(target, std::uint8_t(channel), packet) >= 0; } if (!success) { enet_packet_destroy(packet); } } void NetworkManagerBase::SendTo(Function&& predicate, NetworkChannel channel, std::uint8_t packetType, ArrayView data) { if (_peers.empty()) { return; } enet_uint32 flags; if (channel == NetworkChannel::Main) { flags = ENET_PACKET_FLAG_RELIABLE; } else { flags = ENET_PACKET_FLAG_UNSEQUENCED; } ENetPacket* packet = enet_packet_create(packetType, data.data(), data.size(), flags); bool success = false; { std::unique_lock lock(_lock); for (ENetPeer* peer : _peers) { if (predicate(Peer(peer))) { if (enet_peer_send(peer, std::uint8_t(channel), packet) >= 0) { success = true; } } } } if (!success) { enet_packet_destroy(packet); } } void NetworkManagerBase::SendTo(AllPeersT, NetworkChannel channel, std::uint8_t packetType, ArrayView data) { if (_peers.empty()) { return; } enet_uint32 flags; if (channel == NetworkChannel::Main) { flags = ENET_PACKET_FLAG_RELIABLE; } else { flags = ENET_PACKET_FLAG_UNSEQUENCED; } ENetPacket* packet = enet_packet_create(packetType, data.data(), data.size(), flags); bool success = false; { std::unique_lock lock(_lock); for (ENetPeer* peer : _peers) { if (enet_peer_send(peer, std::uint8_t(channel), packet) >= 0) { success = true; } } } if (!success) { enet_packet_destroy(packet); } } void NetworkManagerBase::Kick(const Peer& peer, Reason reason) { if (peer != nullptr) { std::unique_lock lock(_lock); enet_peer_disconnect(peer._enet, std::uint32_t(reason)); } } String NetworkManagerBase::AddressToString(const struct in_addr& address, std::uint16_t port) { char addressString[64]; if (inet_ntop(AF_INET, &address, addressString, sizeof(addressString) - 1) == NULL) { return {}; } std::size_t addressLength = strnlen(addressString, sizeof(addressString)); if (port != 0) { addressLength = addressLength + formatInto({ &addressString[addressLength], sizeof(addressString) - addressLength }, ":{}", port); } return String(addressString, addressLength); } #if ENET_IPV6 String NetworkManagerBase::AddressToString(const struct in6_addr& address, std::uint16_t scopeId, std::uint16_t port) { char addressString[92]; std::size_t addressLength = 0; if (IN6_IS_ADDR_V4MAPPED(&address)) { struct in_addr buf; enet_inaddr_map6to4(&address, &buf); if (inet_ntop(AF_INET, &buf, addressString, sizeof(addressString) - 1) == NULL) { return {}; } addressLength = strnlen(addressString, sizeof(addressString)); } else { if (inet_ntop(AF_INET6, (void*)&address, &addressString[1], sizeof(addressString) - 3) == NULL) { return {}; } addressString[0] = '['; addressLength = strnlen(addressString, sizeof(addressString)); if (scopeId != 0) { addressLength += formatInto({ &addressString[addressLength], sizeof(addressString) - addressLength }, "%{}", scopeId); } addressString[addressLength] = ']'; addressLength++; } if (port != 0) { addressLength += formatInto({ &addressString[addressLength], sizeof(addressString) - addressLength }, ":{}", port); } return String(addressString, addressLength); } #endif String NetworkManagerBase::AddressToString(const ENetAddress& address, bool includePort) { #if ENET_IPV6 return AddressToString(address.host, address.sin6_scope_id, includePort ? address.port : 0); #else return AddressToString(*(const struct in_addr*)&address.host, includePort ? address.port : 0); #endif } String NetworkManagerBase::AddressToString(const Peer& peer) { if (peer._enet != nullptr) { return AddressToString(peer._enet->address); } return {}; } bool NetworkManagerBase::IsAddressValid(StringView address) { auto nullTerminatedAddress = String::nullTerminatedView(address); #if ENET_IPV6 struct sockaddr_in sa; struct sockaddr_in6 sa6; return (inet_pton(AF_INET6, nullTerminatedAddress.data(), &(sa6.sin6_addr)) == 1) || (inet_pton(AF_INET, nullTerminatedAddress.data(), &(sa.sin_addr)) == 1); #else struct sockaddr_in sa; return (inet_pton(AF_INET, nullTerminatedAddress.data(), &(sa.sin_addr)) == 1); #endif } bool NetworkManagerBase::IsDomainValid(StringView domain) { if (domain.empty() || domain.size() > 253) { return false; } while (!domain.empty()) { StringView end = domain.findOr('.', domain.end()); StringView part = domain.prefix(end.begin()); if (part.size() < 1 || part.size() > 63) { return false; } for (char c : part) { if (!isalnum(c) && c != '-') { return false; } } // Part can't start or end with hyphen if (part[0] == '-' || part[part.size() - 1] == '-') { return false; } if (end.begin() == domain.end()) { break; } domain = domain.suffix(end.begin() + 1); } return true; } bool NetworkManagerBase::TrySplitAddressAndPort(StringView input, StringView& address, std::uint16_t& port) { if (auto portSep = input.findLast(':')) { auto portString = input.suffix(portSep.begin() + 1); if (portString.contains(']')) { // Probably only IPv6 address (or some garbage) address = input; port = 0; return true; } else { // Address (or hostname) and port address = input.prefix(portSep.begin()).trimmed(); if (address.hasPrefix('[') && address.hasSuffix(']')) { address = address.slice(1, address.size() - 1); } if (address.empty()) { return false; } auto portString = input.suffix(portSep.begin() + 1); port = std::uint16_t(stou32(portString.data(), portString.size())); return true; } } else { // Address (or hostname) only address = input.trimmed(); if (address.hasPrefix('[') && address.hasSuffix(']')) { address = address.slice(1, address.size() - 1); } if (address.empty()) { return false; } port = 0; return true; } } const char* NetworkManagerBase::ReasonToString(Reason reason) { switch (reason) { case Reason::Disconnected: return "Client disconnected by user"; break; case Reason::InvalidParameter: return "Invalid parameter specified"; break; case Reason::IncompatibleVersion: return "Incompatible client version"; break; case Reason::AuthFailed: return "Authentication failed"; break; case Reason::InvalidPassword: return "Invalid password specified"; break; case Reason::InvalidPlayerName: return "Invalid player name specified"; break; case Reason::NotInWhitelist: return "Client is not in server whitelist"; break; case Reason::Requires3rdPartyAuthProvider: return "Server requires 3rd party authentication provider"; break; case Reason::ServerIsFull: return "Server is full or busy"; break; case Reason::ServerNotReady: return "Server is not ready yet"; break; case Reason::ServerStopped: return "Server is stopped for unknown reason"; break; case Reason::ServerStoppedForMaintenance: return "Server is stopped for maintenance"; break; case Reason::ServerStoppedForReconfiguration: return "Server is stopped for reconfiguration"; break; case Reason::ServerStoppedForUpdate: return "Server is stopped for update"; break; case Reason::ConnectionLost: return "Connection lost"; break; case Reason::ConnectionTimedOut: return "Connection timed out"; break; case Reason::Kicked: return "Kicked by server"; break; case Reason::Banned: return "Banned by server"; break; case Reason::CheatingDetected: return "Cheating detected"; break; case Reason::AssetStreamingNotAllowed: return "Downloading of assets is not allowed"; break; case Reason::Idle: return "Inactivity"; break; default: return "Unknown reason"; break; } } ConnectionResult NetworkManagerBase::OnPeerConnected(const Peer& peer, std::uint32_t clientData) { return _handler->OnPeerConnected(peer, clientData); } void NetworkManagerBase::OnPeerDisconnected(const Peer& peer, Reason reason) { _handler->OnPeerDisconnected(peer, reason); if (peer && _state == NetworkState::Listening) { std::unique_lock lock(_lock); for (std::size_t i = 0; i < _peers.size(); i++) { if (peer == _peers[i]) { _peers.eraseUnordered(i); break; } } } } void NetworkManagerBase::InitializeBackend() { if (++_initializeCount == 1) { std::int32_t error = enet_initialize(); DEATH_ASSERT(error == 0, ("enet_initialize() failed with error {}", error), ); } } void NetworkManagerBase::ReleaseBackend() { if (--_initializeCount == 0) { enet_deinitialize(); } } void NetworkManagerBase::OnClientThread(void* param) { Thread::SetCurrentName("Multiplayer client"); NetworkManagerBase* _this = static_cast(param); INetworkHandler* handler = _this->_handler; ENetHost* host = nullptr; _this->_host = host; // Try to connect to each specified endpoint ENetEvent ev{}; for (std::int32_t i = 0; i < std::int32_t(_this->_desiredEndpoints.size()) && _this->_state != NetworkState::None; i++) { ENetAddress& addr = _this->_desiredEndpoints[i]; LOGI("[MP] Connecting to {} ({}/{})", AddressToString(addr, true), i + 1, _this->_desiredEndpoints.size()); if (host != nullptr) { enet_host_destroy(host); } host = enet_host_create(nullptr, 1, std::size_t(NetworkChannel::Count), 0, 0); _this->_host = host; if (host == nullptr) { LOGE("[MP] Failed to create client"); _this->OnPeerDisconnected({}, Reason::InvalidParameter); return; } ENetPeer* peer = enet_host_connect(host, &addr, std::size_t(NetworkChannel::Count), _this->_clientData); if (peer == nullptr) { continue; } std::int32_t n = 10; while (n > 0) { if (_this->_state == NetworkState::None) { n = 0; break; } LOGD("enet_host_service() is trying to connect: {} ms", enet_time_get()); if (enet_host_service(host, &ev, 1000) > 0 && ev.type == ENET_EVENT_TYPE_CONNECT) { break; } n--; } if (n != 0) { _this->_peers.push_back(ev.peer); break; } } Reason reason; if (_this->_peers.empty()) { LOGE("[MP] Failed to connect to the server"); _this->_state = NetworkState::None; reason = Reason::ConnectionTimedOut; } else { _this->_state = NetworkState::Connected; _this->OnPeerConnected(ev.peer, ev.data); reason = Reason::Unknown; while DEATH_LIKELY(_this->_state != NetworkState::None) { std::int32_t result; { std::unique_lock lock(_this->_lock); result = enet_host_service(host, &ev, 0); } if DEATH_UNLIKELY(result <= 0) { if DEATH_UNLIKELY(result < 0) { LOGE("[MP] enet_host_service() returned {}", result); reason = Reason::ConnectionLost; break; } Thread::Sleep(ProcessingIntervalMs); continue; } switch (ev.type) { case ENET_EVENT_TYPE_RECEIVE: { auto data = arrayView(ev.packet->data, ev.packet->dataLength); handler->OnPacketReceived(ev.peer, ev.channelID, data[0], data.exceptPrefix(1)); enet_packet_destroy(ev.packet); break; } case ENET_EVENT_TYPE_DISCONNECT: _this->_state = NetworkState::None; reason = (Reason)ev.data; break; case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: _this->_state = NetworkState::None; reason = Reason::ConnectionLost; break; } } } if (!_this->_peers.empty()) { _this->OnPeerDisconnected(_this->_peers[0], reason); for (ENetPeer* peer : _this->_peers) { enet_peer_disconnect_now(peer, (std::uint32_t)Reason::Disconnected); } _this->_peers.clear(); } else { _this->OnPeerDisconnected({}, reason); } enet_host_destroy(_this->_host); _this->_host = nullptr; _this->_handler = nullptr; _this->_thread.Detach(); LOGD("[MP] Client thread exited: {} ({})", NetworkManagerBase::ReasonToString(reason), reason); } void NetworkManagerBase::OnServerThread(void* param) { Thread::SetCurrentName("Multiplayer server"); NetworkManagerBase* _this = static_cast(param); INetworkHandler* handler = _this->_handler; ENetHost* host = _this->_host; _this->_peers.reserve(16); ENetEvent ev{}; while DEATH_LIKELY(_this->_state != NetworkState::None) { std::int32_t result; { std::unique_lock lock(_this->_lock); result = enet_host_service(host, &ev, 0); } if DEATH_UNLIKELY(result <= 0) { if DEATH_UNLIKELY(result < 0) { LOGE("[MP] enet_host_service() returned {}", result); // Server failed, try to recreate it for (auto& peer : _this->_peers) { _this->OnPeerDisconnected(peer, Reason::ConnectionLost); } _this->_peers.clear(); ENetAddress addr = host->address; { std::unique_lock lock(_this->_lock); enet_host_destroy(host); host = enet_host_create(&addr, MaxPeerCount, std::size_t(NetworkChannel::Count), 0, 0); _this->_host = host; } if (host == nullptr) { LOGE("[MP] Failed to recreate the server"); break; } } Thread::Sleep(ProcessingIntervalMs); continue; } switch (ev.type) { case ENET_EVENT_TYPE_CONNECT: { ConnectionResult result = _this->OnPeerConnected(ev.peer, ev.data); if DEATH_LIKELY(result.IsSuccessful()) { std::unique_lock lock(_this->_lock); bool alreadyExists = false; for (std::size_t i = 0; i < _this->_peers.size(); i++) { if (ev.peer == _this->_peers[i]) { alreadyExists = true; break; } } if DEATH_UNLIKELY(alreadyExists) { LOGW("Peer is already connected [{:.8x}]", std::uint64_t(ev.peer)); } else { _this->_peers.push_back(ev.peer); } } else { std::unique_lock lock(_this->_lock); enet_peer_disconnect(ev.peer, std::uint32_t(result.FailureReason)); } break; } case ENET_EVENT_TYPE_RECEIVE: { auto data = arrayView(ev.packet->data, ev.packet->dataLength); handler->OnPacketReceived(ev.peer, ev.channelID, data[0], data.exceptPrefix(1)); enet_packet_destroy(ev.packet); break; } case ENET_EVENT_TYPE_DISCONNECT: _this->OnPeerDisconnected(ev.peer, Reason(ev.data)); break; case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: _this->OnPeerDisconnected(ev.peer, Reason::ConnectionLost); break; } } for (ENetPeer* peer : _this->_peers) { enet_peer_disconnect_now(peer, std::uint32_t(Reason::ServerStopped)); } _this->_peers.clear(); enet_host_destroy(_this->_host); _this->_host = nullptr; _this->_handler = nullptr; _this->_thread.Detach(); LOGD("[MP] Server thread exited"); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/NetworkManagerBase.h000066400000000000000000000135651512772601700277440ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "ConnectionResult.h" #include "Peer.h" #include "Reason.h" #include "ServerDiscovery.h" #include "../../nCine/Threading/Thread.h" #include "../../nCine/Threading/ThreadSync.h" #include #include #include #include #include #include struct _ENetHost; using namespace Death::Containers; using namespace Death::IO; using namespace Death::Threading; using namespace nCine; namespace Jazz2::Multiplayer { class INetworkHandler; /** @brief Network packet channel */ enum class NetworkChannel : std::uint8_t { Main, /**< Main */ UnreliableUpdates, /**< Unreliable updates */ Count /**< Count of supported channels */ }; /** @brief State of network connection */ enum class NetworkState { None, /**< Disconnected */ Listening, /**< Listening as server */ Connecting, /**< Connecting to server as client */ Connected /**< Connected to server as client */ }; /** @brief All connected peers tag type */ struct AllPeersT { #ifndef DOXYGEN_GENERATING_OUTPUT struct Init {}; // Explicit constructor to avoid ambiguous calls when using {} constexpr explicit AllPeersT(Init) {} #endif }; /** @brief Local peer tag type */ struct LocalPeerT { #ifndef DOXYGEN_GENERATING_OUTPUT struct Init {}; // Explicit constructor to avoid ambiguous calls when using {} constexpr explicit LocalPeerT(Init) {} #endif }; /** @brief All connected peers tag Use in @ref NetworkManagerBase::SendTo() to send to all connected peers or the remote server peer. */ constexpr AllPeersT AllPeers{AllPeersT::Init{}}; /** @brief Local peer tag */ constexpr LocalPeerT LocalPeer{LocalPeerT::Init{}}; /** @brief Allows to create generic network clients and servers */ class NetworkManagerBase : public Death::IDisposable { friend class ServerDiscovery; public: /** @{ @name Constants */ /** @brief Maximum connected peer count */ static constexpr std::uint32_t MaxPeerCount = 128; /** @} */ NetworkManagerBase(); ~NetworkManagerBase(); NetworkManagerBase(const NetworkManagerBase&) = delete; NetworkManagerBase& operator=(const NetworkManagerBase&) = delete; /** @brief Creates a client connection to a remote server */ virtual void CreateClient(INetworkHandler* handler, StringView endpoints, std::uint16_t defaultPort, std::uint32_t clientData); /** @brief Creates a server that accepts incoming connections */ virtual bool CreateServer(INetworkHandler* handler, std::uint16_t port); /** @brief Disposes all active connections */ virtual void Dispose(); /** @brief Returns state of network connection */ NetworkState GetState() const; /** @brief Returns mean round trip time to the server, in milliseconds */ std::uint32_t GetRoundTripTimeMs() const; /** @brief Returns all IPv4 and IPv6 addresses along with ports of the server */ Array GetServerEndpoints() const; /** @brief Returns port of the server */ std::uint16_t GetServerPort() const; /** @brief Sends a packet to a given peer */ void SendTo(const Peer& peer, NetworkChannel channel, std::uint8_t packetType, ArrayView data); /** @brief Sends a packet to all connected peers that match a given predicate */ void SendTo(Function&& predicate, NetworkChannel channel, std::uint8_t packetType, ArrayView data); /** @brief Sends a packet to all connected peers or the remote server peer */ void SendTo(AllPeersT, NetworkChannel channel, std::uint8_t packetType, ArrayView data); /** @brief Kicks a given peer from the server */ void Kick(const Peer& peer, Reason reason); /** @brief Converts the specified IPv4 endpoint to the string representation */ static String AddressToString(const struct in_addr& address, std::uint16_t port = 0); #if ENET_IPV6 /** @brief Converts the specified IPv6 endpoint to the string representation */ static String AddressToString(const struct in6_addr& address, std::uint16_t scopeId, std::uint16_t port = 0); #endif /** @brief Converts the specified ENet address to the string representation */ static String AddressToString(const ENetAddress& address, bool includePort = false); /** @brief Converts the endpoint of the specified peer to the string representation */ static String AddressToString(const Peer& peer); /** @brief Returns `true` if the specified string representation of the address is valid */ static bool IsAddressValid(StringView address); /** @brief Returns `true` if the specified string representation of the domain name is valid */ static bool IsDomainValid(StringView domain); /** @brief Attempts to split the specified endpoint into address and port */ static bool TrySplitAddressAndPort(StringView input, StringView& address, std::uint16_t& port); /** @brief Converts the specified reason to the string representation */ static const char* ReasonToString(Reason reason); protected: /** @brief Called when a peer connects to the local server or the local client connects to a server */ virtual ConnectionResult OnPeerConnected(const Peer& peer, std::uint32_t clientData); /** @brief Called when a peer disconnects from the local server or the local client disconnects from a server */ virtual void OnPeerDisconnected(const Peer& peer, Reason reason); private: static constexpr std::uint32_t ProcessingIntervalMs = 4; _ENetHost* _host; Thread _thread; NetworkState _state; std::uint32_t _clientData; SmallVector<_ENetPeer*, 1> _peers; INetworkHandler* _handler; SmallVector _desiredEndpoints; Spinlock _lock; static void InitializeBackend(); static void ReleaseBackend(); static void OnClientThread(void* param); static void OnServerThread(void* param); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/PacketTypes.h000066400000000000000000000046161512772601700264560ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../Main.h" namespace Jazz2::Multiplayer { /** @brief Packet type broadcasted on the local network */ enum class BroadcastPacketType { Null, DiscoveryRequest, DiscoveryResponse }; /** @brief Packet type going from client to server */ enum class ClientPacketType { Null, Ping, Reserved, Rpc, Auth = 10, LevelReady, ChatMessage, ValidateAssetsResponse, ForceResyncActors = 20, PlayerReady = 30, PlayerUpdate, PlayerKeyPress, PlayerChangeWeaponRequest, PlayerSpectate, // TODO PlayerAckWarped }; /** @brief Packet type going from server to client */ enum class ServerPacketType { Null, Pong, Reserved, Rpc, AuthResponse = 70, PeerSetProperty, ValidateAssets, StreamAsset, LoadLevel = 80, LevelSetProperty, LevelResetProperties, // TODO ShowInGameLobby, FadeOut, PlaySfx, PlayCommonSfx, ShowAlert, // TODO ChatMessage, SyncTileMap, SetTrigger, AdvanceTileAnimation, RevertTileAnimation, // TODO CreateDebris, CreateControllablePlayer = 110, CreateRemoteActor, CreateMirroredActor, DestroyRemoteActor, UpdateAllActors, ChangeRemoteActorMetadata, MarkRemoteActorAsPlayer, UpdatePositionsInRound, PlayerSetProperty = 130, PlayerResetProperties, PlayerRespawn, PlayerMoveInstantly, PlayerAckWarped, // TODO PlayerActivateForce, // TODO PlayerEmitWeaponFlare, PlayerChangeWeapon, PlayerTakeDamage, PlayerActivateSpring, PlayerWarpIn }; /** @brief Peer property type from @ref ServerPacketType::PeerSetProperty */ enum class PeerPropertyType { Unknown, Connected, Disconnected, Roasted, Count }; /** @brief Level property type from @ref ServerPacketType::LevelSetProperty */ enum class LevelPropertyType { Unknown, State = 1, GameMode, LevelText = 10, // TODO Music, Count }; /** @brief Player property type from @ref ServerPacketType::PlayerSetProperty */ enum class PlayerPropertyType { Unknown, PlayerType = 1, Lives, Health, Controllable, Invulnerable, Modifier, Dizzy, Freeze, Shield, LimitCameraView, OverrideCameraView, ShakeCameraView, WeaponAmmo = 30, WeaponUpgrades, Coins = 60, Gems, Score, Points = 90, PositionInRound, // TODO: Unused Deaths, Kills, Laps, TreasureCollected, Count }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/Peer.h000066400000000000000000000014211512772601700251040ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../Main.h" struct _ENetPeer; namespace Jazz2::Multiplayer { /** @brief Remote peer as opaque handle */ struct Peer { Peer(std::nullptr_t = nullptr) : _enet(nullptr) {} #ifndef DOXYGEN_GENERATING_OUTPUT Peer(_ENetPeer* peer) : _enet(peer) {} #endif inline bool operator==(const Peer& other) const { return (_enet == other._enet); } inline bool operator!=(const Peer& other) const { return (_enet != other._enet); } explicit operator bool() const { return IsValid(); } /** @brief Returns `true` if the peer is valid */ bool IsValid() const { return (_enet != nullptr); } #ifndef DOXYGEN_GENERATING_OUTPUT _ENetPeer* _enet; #endif }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/PeerDescriptor.h000066400000000000000000000055671512772601700271620ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "Peer.h" #include "../PlayerType.h" #include "../PreferencesCache.h" #include "../../nCine/Base/TimeStamp.h" #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Actors::Multiplayer { class MpPlayer; } namespace Jazz2::Multiplayer { /** @brief Peer state in a level */ enum class PeerLevelState { Unknown, /**< Unknown */ ValidatingAssets, /**< Peer received list of required assets, the server is waiting for response */ StreamingMissingAssets, /**< Missing assets are being streamed to peer */ LevelLoaded, /**< Peer finished loading of the level */ LevelSynchronized, /**< Peer finished synchronized entities in the level */ Spectating, /**< Peer is spectating */ PlayerReady, /**< Player is ready to spawn */ PlayerSpawned /**< Player is spawned */ }; /** @brief Peer descriptor */ struct PeerDescriptor { /** @brief Remote peer if the peer is connected remotely */ Peer RemotePeer; /** @brief Unique Player ID if the peer is connected remotely */ Uuid UniquePlayerID; /** @brief Whether the peer is already successfully authenticated */ bool IsAuthenticated; /** @brief Whether the peer has admin privileges */ bool IsAdmin; /** @brief Whether ledge climbing is enabled by client */ bool EnableLedgeClimb; /** @brief Whether the peer voted "yes" in the active poll */ bool VotedYes; /** @brief Team ID */ std::uint8_t Team; /** @brief Preferred player type selected by the peer */ PlayerType PreferredPlayerType; /** @brief Player display name */ String PlayerName; /** @brief Earned points in the current session (championship) */ std::uint32_t Points; /** @brief Game mode specific points held by the player in a round */ std::uint32_t PointsInRound; /** @brief Position in a round */ std::uint32_t PositionInRound; /** @brief State of the player in the current level */ PeerLevelState LevelState; /** @brief Spawned player in the current level */ Actors::Multiplayer::MpPlayer* Player; /** @brief Last update of the player from client */ std::uint64_t LastUpdated; /** @brief Deaths of the player in the current round */ std::uint32_t Deaths; /** @brief Kills of the player in the current round */ std::uint32_t Kills; /** @brief Laps of the player in the current round */ std::uint32_t Laps; /** @brief Timestamp when the last lap started */ TimeStamp LapStarted; /** @brief Treasure collected of the player in the current round */ std::uint32_t TreasureCollected; /** @brief Elapsed frames when the player is idle */ float IdleElapsedFrames; /** @brief Elapsed frames when the player lost all lives */ float DeathElapsedFrames; /** @brief Elapsed frames of all completed laps */ float LapsElapsedFrames; PeerDescriptor(); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/Reason.h000066400000000000000000000030561512772601700254460ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../Main.h" namespace Jazz2::Multiplayer { /** @brief Client disconnect reason */ enum class Reason : std::uint32_t { Unknown, /**< Unspecified */ Disconnected, /**< Client disconnected by user */ InvalidParameter, /**< Invalid parameter specified */ IncompatibleVersion, /**< Incompatible client version */ AuthFailed, /**< Authentication failed */ InvalidPassword, /**< Invalid password specified */ InvalidPlayerName, /**< Invalid player name specified */ NotInWhitelist, /**< Client is not in server whitelist */ Requires3rdPartyAuthProvider, /**< Server requires 3rd party authentication provider (e.g., Discord) */ ServerIsFull, /**< Server is full or busy */ ServerNotReady, /**< Server is not ready yet */ ServerStopped, /**< Server is stopped for unknown reason */ ServerStoppedForMaintenance, /**< Server is stopped for maintenance */ ServerStoppedForReconfiguration, /**< Server is stopped for reconfiguration */ ServerStoppedForUpdate, /**< Server is stopped for update */ ConnectionLost, /**< Connection lost */ ConnectionTimedOut, /**< Connection timed out */ Kicked, /**< Kicked by server */ Banned, /**< Banned by server */ CheatingDetected, /**< Cheating detected */ AssetStreamingNotAllowed, /**< Downloading of assets is not allowed, but some assets are missing */ Idle /**< Inactivity */ }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/ServerDiscovery.cpp000066400000000000000000000571451512772601700277200ustar00rootroot00000000000000#include "ServerDiscovery.h" #if defined(WITH_MULTIPLAYER) #include "NetworkManager.h" #include "PacketTypes.h" #include "../PreferencesCache.h" #include "../../nCine/Application.h" #include "../../nCine/Base/Algorithms.h" #include "../../nCine/Base/FrameTimer.h" #include "../../nCine/Threading/Thread.h" #include #include #include #include #include #include #include #if defined(DEATH_TARGET_ANDROID) # include "Backends/ifaddrs-android.h" #elif defined(DEATH_TARGET_SWITCH) && ENET_IPV6 // `ipv6_mreq` is not defined in Switch SDK, but it doesn't work well anyway struct ipv6_mreq { struct in6_addr ipv6mr_multiaddr; /* IPv6 multicast address */ unsigned int ipv6mr_interface; /* Interface index */ }; #elif defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) # include # include #elif defined(DEATH_TARGET_WINDOWS) # include # include #endif #include "../../jsoncpp/json.h" using namespace Death::Containers::Literals; using namespace Death::IO; using namespace nCine; using namespace std::string_view_literals; /** @brief @ref Death::Containers::StringView from @ref NCINE_VERSION */ #define NCINE_VERSION_s DEATH_PASTE(NCINE_VERSION, _s) namespace Jazz2::Multiplayer { #if ENET_IPV6 static std::int32_t GetDefaultIPv6MulticastIfIndex() { # if defined(DEATH_TARGET_ANDROID) || defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) std::int32_t ifidx = 0; struct ifaddrs* ifaddr; struct ifaddrs* ifa; if (getifaddrs(&ifaddr) == 0) { for (ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { // Prefer first adapter that is up, not loopback, supports multicast if (ifa->ifa_addr != nullptr && (ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6) && (ifa->ifa_flags & IFF_UP) && !(ifa->ifa_flags & IFF_LOOPBACK) && (ifa->ifa_flags & IFF_MULTICAST)) { ifidx = if_nametoindex(ifa->ifa_name); if (ifidx > 0) { LOGI("[MP] Using {} interface \"{}\" ({}) for local discovery", ifa->ifa_addr->sa_family == AF_INET6 ? "IPv6" : "IPv4", ifa->ifa_name, ifidx); break; } } } freeifaddrs(ifaddr); } if (ifidx == 0) { LOGI("[MP] No suitable interface found for local discovery"); ifidx = if_nametoindex("wlan0"); } return ifidx; # elif defined(DEATH_TARGET_WINDOWS) ULONG bufferSize = 0; ::GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, NULL, NULL, &bufferSize); std::unique_ptr buffer = std::make_unique(bufferSize); auto* adapterAddresses = reinterpret_cast(buffer.get()); if (::GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, NULL, adapterAddresses, &bufferSize) == NO_ERROR) { for (auto* adapter = adapterAddresses; adapter != nullptr; adapter = adapter->Next) { // Prefer first adapter that is up, not loopback, supports multicast if (adapter->OperStatus == IfOperStatusUp && adapter->IfType != IF_TYPE_SOFTWARE_LOOPBACK && !(adapter->Flags & IP_ADAPTER_NO_MULTICAST)) { LOGI("[MP] Using IPv6 interface \"{}\" ({}:{}) for local discovery", Utf8::FromUtf16(adapter->FriendlyName), adapter->AdapterName, (std::int32_t)adapter->Ipv6IfIndex); return (std::int32_t)adapter->Ipv6IfIndex; } } } LOGI("[MP] No suitable interface found for local discovery"); return 0; # else return 0; # endif } #endif ServerDiscovery::ServerDiscovery(NetworkManager* server) : _server(server), _observer(nullptr), _onlineSuccess(false) { DEATH_DEBUG_ASSERT(server != nullptr, "server is null", ); _thread = Thread(ServerDiscovery::OnServerThread, this); } ServerDiscovery::ServerDiscovery(IServerObserver* observer) : _server(nullptr), _observer(observer) { DEATH_DEBUG_ASSERT(observer != nullptr, "observer is null", ); _thread = Thread(ServerDiscovery::OnClientThread, this); } ServerDiscovery::~ServerDiscovery() { _server = nullptr; _observer = nullptr; _thread.Join(); NetworkManagerBase::ReleaseBackend(); } void ServerDiscovery::SetStatusProvider(std::weak_ptr statusProvider) { _statusProvider = std::move(statusProvider); } ENetSocket ServerDiscovery::TryCreateLocalSocket(const char* multicastAddress, ENetAddress& parsedAddress) { #if ENET_IPV6 std::int32_t ifidx = GetDefaultIPv6MulticastIfIndex(); ENetSocket socket = enet_socket_create(ENET_SOCKET_TYPE_DATAGRAM); if (socket == ENET_SOCKET_NULL) { # if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); # else std::int32_t error = errno; # endif LOGE("[MP] Failed to create socket for local server discovery (error: {})", error); return ENET_SOCKET_NULL; } std::int32_t on = 1, hops = 3; if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on)) != 0 || setsockopt(socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, (const char*)&ifidx, sizeof(ifidx)) != 0 || setsockopt(socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (const char*)&hops, sizeof(hops)) != 0 || setsockopt(socket, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const char*)&on, sizeof(on)) != 0) { # if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); # else std::int32_t error = errno; # endif LOGE("[MP] Failed to enable multicast on socket for local server discovery (error: {})", error); enet_socket_destroy(socket); return ENET_SOCKET_NULL; } struct sockaddr_in6 saddr; std::memset(&saddr, 0, sizeof(saddr)); saddr.sin6_family = AF_INET6; saddr.sin6_port = htons(DiscoveryPort); saddr.sin6_addr = in6addr_any; if (bind(socket, (struct sockaddr*)&saddr, sizeof(saddr))) { # if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); # else std::int32_t error = errno; # endif LOGE("[MP] Failed to bind socket for local server discovery (error: {})", error); enet_socket_destroy(socket); return ENET_SOCKET_NULL; } std::int32_t result = inet_pton(AF_INET6, multicastAddress, &parsedAddress.host); if (result != 1) { # if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); # else std::int32_t error = errno; # endif LOGE("[MP] Failed to parse multicast address for local server discovery (result: {}, error: {})", result, error); enet_socket_destroy(socket); return ENET_SOCKET_NULL; } parsedAddress.sin6_scope_id = ifidx; parsedAddress.port = DiscoveryPort; struct ipv6_mreq mreq; std::memcpy(&mreq.ipv6mr_multiaddr, &parsedAddress.host, sizeof(parsedAddress.host)); mreq.ipv6mr_interface = ifidx; if (setsockopt(socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char*)&mreq, sizeof(mreq))) { # if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); # else std::int32_t error = errno; # endif LOGE("[MP] Failed to join multicast group on socket for local server discovery (error: {})", error); enet_socket_destroy(socket); return ENET_SOCKET_NULL; } #else // TODO: Use broadcast on IPv4 LOGW("[MP] Local server discovery is not supported on IPv4"); ENetSocket socket = ENET_SOCKET_NULL; #endif return socket; } void ServerDiscovery::SendLocalDiscoveryRequest(ENetSocket socket, const ENetAddress& address) { if (socket == ENET_SOCKET_NULL) { return; } MemoryStream packet(9); packet.WriteValue(PacketSignature); packet.WriteValue((std::uint8_t)BroadcastPacketType::DiscoveryRequest); ENetBuffer sendbuf; sendbuf.data = (void*)packet.GetBuffer(); sendbuf.dataLength = packet.GetSize(); std::int32_t result = enet_socket_send(socket, &address, &sendbuf, 1); if (result != (std::int32_t)sendbuf.dataLength) { #if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); #else std::int32_t error = errno; #endif LOGE("[MP] Failed to send local discovery request (result: {}, error: {})", result, error); } } void ServerDiscovery::DownloadPublicServerList(IServerObserver* observer) { LOGD("[MP] Downloading public server list…"); String url = "https://deat.tk/jazz2/servers?fetch&v=2&d="_s + PreferencesCache::GetDeviceID(); auto request = WebSession::GetDefault().CreateRequest(url); auto result = request.Execute(); if (result) { auto s = request.GetResponse().GetStream(); auto size = s->GetSize(); auto buffer = std::make_unique(size); s->Read(buffer.get(), size); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + size, &doc, &errors)) { LOGI("[MP] Downloaded public server list with {} entries ({} bytes)", (std::uint32_t)doc["s"].size(), (std::uint32_t)size); for (auto serverItem : doc["s"]) { std::string_view serverName, serverUuid, serverEndpoints; if (serverItem["n"].get(serverName) == Json::SUCCESS && !serverName.empty() && serverItem["u"].get(serverUuid) == Json::SUCCESS && !serverUuid.empty() && serverItem["e"].get(serverEndpoints) == Json::SUCCESS && !serverEndpoints.empty()) { std::int64_t currentPlayers = 0, maxPlayers = 0; serverItem["c"].get(currentPlayers); serverItem["m"].get(maxPlayers); std::string_view version; serverItem["v"].get(version); ServerDescription discoveredServer{}; discoveredServer.Version = version; discoveredServer.Name = serverName; discoveredServer.EndpointString = serverEndpoints; discoveredServer.Name = serverName; discoveredServer.CurrentPlayerCount = (std::uint32_t)currentPlayers; discoveredServer.MaxPlayerCount = (std::uint32_t)maxPlayers; LOGD("[MP] -\tFound server \"{}\" at {}", discoveredServer.Name, discoveredServer.EndpointString); observer->OnServerFound(std::move(discoveredServer)); } } } else { LOGE("[MP] Failed to parse public server list: {}", StringView(errors)); } } else { LOGE("[MP] Failed to download public server list: {}", result.error); } } bool ServerDiscovery::ProcessLocalDiscoveryResponses(ENetSocket socket, ServerDescription& discoveredServer, std::int32_t timeoutMs) { if (socket == ENET_SOCKET_NULL) { return false; } ENetSocketSet set; ENET_SOCKETSET_EMPTY(set); ENET_SOCKETSET_ADD(set, socket); if (enet_socketset_select(socket, &set, NULL, timeoutMs) <= 0) { return false; } ENetAddress endpoint; std::uint8_t buffer[512]; ENetBuffer recvbuf; recvbuf.data = buffer; recvbuf.dataLength = sizeof(buffer); const std::int32_t bytesRead = enet_socket_receive(socket, &endpoint, &recvbuf, 1); if (bytesRead <= 0) { return false; } MemoryStream packet(buffer, bytesRead); std::uint64_t signature = packet.ReadValue(); BroadcastPacketType packetType = (BroadcastPacketType)packet.ReadValue(); if (signature != PacketSignature || packetType != BroadcastPacketType::DiscoveryResponse) { return false; } // Override the port, because it points to the discovery service, not the actual server endpoint.port = packet.ReadValue(); discoveredServer.EndpointString = NetworkManagerBase::AddressToString(endpoint, true); if (discoveredServer.EndpointString.empty()) { return false; } packet.Read(discoveredServer.UniqueServerID, sizeof(discoveredServer.UniqueServerID)); std::uint8_t versionLength = packet.ReadValue(); discoveredServer.Version = String(NoInit, versionLength); packet.Read(discoveredServer.Version.data(), versionLength); std::uint8_t nameLength = packet.ReadValue(); discoveredServer.Name = String(NoInit, nameLength); packet.Read(discoveredServer.Name.data(), nameLength); discoveredServer.Flags = packet.ReadVariableUint32() | 0x80000000u /*Local*/; discoveredServer.GameMode = (MpGameMode)packet.ReadValue(); discoveredServer.CurrentPlayerCount = packet.ReadVariableUint32(); discoveredServer.MaxPlayerCount = packet.ReadVariableUint32(); nameLength = packet.ReadValue(); discoveredServer.LevelName = String(NoInit, nameLength); packet.Read(discoveredServer.LevelName.data(), nameLength); LOGD("[MP] Found local server \"{}\" at {}", discoveredServer.Name, discoveredServer.EndpointString); return true; } bool ServerDiscovery::ProcessLocalDiscoveryRequests(ENetSocket socket, std::int32_t timeoutMs) { if (socket == ENET_SOCKET_NULL) { return false; } ENetSocketSet set; ENET_SOCKETSET_EMPTY(set); ENET_SOCKETSET_ADD(set, socket); if (enet_socketset_select(socket, &set, NULL, timeoutMs) <= 0) { return false; } ENetAddress endpoint; std::uint8_t buffer[512]; ENetBuffer recvbuf; recvbuf.data = buffer; recvbuf.dataLength = sizeof(buffer); const std::int32_t bytesRead = enet_socket_receive(socket, &endpoint, &recvbuf, 1); if (bytesRead <= 0) { return false; } MemoryStream packet(buffer, bytesRead); std::uint64_t signature = packet.ReadValue(); BroadcastPacketType packetType = (BroadcastPacketType)packet.ReadValue(); if (signature != PacketSignature || packetType != BroadcastPacketType::DiscoveryRequest) { return false; } return true; } void ServerDiscovery::SendLocalDiscoveryResponse(ENetSocket socket, NetworkManager* server) { if (socket == ENET_SOCKET_NULL) { return; } // If server name is empty, it's private and shouldn't respond to discovery messages auto& serverConfig = server->GetServerConfiguration(); if (!serverConfig.ServerName.empty()) { const auto& id = serverConfig.UniqueServerID; MemoryStream packet(512); packet.WriteValue(PacketSignature); packet.WriteValue((std::uint8_t)BroadcastPacketType::DiscoveryResponse); packet.WriteValue(server->GetServerPort()); packet.Write(id.data(), id.size()); StringView serverVersion = NCINE_VERSION_s; serverVersion = serverVersion.prefix(serverVersion.findOr('-', serverVersion.end()).begin()); packet.WriteValue((std::uint8_t)serverVersion.size()); packet.Write(serverVersion.data(), (std::uint8_t)serverVersion.size()); packet.WriteValue((std::uint8_t)serverConfig.ServerName.size()); packet.Write(serverConfig.ServerName.data(), (std::uint8_t)serverConfig.ServerName.size()); std::uint32_t flags = 0; if (!serverConfig.ServerPassword.empty()) { flags |= 0x01; } if (!serverConfig.WhitelistedUniquePlayerIDs.empty()) { flags |= 0x02; } packet.WriteVariableUint32(flags); packet.WriteValue((std::uint8_t)serverConfig.GameMode); packet.WriteVariableUint32(server->GetPeerCount()); packet.WriteVariableUint32(serverConfig.MaxPlayerCount); if (auto statusProvider = _statusProvider.lock()) { auto levelDisplayName = statusProvider->GetLevelDisplayName().trimmed(); packet.WriteValue((std::uint8_t)levelDisplayName.size()); packet.Write(levelDisplayName.data(), (std::uint8_t)levelDisplayName.size()); } else { packet.WriteValue(0); } ENetBuffer sendbuf; sendbuf.data = (void*)packet.GetBuffer(); sendbuf.dataLength = packet.GetSize(); std::int32_t result = enet_socket_send(socket, &_localMulticastAddress, &sendbuf, 1); if (result != (std::int32_t)sendbuf.dataLength) { #if defined(DEATH_TARGET_WINDOWS) std::int32_t error = ::WSAGetLastError(); #else std::int32_t error = errno; #endif LOGE("[MP] Failed to send local discovery response (result: {}, error: {})", result, error); } } } void ServerDiscovery::PublishToPublicServerList(NetworkManager* server) { _onlineSuccess = false; auto& serverConfig = server->GetServerConfiguration(); if (serverConfig.ServerName.empty()) { return; } String serverName = StringUtils::replaceAll(StringUtils::replaceAll(StringUtils::replaceAll(serverConfig.ServerName, "\\"_s, "\\\\"_s), "\""_s, "\\\""_s), "\f"_s, "\\f"_s); char input[2048]; std::size_t length = formatInto(input, "{{\"n\":\"{}\",\"u\":\"", serverName); const auto& id = serverConfig.UniqueServerID; length += formatInto({ input + length, sizeof(input) - length }, "{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); length += formatInto({ input + length, sizeof(input) - length }, "\",\"e\":\""); StringView address; std::uint16_t port; if (NetworkManagerBase::TrySplitAddressAndPort(serverConfig.ServerAddressOverride, address, port)) { String addressEscaped = StringUtils::replaceAll(StringUtils::replaceAll(address, "\\"_s, "\\\\"_s), "\""_s, "\\\""_s); if (port == 0) { port = server->_host->address.port; } length += formatInto({ input + length, sizeof(input) - length }, "{}:{}", addressEscaped, port); } else { bool isFirst = true; auto endpoints = server->GetServerEndpoints(); for (auto& endpoint : endpoints) { if (length > 1228) { // It's usually enough for all the endpoints break; } if (isFirst) { isFirst = false; } else { length += formatInto({ input + length, sizeof(input) - length }, "|"); } length += formatInto({ input + length, sizeof(input) - length }, "{}", endpoint); } } std::int32_t serverLoad = (std::int32_t)(theApplication().GetFrameTimer().GetLastFrameDuration() * 1000.0f); if (serverLoad > 400) { serverLoad = -1; } String levelDisplayName; if (auto statusProvider = _statusProvider.lock()) { levelDisplayName = StringUtils::replaceAll(StringUtils::replaceAll(StringUtils::replaceAll(statusProvider->GetLevelDisplayName().trimmed(), "\\"_s, "\\\\"_s), "\""_s, "\\\""_s), "\f"_s, "\\f"_s); } length += formatInto({ input + length, sizeof(input) - length }, "\",\"v\":\"{}\",\"d\":\"{}\",\"p\":{},\"m\":{},\"s\":{},\"l\":{},\"g\":{},\"f\":\"{}\"}}", NCINE_VERSION, PreferencesCache::GetDeviceID(), server->GetPeerCount(), serverConfig.MaxPlayerCount, serverConfig.StartUnixTimestamp, serverLoad, serverConfig.GameMode, levelDisplayName); auto request = WebSession::GetDefault().CreateRequest("https://deat.tk/jazz2/servers"_s); request.SetMethod("POST"_s); request.SetData(StringView(input, length), "application/json"_s); if (auto result = request.Execute()) { auto s = request.GetResponse().GetStream(); auto size = s->GetSize(); auto buffer = std::make_unique(size); s->Read(buffer.get(), size); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + size, &doc, &errors)) { bool success; std::string_view endpoints; if (doc["r"].get(success) == Json::SUCCESS && success && doc["e"].get(endpoints) == Json::SUCCESS && !endpoints.empty()) { _onlineSuccess = true; LOGD("[MP] Server published with following endpoints: {}", StringView(endpoints)); } else { LOGE("[MP] Failed to publish the server: Request rejected"); } } else { LOGE("[MP] Failed to publish the server: Response cannot be parsed: {}", StringView(errors)); } } else { LOGE("[MP] Failed to publish the server: {}", result.error); } } void ServerDiscovery::DelistFromPublicServerList(NetworkManager* server) { if (!_onlineSuccess) { return; } _onlineSuccess = false; auto& serverConfig = server->GetServerConfiguration(); if (serverConfig.ServerName.empty()) { return; } String serverName = StringUtils::replaceAll(StringUtils::replaceAll(StringUtils::replaceAll(serverConfig.ServerName, "\\"_s, "\\\\"_s), "\""_s, "\\\""_s), "\f"_s, "\\f"_s); char input[2048]; std::size_t length = formatInto(input, "{{\"n\":\"{}\",\"u\":\"", serverName); const auto& id = serverConfig.UniqueServerID; length += formatInto({ input + length, sizeof(input) - length }, "{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}:{:.2X}{:.2X}", id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8], id[9], id[10], id[11], id[12], id[13], id[14], id[15]); length += formatInto({ input + length, sizeof(input) - length }, "\",\"e\":null,\"v\":\"{}\",\"d\":\"{}\"}}", NCINE_VERSION_s, PreferencesCache::GetDeviceID()); auto request = WebSession::GetDefault().CreateRequest("https://deat.tk/jazz2/servers"_s); request.SetMethod("POST"_s); request.SetData(StringView(input, length), "application/json"_s); if (auto result = request.Execute()) { auto s = request.GetResponse().GetStream(); auto size = s->GetSize(); auto buffer = std::make_unique(size); s->Read(buffer.get(), size); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(buffer.get(), buffer.get() + size, &doc, &errors)) { bool success; std::string_view endpoints; if (doc["r"].get(success) == Json::SUCCESS && success) { LOGD("[MP] Server delisted successfully"); } else { LOGE("[MP] Failed to delist the server: Request rejected"); } } else { LOGE("[MP] Failed to delist the server: Response cannot be parsed: {}", StringView(errors)); } } else { LOGE("[MP] Failed to delist the server: {}", result.error); } } void ServerDiscovery::OnClientThread(void* param) { ServerDiscovery* _this = static_cast(param); IServerObserver* observer = _this->_observer; NetworkManagerBase::InitializeBackend(); ENetSocket socket = TryCreateLocalSocket("ff02::1", _this->_localMulticastAddress); _this->_socket = socket; while (_this->_observer != nullptr) { if (_this->_lastLocalRequestTime.secondsSince() > 10) { _this->_lastLocalRequestTime = TimeStamp::now(); _this->SendLocalDiscoveryRequest(socket, _this->_localMulticastAddress); } if (_this->_lastOnlineRequestTime.secondsSince() > 60) { _this->_lastOnlineRequestTime = TimeStamp::now(); _this->DownloadPublicServerList(observer); } ServerDescription discoveredServer; if (_this->ProcessLocalDiscoveryResponses(socket, discoveredServer, 0)) { observer->OnServerFound(std::move(discoveredServer)); } else { // No responses, sleep for a while Thread::Sleep(500); } } if (_this->_socket != ENET_SOCKET_NULL) { enet_socket_destroy(_this->_socket); _this->_socket = ENET_SOCKET_NULL; } LOGD("[MP] Server discovery thread exited"); } void ServerDiscovery::OnServerThread(void* param) { ServerDiscovery* _this = static_cast(param); NetworkManager* server = _this->_server; std::int32_t delayCount = 30; // Delay for 15 seconds before starting to send discovery responses NetworkManagerBase::InitializeBackend(); ENetSocket socket = TryCreateLocalSocket("ff02::1", _this->_localMulticastAddress); _this->_socket = socket; while (_this->_server != nullptr) { delayCount--; if (delayCount <= 0) { delayCount = 10; while (_this->_localMulticastAddress.port != 0 && _this->ProcessLocalDiscoveryRequests(socket, 0)) { if (_this->_lastLocalRequestTime.secondsSince() > 15) { _this->_lastLocalRequestTime = TimeStamp::now(); _this->SendLocalDiscoveryResponse(socket, server); } } if (_this->_lastOnlineRequestTime.secondsSince() > 300) { _this->_lastOnlineRequestTime = TimeStamp::now(); _this->PublishToPublicServerList(server); } } Thread::Sleep(500); } _this->DelistFromPublicServerList(server); if (_this->_socket != ENET_SOCKET_NULL) { enet_socket_destroy(_this->_socket); _this->_socket = ENET_SOCKET_NULL; } LOGD("[MP] Server discovery thread exited"); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/ServerDiscovery.h000066400000000000000000000075261512772601700273630ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "MpGameMode.h" #include "../PreferencesCache.h" #include "../../nCine/Base/TimeStamp.h" #include "../../nCine/Threading/Thread.h" // included by "enet.h" still uses `far` macro #define far #define ENET_FEATURE_ADDRESS_MAPPING //#if defined(DEATH_DEBUG) # define ENET_DEBUG //#endif #include "Backends/enet.h" // Undefine it again after include #undef far #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Multiplayer { class NetworkManager; /** @brief Server description */ struct ServerDescription { /** @brief Server endpoint in text format */ String EndpointString; /** @brief Server unique identifier */ Uuid UniqueServerID; /** @brief Server version */ String Version; /** @brief Server name */ String Name; /** @brief Multiplayer game mode */ MpGameMode GameMode; /** @brief Server flags */ std::uint32_t Flags; /** @brief Current number of players */ std::uint32_t CurrentPlayerCount; /** @brief Maximum number of players */ std::uint32_t MaxPlayerCount; /** @brief Current level name */ String LevelName; /** @brief Whether the server is compatible with the local client */ bool IsCompatible; // TODO: LastPingTime //bool IsLost; }; /** @brief Interface to observe publicly-listed running servers @experimental */ class IServerObserver { public: /** @brief Called when a server is discovered */ virtual void OnServerFound(ServerDescription&& desc) = 0; }; /** @brief Interface to provide current status of the server @experimental */ class IServerStatusProvider { DEATH_RUNTIME_OBJECT(); public: /** @brief Returns display name of current level */ virtual StringView GetLevelDisplayName() const = 0; }; /** @brief Allows to monitor publicly-listed running servers for server listing @experimental */ class ServerDiscovery { public: /** @{ @name Constants */ /** @brief UDP port for server discovery broadcast */ static constexpr std::uint16_t DiscoveryPort = 7439; /** @brief Length of server unique identifier */ static constexpr std::int32_t UniqueIdentifierLength = 16; /** @} */ /** @brief Creates an instance to advertise a running local server */ ServerDiscovery(NetworkManager* server); /** @brief Creates an instance to observe remote servers */ ServerDiscovery(IServerObserver* observer); ~ServerDiscovery(); /** @brief Sets status provider */ void SetStatusProvider(std::weak_ptr statusProvider); private: ServerDiscovery(const ServerDiscovery&) = delete; ServerDiscovery& operator=(const ServerDiscovery&) = delete; static constexpr std::uint64_t PacketSignature = 0x2095A59FF0BFBBEF; NetworkManager* _server; IServerObserver* _observer; std::weak_ptr _statusProvider; ENetSocket _socket; Thread _thread; TimeStamp _lastLocalRequestTime; TimeStamp _lastOnlineRequestTime; ENetAddress _localMulticastAddress; bool _onlineSuccess; static ENetSocket TryCreateLocalSocket(const char* multicastAddress, ENetAddress& parsedAddress); void SendLocalDiscoveryRequest(ENetSocket socket, const ENetAddress& address); void DownloadPublicServerList(IServerObserver* observer); bool ProcessLocalDiscoveryResponses(ENetSocket socket, ServerDescription& discoveredServer, std::int32_t timeoutMs = 0); bool ProcessLocalDiscoveryRequests(ENetSocket socket, std::int32_t timeoutMs = 0); void SendLocalDiscoveryResponse(ENetSocket socket, NetworkManager* server); void PublishToPublicServerList(NetworkManager* server); void DelistFromPublicServerList(NetworkManager* server); static void OnClientThread(void* param); static void OnServerThread(void* param); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Multiplayer/ServerInitialization.h000066400000000000000000000333611512772601700303770ustar00rootroot00000000000000#pragma once #if defined(WITH_MULTIPLAYER) || defined(DOXYGEN_GENERATING_OUTPUT) #include "MpGameMode.h" #include "../LevelInitialization.h" #include "../PreferencesCache.h" #include "../../nCine/Base/HashMap.h" #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Multiplayer { /** @brief Playlist entry in @ref ServerConfiguration */ struct PlaylistEntry { /** @brief Level name in `/` format */ String LevelName; /** @brief Game mode */ MpGameMode GameMode; /** @brief Whether reforged gameplay is enabled */ bool ReforgedGameplay; /** @brief Whether every player has limited number of lives, the game ends when only one player remains */ bool Elimination; /** @brief Initial player health, default is unlimited for Race and Treasure Hunt, otherwise 5 */ std::int32_t InitialPlayerHealth; /** @brief Maximum number of seconds for a game */ std::uint32_t MaxGameTimeSecs; /** @brief Duration of pre-game before starting a round */ std::uint32_t PreGameSecs; /** @brief Duration of invulnerability after (re)spawning */ std::uint32_t SpawnInvulnerableSecs; /** @brief Total number of kills, default is 10 (Battle) */ std::uint32_t TotalKills; /** @brief Total number of laps, default is 3 (Race) */ std::uint32_t TotalLaps; /** @brief Total number of treasure collected, default is 80 (Treasure Hunt) */ std::uint32_t TotalTreasureCollected; }; /** @brief Server configuration This structure contains the server configuration, which is usually loaded from a file and partially also transferred to connected clients. @section Multiplayer-ServerConfiguration-format File format description The server configuration is read from a JSON file, which may contain the following fields: - @cpp "$include" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Include configuration from another file by path - If the JSON contains a @cpp "$include" @ce directive, it will load the referenced files recursively, but only once to avoid infinite loops - @cpp "ServerName" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Name of the server - @cpp "ServerAddressOverride" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Address override allows to specify an alternate address - The address is used only in the public list to be able to connect to the server from the outside - IPv4 address, IPv6 address or domain name can be used - It is also possible to specify a different port if it is different from the local port - @cpp "ServerPassword" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Password to join the server - @cpp "WelcomeMessage" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Message displayed to players upon joining - @cpp "MaxPlayerCount" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Maximum number of players allowed to join - @cpp "MinPlayerCount" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Minimum number of players required to start a round - @cpp "ServerPort" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan UDP port number on which the server runs - @cpp "IsPrivate" @ce : @m_span{m-label m-default m-flat} bool @m_endspan Whether the server is private and hidden in the server list (default is **false**) - @cpp "AllowAssetStreaming" @ce : @m_span{m-label m-default m-flat} bool @m_endspan Whether clients are allowed to download assets from the server (default is **true**) - @cpp "RequiresDiscordAuth" @ce : @m_span{m-label m-default m-flat} bool @m_endspan If `true`, the server requires Discord authentication (default is **false**) - Discord authentication requires a running Discord client - Supported platforms are Linux, macOS and Windows, players from other platforms won't be able to join - @cpp "AllowedPlayerTypes" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Bitmask for allowed player types (@cpp 1 @ce - Jazz, @cpp 2 @ce - Spaz, @cpp 4 @ce - Lori) - @cpp "IdleKickTimeSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Time in seconds after idle players are kicked (default is **never**) - @cpp "AdminUniquePlayerIDs" @ce : @m_span{m-label m-primary m-flat} object @m_endspan Map of admin player IDs - Key specifies player ID, value contains privileges - @cpp "WhitelistedUniquePlayerIDs" @ce : @m_span{m-label m-primary m-flat} object @m_endspan Map of whitelisted player IDs - Key specifies player ID, value can contain a user-defined comment - If at least one entry is specified, only whitelisted players can join the server - @cpp "BannedUniquePlayerIDs" @ce : @m_span{m-label m-primary m-flat} object @m_endspan Map of banned player IDs - Key specifies player ID, value can contain a user-defined comment (e.g., reason) - @cpp "BannedIPAddresses" @ce : @m_span{m-label m-primary m-flat} object @m_endspan Map of banned IP addresses - Key specifies IP address, value can contain a user-defined comment (e.g., reason) - @cpp "ReforgedGameplay" @ce : @m_span{m-label m-default m-flat} bool @m_endspan Whether reforged gameplay is enabled - Has a higher priority than settings of the player - @cpp "RandomizePlaylist" @ce : @m_span{m-label m-default m-flat} bool @m_endspan Whether to play the playlist in random order - If enabled, the list is shuffled when the server is started and when the end of the list is reached - @cpp "TotalPlayerPoints" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Total points to win the championship (default is **0**) - Player can score a maximum of 20 points per round - @cpp "GameMode" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Game mode - @cpp "b" @ce / @cpp "battle" @ce - Battle - @cpp "tb" @ce / @cpp "teambattle" @ce - Team Battle - @cpp "r" @ce / @cpp "race" @ce - Race - @cpp "tr" @ce / @cpp "teamrace" @ce - Team Race - @cpp "th" @ce / @cpp "treasurehunt" @ce - Treasure Hunt - @cpp "tth" @ce / @cpp "teamtreasurehunt" @ce - Team Treasure Hunt - @cpp "ctf" @ce / @cpp "capturetheflag" @ce - Capture The Flag - @cpp "c" @ce / @cpp "coop" @ce / @cpp "cooperation" @ce - Cooperation - @cpp "Elimination" @ce : @m_span{m-label m-default m-flat} bool @m_endspan Whether elimination mode is enabled - If enabled, a player has a limited number of lives given by @cpp "TotalKills" @ce property - Game ends when only one player remains, or when the conditions of the specified game mode are met - Elimination can be combined with any game mode - @cpp "InitialPlayerHealth" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Initial health of players - If the property is not specified or is less than 1, the player's health is automatically assigned depending on the game mode - Default value for Race and Treasure Hunt is **unlimited**, in all other game modes it's **5** - @cpp "MaxGameTimeSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Maximum allowed game time in seconds per level (default is **unlimited**) - @cpp "PreGameSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Pre-game duration in seconds (default is **60** seconds) - Pre-game is skipped in Cooperation - @cpp "SpawnInvulnerableSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Duration of invulnerability after (re)spawning (default is **4** seconds) - Invulnerability is skipped in Cooperation - @cpp "TotalKills" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Number of kills required to win (Battle) - @cpp "TotalLaps" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Number of laps required to win (Race) - @cpp "TotalTreasureCollected" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Amount of treasure required to win (Treasure Hunt) - @cpp "Playlist" @ce : @m_span{m-label m-success m-flat} array @m_endspan List of game configurations per round, each entry may contain: - @cpp "LevelName" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Name of the level in `/` format - @cpp "GameMode" @ce : @m_span{m-label m-danger m-flat} string @m_endspan Specific game mode for this round - @cpp "Elimination" @ce : @m_span{m-label m-default m-flat} bool @m_endspan Whether elimination mode is enabled for this round - @cpp "InitialPlayerHealth" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Initial health of players for this round - @cpp "MaxGameTimeSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Maximum game duration for this round - @cpp "PreGameSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Pre-game duration before this round starts - @cpp "SpawnInvulnerableSecs" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Duration of invulnerability after (re)spawning - @cpp "TotalKills" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Number of kills required to win this round (Battle) - @cpp "TotalLaps" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Number of laps required to win this round (Race) - @cpp "TotalTreasureCollected" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Amount of treasure required to win this round (Treasure Hunt) - @cpp "PlaylistIndex" @ce : @m_span{m-label m-warning m-flat} integer @m_endspan Index of the current playlist entry If a property is missing in a playlist entry, it will inherit the value from the root configuration. If a property is missing in the root configuration, the default value is used. `{PlayerName}` and `{ServerName}` variables can be used in @cpp "ServerName" @ce and @cpp "WelcomeMessage" @ce properties. Both properties also support @ref Jazz2-UI-Font-format "text formatting" using the @cpp "\f[…]" @ce notation. @subsection Multiplayer-ServerConfiguration-format-example Example server configuration @include ServerConfiguration.json */ struct ServerConfiguration { /** @{ @name Server settings */ /** @brief Server name */ String ServerName; /** @brief Server address override allows to specify an alternate address under which the server will be listed */ String ServerAddressOverride; /** @brief Password of the server */ String ServerPassword; /** @brief Welcome message displayed to players upon joining */ String WelcomeMessage; /** @brief Maximum number of players allowed to join */ std::uint32_t MaxPlayerCount; /** @brief Minimum number of players to start a round */ std::uint32_t MinPlayerCount; /** @brief Game mode */ MpGameMode GameMode; /** @brief Server port */ std::uint16_t ServerPort; /** @brief Whether the server is private (i.e. not visible in the server list) */ bool IsPrivate; /** @brief Whether clients are allowed to automatically download missing assets from the server */ bool AllowAssetStreaming; /** @brief Whether Discord authentication is required to join the server */ bool RequiresDiscordAuth; /** @brief Allowed player types as bitmask of @ref PlayerType */ std::uint8_t AllowedPlayerTypes; /** @brief Time after which inactive players will be kicked, in seconds, -1 to disable */ std::int32_t IdleKickTimeSecs; /** @brief List of unique player IDs with admin rights, value contains list of privileges, or `*` for all privileges */ HashMap AdminUniquePlayerIDs; /** @brief List of whitelisted unique player IDs, value can contain user-defined comment */ HashMap WhitelistedUniquePlayerIDs; /** @brief List of banned unique player IDs, value can contain user-defined reason */ HashMap BannedUniquePlayerIDs; /** @brief List of banned IP addresses, value can contain user-defined reason */ HashMap BannedIPAddresses; /** @} */ /** @{ @name Game-specific settings */ /** @brief Whether reforged gameplay is enabled, see @ref PreferencesCache::EnableReforgedGameplay */ bool ReforgedGameplay; /** @brief Whether to play the playlist in random order */ bool RandomizePlaylist; /** @brief Whether every player has limited number of lives, the game ends when only one player remains */ bool Elimination; /** @brief Total player points to win the championship */ std::uint32_t TotalPlayerPoints; /** @brief Initial player health, default is unlimited for Race and Treasure Hunt, otherwise 5 */ std::int32_t InitialPlayerHealth; /** @brief Maximum number of seconds for a game */ std::uint32_t MaxGameTimeSecs; /** @brief Duration of pre-game before starting a round */ std::uint32_t PreGameSecs; /** @brief Duration of invulnerability after (re)spawning */ std::uint32_t SpawnInvulnerableSecs; /** @brief Total number of kills, default is 10 (Battle) */ std::uint32_t TotalKills; /** @brief Total number of laps, default is 3 (Race) */ std::uint32_t TotalLaps; /** @brief Total number of treasure collected, default is 0 for automatic calculation (Treasure Hunt) */ std::uint32_t TotalTreasureCollected; /** @brief Index of the current playlist entry */ std::int32_t PlaylistIndex; /** @brief Playlist */ SmallVector Playlist; /** @} */ /** @{ @name Pure runtime information */ /** @brief Path to the configuration file */ String FilePath; /** @brief Server unique identifier */ Uuid UniqueServerID; /** @brief Start time of the server as Unix timestamp */ std::uint64_t StartUnixTimestamp; /** @} */ }; /** @brief Server initialization parameters */ struct ServerInitialization { /** @brief Server configuration */ ServerConfiguration Configuration; /** @brief Level initialization parameters */ LevelInitialization InitialLevel; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/PitType.h000066400000000000000000000005371512772601700233070ustar00rootroot00000000000000#pragma once namespace Jazz2 { /** @brief Level pit type */ enum class PitType { FallForever, /**< Player should fall forever out of the level boundaries */ InstantDeathPit, /**< Player should die instantly out of the level boundaries */ StandOnPlatform, /**< Player should stand on platform, not leaving the level boundaries */ }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/PlayerAction.h000066400000000000000000000021361512772601700243000ustar00rootroot00000000000000#pragma once namespace Jazz2 { /** @brief Player action */ enum class PlayerAction { Left, /**< Left */ Right, /**< Right */ Up, /**< Up */ Down, /**< Down */ Buttstomp, /**< Buttstomp (Down in the air) */ Fire, /**< Fire a weapon */ Jump, /**< Jump */ Run, /**< Run */ ChangeWeapon, /**< Change a weapon */ Menu, /**< Menu / Back */ Console, /**< Toggle in-game console */ SwitchToBlaster, /**< Switch to weapon 1 */ SwitchToBouncer, /**< Switch to weapon 2 */ SwitchToFreezer, /**< Switch to weapon 3 */ SwitchToSeeker, /**< Switch to weapon 4 */ SwitchToRF, /**< Switch to weapon 5 */ SwitchToToaster, /**< Switch to weapon 6 */ SwitchToTNT, /**< Switch to weapon 7 */ SwitchToPepper, /**< Switch to weapon 8 */ SwitchToElectro, /**< Switch to weapon 9 */ SwitchToThunderbolt, /**< Switch to weapon 10 */ Count, /**< Number of all actions */ CountInMenu = SwitchToBlaster, /**< Number of actions usable in a menu */ None = -1 /**< No action */ }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/PlayerType.h000066400000000000000000000004011512772601700237750ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Player type */ enum class PlayerType : std::uint8_t { None, /**< None/unspecified */ Jazz, /**< Jazz */ Spaz, /**< Spaz */ Lori, /**< Lori */ Frog /**< Frog */ }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/PreferencesCache.cpp000066400000000000000000001104541512772601700254310ustar00rootroot00000000000000#include "PreferencesCache.h" #include "ContentResolver.h" #include "LevelHandler.h" #include "Input/ControlScheme.h" #include "UI/DiscordRpcClient.h" #include "../nCine/Application.h" #include "../nCine/I18n.h" #include "../nCine/Base/Random.h" #include #include #include #include #include #include #include #if defined(DEATH_TARGET_ANDROID) # include "../nCine/Backends/Android/AndroidApplication.h" # include "../nCine/Backends/Android/AndroidJniHelper.h" #elif defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) # include #endif using namespace Death::Containers::Literals; using namespace Death::IO; using namespace Death::IO::Compression; using namespace nCine; namespace Jazz2 { bool PreferencesCache::FirstRun = false; #if defined(DEATH_TARGET_EMSCRIPTEN) bool PreferencesCache::IsStandalone = false; #endif UnlockableEpisodes PreferencesCache::UnlockedEpisodes = UnlockableEpisodes::None; RescaleMode PreferencesCache::ActiveRescaleMode = RescaleMode::None; bool PreferencesCache::EnableFullscreen = false; std::int32_t PreferencesCache::MaxFps = PreferencesCache::UseVsync; bool PreferencesCache::ShowPerformanceMetrics = false; bool PreferencesCache::KeepAspectRatioInCinematics = false; bool PreferencesCache::ShowPlayerTrails = true; bool PreferencesCache::LowWaterQuality = false; bool PreferencesCache::UnalignedViewport = false; bool PreferencesCache::PreferVerticalSplitscreen = false; bool PreferencesCache::PreferZoomOut = false; bool PreferencesCache::BackgroundDithering = true; bool PreferencesCache::EnableReforgedGameplay = true; bool PreferencesCache::EnableReforgedHUD = true; bool PreferencesCache::EnableReforgedMainMenu = true; #if defined(DEATH_TARGET_ANDROID) // Used to swap Android activity icons on exit/suspend bool PreferencesCache::EnableReforgedMainMenuInitial = true; #endif bool PreferencesCache::EnableContinuousJump = true; bool PreferencesCache::EnableLedgeClimb = true; WeaponWheelStyle PreferencesCache::WeaponWheel = WeaponWheelStyle::Enabled; bool PreferencesCache::SwitchToNewWeapon = true; #if defined(DEATH_TARGET_ANDROID) || defined(DEATH_TARGET_EMSCRIPTEN) || defined(DEATH_TARGET_IOS) || defined(DEATH_TARGET_SWITCH) || defined(DEATH_TARGET_WINDOWS_RT) bool PreferencesCache::EnableRgbLights = false; #else bool PreferencesCache::EnableRgbLights = true; #endif bool PreferencesCache::AllowUnsignedScripts = true; bool PreferencesCache::TutorialCompleted = false; bool PreferencesCache::ResumeOnStart = false; bool PreferencesCache::AllowCheats = false; bool PreferencesCache::AllowCheatsLives = false; bool PreferencesCache::AllowCheatsUnlock = false; EpisodeEndOverwriteMode PreferencesCache::OverwriteEpisodeEnd = EpisodeEndOverwriteMode::Always; char PreferencesCache::Language[6]{}; bool PreferencesCache::BypassCache = false; float PreferencesCache::MasterVolume = 0.7f; float PreferencesCache::SfxVolume = 0.8f; float PreferencesCache::MusicVolume = 0.4f; bool PreferencesCache::ToggleRunAction = false; #if defined(DEATH_TARGET_SWITCH) GamepadType PreferencesCache::GamepadButtonLabels = GamepadType::Switch; #else GamepadType PreferencesCache::GamepadButtonLabels = GamepadType::Xbox; #endif std::uint8_t PreferencesCache::GamepadRumble = 1; bool PreferencesCache::PlayStationExtendedSupport = false; bool PreferencesCache::UseNativeBackButton = false; Vector2f PreferencesCache::TouchLeftPadding; Vector2f PreferencesCache::TouchRightPadding; Uuid PreferencesCache::UniquePlayerID; Uuid PreferencesCache::UniqueServerID; String PreferencesCache::PlayerName; bool PreferencesCache::EnableDiscordIntegration = true; String PreferencesCache::_configPath; HashMap PreferencesCache::_episodeEnd; HashMap PreferencesCache::_episodeContinue; static void ReadEpisodeContinuationState(Stream& s, EpisodeContinuationState& state) { state.Flags = EpisodeContinuationFlags(s.ReadValue()); state.DifficultyAndPlayerType = s.ReadValue(); state.Lives = s.ReadValue(); state.Unused1 = s.ReadValue(); state.Score = s.ReadValueAsLE(); state.Unused2 = s.ReadValueAsLE(); state.ElapsedMilliseconds = s.ReadValueAsLE(); for (std::size_t i = 0; i < arraySize(state.Gems); i++) { state.Gems[i] = s.ReadValueAsLE(); } for (std::size_t i = 0; i < arraySize(state.Ammo); i++) { state.Ammo[i] = s.ReadValueAsLE(); } for (std::size_t i = 0; i < arraySize(state.WeaponUpgrades); i++) { state.WeaponUpgrades[i] = s.ReadValue(); } } static void WriteEpisodeContinuationState(Stream& s, const EpisodeContinuationState& state) { s.WriteValue(std::uint8_t(state.Flags)); s.WriteValue(state.DifficultyAndPlayerType); s.WriteValue(state.Lives); s.WriteValue(state.Unused1); s.WriteValueAsLE(state.Score); s.WriteValueAsLE(state.Unused2); s.WriteValueAsLE(state.ElapsedMilliseconds); for (std::size_t i = 0; i < arraySize(state.Gems); i++) { s.WriteValueAsLE(state.Gems[i]); } for (std::size_t i = 0; i < arraySize(state.Ammo); i++) { s.WriteValueAsLE(state.Ammo[i]); } for (std::size_t i = 0; i < arraySize(state.WeaponUpgrades); i++) { s.WriteValue(state.WeaponUpgrades[i]); } } void PreferencesCache::Initialize(AppConfiguration& config) { bool resetConfig = false; #if defined(DEATH_TARGET_EMSCRIPTEN) auto configDir = "/Persistent"_s; fs::MountAsPersistent(configDir); _configPath = "/Persistent/Jazz2.config"_s; for (int32_t i = 0; i < config.argc(); i++) { auto arg = config.argv(i); if (arg == "/reset-config"_s) { resetConfig = true; } } #else _configPath = "Jazz2.config"_s; bool overrideConfigPath = false; # if !defined(DEATH_TARGET_ANDROID) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) for (std::int32_t i = 0; i < config.argc(); i++) { auto arg = config.argv(i); if (arg == "/config"_s) { if (i + 1 < config.argc()) { _configPath = config.argv(i + 1); overrideConfigPath = true; i++; } } else if (arg == "/reset-config"_s) { resetConfig = true; } } # endif // If config path is not overriden and portable config doesn't exist, use common path for current user if (!overrideConfigPath && !fs::IsReadableFile(_configPath)) { # if defined(DEATH_TARGET_SWITCH) // Save config file next to `Source` directory auto& resolver = ContentResolver::Get(); _configPath = fs::CombinePath(fs::GetDirectoryName(resolver.GetSourcePath()), "Jazz2.config"_s); # elif defined(DEATH_TARGET_UNIX) && defined(NCINE_PACKAGED_CONTENT_PATH) _configPath = fs::CombinePath(fs::GetSavePath(NCINE_LINUX_PACKAGE), "Jazz2.config"_s); # else _configPath = fs::CombinePath(fs::GetSavePath("Jazz² Resurrection"_s), "Jazz2.config"_s); # endif # if defined(DEATH_TARGET_ANDROID) // Save config file to external path if possible auto& resolver = ContentResolver::Get(); auto externalConfigPath = fs::CombinePath(fs::GetDirectoryName(resolver.GetSourcePath()), "Jazz2.config"_s); if (!fs::IsReadableFile(_configPath) || fs::IsReadableFile(externalConfigPath)) { _configPath = externalConfigPath; } # elif defined(DEATH_TARGET_WINDOWS_RT) // Save config file next to `Source` directory (e.g., on external drive) if possible auto& resolver = ContentResolver::Get(); auto localConfigPath = fs::CombinePath(fs::GetDirectoryName(resolver.GetSourcePath()), "Jazz2.config"_s); if (_configPath != localConfigPath) { auto configFileWritable = fs::Open(localConfigPath, FileAccess::ReadWrite); if (configFileWritable->IsValid()) { configFileWritable->Dispose(); _configPath = localConfigPath; } } # endif } auto configDir = fs::GetDirectoryName(_configPath); # if defined(DEATH_TRACE) # if defined(DEATH_TARGET_ANDROID) || defined(DEATH_TARGET_SWITCH) fs::CreateDirectories(configDir); theApplication().AttachTraceTarget(fs::CombinePath(configDir, "Jazz2.log"_s)); # elif defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) || (defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT)) for (std::int32_t i = 0; i < config.argc(); i++) { auto arg = config.argv(i); if (arg == "/log:file"_s || arg.hasPrefix("/log:file:"_s)) { fs::CreateDirectories(configDir); if (arg.size() > "/log:file:"_s.size()) { theApplication().AttachTraceTarget(fs::CombinePath(configDir, arg.exceptPrefix("/log:file:"_s))); } else { theApplication().AttachTraceTarget(fs::CombinePath(configDir, "Jazz2.log"_s)); } } # if defined(DEATH_TARGET_WINDOWS) else if (arg == "/log"_s) { theApplication().AttachTraceTarget(Application::ConsoleTarget); } # endif } # endif # endif #endif ControlScheme::Reset(); // Try to read config file if (!resetConfig) { auto s = fs::Open(_configPath, FileAccess::Read); if (s->GetSize() > 18) { std::uint64_t signature = s->ReadValueAsLE(); std::uint8_t fileType = s->ReadValue(); std::uint8_t version = s->ReadValue(); if (signature == 0x2095A59FF0BFBBEF && fileType == ContentResolver::ConfigFile && version <= FileVersion) { if (version == 1) { // Version 1 included compressedSize and decompressedSize, it's not needed anymore /*std::int32_t compressedSize =*/ s->ReadValue(); /*std::int32_t uncompressedSize =*/ s->ReadValue(); } DeflateStream uc(*s); BoolOptions boolOptions = (BoolOptions)uc.ReadValueAsLE(); #if !defined(DEATH_TARGET_EMSCRIPTEN) EnableFullscreen = ((boolOptions & BoolOptions::EnableFullscreen) == BoolOptions::EnableFullscreen); #endif ShowPerformanceMetrics = ((boolOptions & BoolOptions::ShowPerformanceMetrics) == BoolOptions::ShowPerformanceMetrics); KeepAspectRatioInCinematics = ((boolOptions & BoolOptions::KeepAspectRatioInCinematics) == BoolOptions::KeepAspectRatioInCinematics); ShowPlayerTrails = ((boolOptions & BoolOptions::ShowPlayerTrails) == BoolOptions::ShowPlayerTrails); LowWaterQuality = ((boolOptions & BoolOptions::LowWaterQuality) == BoolOptions::LowWaterQuality); UnalignedViewport = ((boolOptions & BoolOptions::UnalignedViewport) == BoolOptions::UnalignedViewport); PreferVerticalSplitscreen = ((boolOptions & BoolOptions::PreferVerticalSplitscreen) == BoolOptions::PreferVerticalSplitscreen); PreferZoomOut = ((boolOptions & BoolOptions::PreferZoomOut) == BoolOptions::PreferZoomOut); BackgroundDithering = ((boolOptions & BoolOptions::BackgroundDithering) == BoolOptions::BackgroundDithering); EnableReforgedGameplay = ((boolOptions & BoolOptions::EnableReforgedGameplay) == BoolOptions::EnableReforgedGameplay); EnableLedgeClimb = ((boolOptions & BoolOptions::EnableLedgeClimb) == BoolOptions::EnableLedgeClimb); WeaponWheel = ((boolOptions & BoolOptions::EnableWeaponWheel) == BoolOptions::EnableWeaponWheel ? WeaponWheelStyle::Enabled : WeaponWheelStyle::Disabled); EnableRgbLights = ((boolOptions & BoolOptions::EnableRgbLights) == BoolOptions::EnableRgbLights); AllowUnsignedScripts = ((boolOptions & BoolOptions::AllowUnsignedScripts) == BoolOptions::AllowUnsignedScripts); ToggleRunAction = ((boolOptions & BoolOptions::ToggleRunAction) == BoolOptions::ToggleRunAction); UseNativeBackButton = ((boolOptions & BoolOptions::UseNativeBackButton) == BoolOptions::UseNativeBackButton); EnableDiscordIntegration = ((boolOptions & BoolOptions::EnableDiscordIntegration) == BoolOptions::EnableDiscordIntegration); TutorialCompleted = ((boolOptions & BoolOptions::TutorialCompleted) == BoolOptions::TutorialCompleted); ResumeOnStart = ((boolOptions & BoolOptions::ResumeOnStart) == BoolOptions::ResumeOnStart); if (version >= 3) { // These 2 new options needs to be enabled by default EnableReforgedHUD = ((boolOptions & BoolOptions::EnableReforgedHUD) == BoolOptions::EnableReforgedHUD); EnableReforgedMainMenu = ((boolOptions & BoolOptions::EnableReforgedMainMenu) == BoolOptions::EnableReforgedMainMenu); #if defined(DEATH_TARGET_ANDROID) EnableReforgedMainMenuInitial = EnableReforgedMainMenu; #endif } if (WeaponWheel != WeaponWheelStyle::Disabled && (boolOptions & BoolOptions::ShowWeaponWheelAmmoCount) == BoolOptions::ShowWeaponWheelAmmoCount) { WeaponWheel = WeaponWheelStyle::EnabledWithAmmoCount; } if ((boolOptions & BoolOptions::SetLanguage) == BoolOptions::SetLanguage) { uc.Read(Language, sizeof(Language)); } else { std::memset(Language, 0, sizeof(Language)); } // Bitmask of unlocked episodes, used only if compiled with SHAREWARE_DEMO_ONLY UnlockedEpisodes = (UnlockableEpisodes)uc.ReadValueAsLE(); ActiveRescaleMode = (RescaleMode)uc.ReadValue(); MasterVolume = uc.ReadValue() / 255.0f; SfxVolume = uc.ReadValue() / 255.0f; MusicVolume = uc.ReadValue() / 255.0f; TouchLeftPadding.X = std::round(uc.ReadValue() / (TouchPaddingMultiplier * INT8_MAX)); TouchLeftPadding.Y = std::round(uc.ReadValue() / (TouchPaddingMultiplier * INT8_MAX)); TouchRightPadding.X = std::round(uc.ReadValue() / (TouchPaddingMultiplier * INT8_MAX)); TouchRightPadding.Y = std::round(uc.ReadValue() / (TouchPaddingMultiplier * INT8_MAX)); if (version >= 5) { GamepadButtonLabels = (GamepadType)uc.ReadValue(); } if (version >= 6) { GamepadRumble = uc.ReadValue(); } if (version >= 7) { AllowCheats = ((boolOptions & BoolOptions::AllowCheats) == BoolOptions::AllowCheats); PlayStationExtendedSupport = ((boolOptions & BoolOptions::PlayStationExtendedSupport) == BoolOptions::PlayStationExtendedSupport); SwitchToNewWeapon = ((boolOptions & BoolOptions::SwitchToNewWeapon) == BoolOptions::SwitchToNewWeapon); OverwriteEpisodeEnd = (EpisodeEndOverwriteMode)uc.ReadValue(); } if (version >= 12) { EnableContinuousJump = ((boolOptions & BoolOptions::EnableContinuousJump) == BoolOptions::EnableContinuousJump); } if (version >= 10) { uc.Read(UniquePlayerID, sizeof(UniquePlayerID)); std::uint32_t playerNameLength = uc.ReadVariableUint32(); PlayerName = String(NoInit, playerNameLength); uc.Read(PlayerName.data(), playerNameLength); } else { // Generate a new UUID when upgrading from older version Random().Uuid(UniquePlayerID); } if (version >= 11) { uc.Read(UniqueServerID, sizeof(UniqueServerID)); } else { // Generate a new UUID when upgrading from older version Random().Uuid(UniqueServerID); } // Controls if (version >= 4) { auto mappings = ControlScheme::GetAllMappings(); bool shouldResetBecauseOfOldVersion = (version < 9); std::uint32_t playerCount = uc.ReadValue(); std::uint32_t controlMappingCount = uc.ReadValue(); for (std::uint32_t i = 0; i < playerCount; i++) { for (std::uint32_t j = 0; j < controlMappingCount; j++) { std::uint8_t targetCount = uc.ReadValue(); if (!shouldResetBecauseOfOldVersion && i < ControlScheme::MaxSupportedPlayers && j < (std::uint32_t)PlayerAction::Count) { auto& mapping = mappings[i * (std::uint32_t)PlayerAction::Count + j]; mapping.Targets.clear(); for (std::uint32_t k = 0; k < targetCount; k++) { MappingTarget target = { uc.ReadValueAsLE() }; mapping.Targets.push_back(target); } } else { uc.Seek(targetCount * sizeof(std::uint32_t), SeekOrigin::Current); } } } // Reset primary Menu action, because it's hardcoded auto& menuMapping = mappings[(std::uint32_t)PlayerAction::Menu]; if (menuMapping.Targets.empty()) { mappings[(std::int32_t)PlayerAction::Menu].Targets.push_back(ControlScheme::CreateTarget(Keys::Escape)); } } else { // Skip old control mapping definitions std::uint8_t controlMappingCount = uc.ReadValue(); uc.Seek(controlMappingCount * sizeof(std::uint32_t), SeekOrigin::Current); } // Episode End std::uint16_t episodeEndSize = uc.ReadValueAsLE(); std::uint16_t episodeEndCount = uc.ReadValueAsLE(); for (std::uint32_t i = 0; i < episodeEndCount; i++) { std::uint8_t nameLength = uc.ReadValue(); String episodeName{NoInit, nameLength}; uc.Read(episodeName.data(), nameLength); EpisodeContinuationState state = {}; if (episodeEndSize == sizeof(EpisodeContinuationState)) { ReadEpisodeContinuationState(uc, state); } else { // Struct has different size, so it's better to skip it uc.Seek(episodeEndSize, SeekOrigin::Current); state.Flags = EpisodeContinuationFlags::IsCompleted; } _episodeEnd.emplace(std::move(episodeName), std::move(state)); } // Episode Continue std::uint16_t episodeContinueSize = uc.ReadValueAsLE(); std::uint16_t episodeContinueCount = uc.ReadValueAsLE(); for (std::uint32_t i = 0; i < episodeContinueCount; i++) { std::uint8_t nameLength = uc.ReadValue(); String episodeName{NoInit, nameLength}; uc.Read(episodeName.data(), nameLength); if (episodeContinueSize == sizeof(EpisodeContinuationState)) { EpisodeContinuationStateWithLevel stateWithLevel = {}; nameLength = uc.ReadValue(); stateWithLevel.LevelName = String(NoInit, nameLength); uc.Read(stateWithLevel.LevelName.data(), nameLength); ReadEpisodeContinuationState(uc, stateWithLevel.State); _episodeContinue.emplace(std::move(episodeName), std::move(stateWithLevel)); } else { // Struct has different size, so it's better to skip it nameLength = uc.ReadValue(); uc.Seek(nameLength + episodeContinueSize, SeekOrigin::Current); } } } else { // The file is too new or corrupted resetConfig = true; } } else { // The file doesn't exist resetConfig = true; } } if (resetConfig) { // Config file doesn't exist or reset is requested FirstRun = true; Random().Uuid(UniquePlayerID); Random().Uuid(UniqueServerID); PlayerName = GetEffectivePlayerName(); TryLoadPreferredLanguage(); fs::CreateDirectories(configDir); #if !defined(DEATH_TARGET_EMSCRIPTEN) // Create "Source" directory on the first launch auto& resolver = ContentResolver::Get(); fs::CreateDirectories(resolver.GetSourcePath()); # if defined(DEATH_TARGET_ANDROID) // Use native Back button as default on smart watches UseNativeBackButton = static_cast(theApplication()).IsScreenRound(); # elif defined(DEATH_TARGET_UNIX) StringView isSteamDeck = ::getenv("SteamDeck"); if (isSteamDeck == "1"_s) { GamepadButtonLabels = GamepadType::Steam; } # elif defined(DEATH_TARGET_WINDOWS) wchar_t envSteamDeck[2] = {}; DWORD envLength = ::GetEnvironmentVariable(L"SteamDeck", envSteamDeck, 2); if (envLength == 1 && envSteamDeck[0] == L'1') { GamepadButtonLabels = GamepadType::Steam; } # endif #endif } #if !defined(DEATH_TARGET_ANDROID) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) // Override some settings by command-line arguments for (std::int32_t i = 0; i < config.argc(); i++) { auto arg = config.argv(i); if (arg == "/bypass-cache"_s) { BypassCache = true; } else if (arg == "/cheats"_s) { AllowCheats = true; } else if (arg == "/cheats-lives"_s) { AllowCheatsLives = true; } else if (arg == "/cheats-unlock"_s) { AllowCheatsUnlock = true; } else if (arg == "/fullscreen"_s) { EnableFullscreen = true; } else if (arg == "/windowed"_s) { EnableFullscreen = false; } else if (arg == "/no-vsync"_s) { // V-Sync can be turned off only with command-line parameter if (MaxFps == UseVsync) { MaxFps = UnlimitedFps; } } else if (arg.hasPrefix("/max-fps:"_s)) { // Max. FPS can be set only with command-line parameter char* end; unsigned long paramValue = strtoul(arg.exceptPrefix("/max-fps:"_s).data(), &end, 10); if (paramValue > 0) { MaxFps = std::max(paramValue, 30ul); } } # if !defined(DEATH_TARGET_EMSCRIPTEN) else if (arg == "/gpu-workaround"_s) { if (i + 1 < config.argc() && config.argv(i + 1) == "fixed-batch-size"_s) { config.fixedBatchSize = 10; } } # endif else if (arg == "/no-rgb"_s) { EnableRgbLights = false; } else if (arg == "/no-rescale"_s) { ActiveRescaleMode = RescaleMode::None; } else if (arg == "/mute"_s) { MasterVolume = 0.0f; } else if (arg == "/reset-controls"_s) { ControlScheme::Reset(); } # if defined(DEATH_TARGET_EMSCRIPTEN) else if (arg == "/standalone"_s) { IsStandalone = true; } # endif } #endif } void PreferencesCache::Save() { // `FirstRun` is true only if config file doesn't exist yet FirstRun = false; fs::CreateDirectories(fs::GetDirectoryName(_configPath)); auto so = fs::Open(_configPath, FileAccess::Write); if (!so->IsValid()) { return; } so->WriteValueAsLE(0x2095A59FF0BFBBEF); so->WriteValue(ContentResolver::ConfigFile); so->WriteValue(FileVersion); DeflateWriter co(*so); BoolOptions boolOptions = BoolOptions::None; if (EnableFullscreen) boolOptions |= BoolOptions::EnableFullscreen; if (ShowPerformanceMetrics) boolOptions |= BoolOptions::ShowPerformanceMetrics; if (KeepAspectRatioInCinematics) boolOptions |= BoolOptions::KeepAspectRatioInCinematics; if (ShowPlayerTrails) boolOptions |= BoolOptions::ShowPlayerTrails; if (LowWaterQuality) boolOptions |= BoolOptions::LowWaterQuality; if (UnalignedViewport) boolOptions |= BoolOptions::UnalignedViewport; if (PreferVerticalSplitscreen) boolOptions |= BoolOptions::PreferVerticalSplitscreen; if (PreferZoomOut) boolOptions |= BoolOptions::PreferZoomOut; if (BackgroundDithering) boolOptions |= BoolOptions::BackgroundDithering; if (EnableReforgedGameplay) boolOptions |= BoolOptions::EnableReforgedGameplay; if (EnableLedgeClimb) boolOptions |= BoolOptions::EnableLedgeClimb; if (WeaponWheel != WeaponWheelStyle::Disabled) boolOptions |= BoolOptions::EnableWeaponWheel; if (WeaponWheel == WeaponWheelStyle::EnabledWithAmmoCount) boolOptions |= BoolOptions::ShowWeaponWheelAmmoCount; if (EnableRgbLights) boolOptions |= BoolOptions::EnableRgbLights; if (AllowUnsignedScripts) boolOptions |= BoolOptions::AllowUnsignedScripts; if (ToggleRunAction) boolOptions |= BoolOptions::ToggleRunAction; if (UseNativeBackButton) boolOptions |= BoolOptions::UseNativeBackButton; if (EnableDiscordIntegration) boolOptions |= BoolOptions::EnableDiscordIntegration; if (TutorialCompleted) boolOptions |= BoolOptions::TutorialCompleted; if (Language[0] != '\0') boolOptions |= BoolOptions::SetLanguage; if (ResumeOnStart) boolOptions |= BoolOptions::ResumeOnStart; if (EnableReforgedHUD) boolOptions |= BoolOptions::EnableReforgedHUD; if (EnableReforgedMainMenu) boolOptions |= BoolOptions::EnableReforgedMainMenu; if (AllowCheats) boolOptions |= BoolOptions::AllowCheats; if (PlayStationExtendedSupport) boolOptions |= BoolOptions::PlayStationExtendedSupport; if (SwitchToNewWeapon) boolOptions |= BoolOptions::SwitchToNewWeapon; co.WriteValueAsLE(std::uint64_t(boolOptions)); if (Language[0] != '\0') { co.Write(Language, sizeof(Language)); } // Bitmask of unlocked episodes, used only if compiled with SHAREWARE_DEMO_ONLY co.WriteValueAsLE(std::uint32_t(UnlockedEpisodes)); co.WriteValue(std::uint8_t(ActiveRescaleMode)); co.WriteValue(std::uint8_t(MasterVolume * 255.0f)); co.WriteValue(std::uint8_t(SfxVolume * 255.0f)); co.WriteValue(std::uint8_t(MusicVolume * 255.0f)); co.WriteValue(std::int8_t(TouchLeftPadding.X * INT8_MAX * TouchPaddingMultiplier)); co.WriteValue(std::int8_t(TouchLeftPadding.Y * INT8_MAX * TouchPaddingMultiplier)); co.WriteValue(std::int8_t(TouchRightPadding.X * INT8_MAX * TouchPaddingMultiplier)); co.WriteValue(std::int8_t(TouchRightPadding.Y * INT8_MAX * TouchPaddingMultiplier)); co.WriteValue(std::uint8_t(GamepadButtonLabels)); co.WriteValue(GamepadRumble); co.WriteValue(std::uint8_t(OverwriteEpisodeEnd)); co.Write(UniquePlayerID, sizeof(UniquePlayerID)); co.WriteVariableUint32(std::uint32_t(PlayerName.size())); co.Write(PlayerName.data(), std::int64_t(PlayerName.size())); co.Write(UniqueServerID, sizeof(UniqueServerID)); // Controls co.WriteValue(std::uint8_t(ControlScheme::MaxSupportedPlayers)); co.WriteValue(std::uint8_t(PlayerAction::Count)); for (std::int32_t i = 0; i < ControlScheme::MaxSupportedPlayers; i++) { auto mappings = ControlScheme::GetMappings(i); for (std::uint32_t j = 0; j < mappings.size(); j++) { const auto& mapping = mappings[j]; std::uint8_t targetCount = (std::uint8_t)mapping.Targets.size(); co.WriteValue(targetCount); for (std::uint32_t k = 0; k < targetCount; k++) { co.WriteValueAsLE(mapping.Targets[k].Data); } } } // Episode End co.WriteValueAsLE(sizeof(EpisodeContinuationState)); co.WriteValueAsLE(std::uint16_t(_episodeEnd.size())); for (auto& pair : _episodeEnd) { co.WriteValue((std::uint8_t)pair.first.size()); co.Write(pair.first.data(), (std::int64_t)pair.first.size()); WriteEpisodeContinuationState(co, pair.second); } // Episode Continue co.WriteValueAsLE(sizeof(EpisodeContinuationState)); co.WriteValueAsLE(std::uint16_t(_episodeContinue.size())); for (auto& pair : _episodeContinue) { co.WriteValue((std::uint8_t)pair.first.size()); co.Write(pair.first.data(), (std::int64_t)pair.first.size()); co.WriteValue((std::uint8_t)pair.second.LevelName.size()); co.Write(pair.second.LevelName.data(), (std::int64_t)pair.second.LevelName.size()); WriteEpisodeContinuationState(co, pair.second.State); } co.Dispose(); so->Dispose(); #if defined(DEATH_TARGET_EMSCRIPTEN) fs::SyncToPersistent(); #endif } StringView PreferencesCache::GetDirectory() { return fs::GetDirectoryName(_configPath); } String PreferencesCache::GetDeviceID() { #if defined(DEATH_TARGET_X86) std::int32_t arch = 1; Cpu::Features cpuFeatures = Cpu::runtimeFeatures(); if (cpuFeatures & Cpu::Avx) { arch |= 0x400; } if (cpuFeatures & Cpu::Avx2) { arch |= 0x800; } if (cpuFeatures & Cpu::Avx512f) { arch |= 0x1000; } #elif defined(DEATH_TARGET_ARM) std::int32_t arch = 2; Cpu::Features cpuFeatures = Cpu::runtimeFeatures(); if (cpuFeatures & Cpu::Neon) { arch |= 0x2000; } #elif defined(DEATH_TARGET_POWERPC) std::int32_t arch = 3; #elif defined(DEATH_TARGET_RISCV) std::int32_t arch = 5; #elif defined(DEATH_TARGET_WASM) std::int32_t arch = 4; Cpu::Features cpuFeatures = Cpu::runtimeFeatures(); if (cpuFeatures & Cpu::Simd128) { arch |= 0x4000; } #else std::int32_t arch = 0; #endif #if defined(DEATH_TARGET_32BIT) arch |= 0x100; #endif #if defined(DEATH_TARGET_BIG_ENDIAN) arch |= 0x200; #endif #if defined(DEATH_TARGET_CYGWIN) arch |= 0x200000; #endif #if defined(DEATH_TARGET_MINGW) arch |= 0x400000; #endif #if defined(DEATH_TARGET_ANDROID) auto sanitizeName = [](char* dst, std::size_t dstMaxLength, std::size_t& dstLength, StringView name, bool isBrand) { bool wasSpace = true; std::size_t lowercaseLength = 0; if (isBrand) { for (char c : name) { if (c == '\0' || c == ' ') { break; } lowercaseLength++; } if (lowercaseLength < 5 || name[0] < 'A' || name[0] > 'Z' || name[lowercaseLength - 1] < 'A' || name[lowercaseLength - 1] > 'Z') { lowercaseLength = 0; } } for (char c : name) { if (c == '\0' || dstLength >= dstMaxLength) { break; } if (isalnum(c) || c == ' ' || c == '.' || c == ',' || c == ':' || c == '_' || c == '-' || c == '+' || c == '/' || c == '*' || c == '!' || c == '(' || c == ')' || c == '[' || c == ']' || c == '@' || c == '&' || c == '#' || c == '\'' || c == '"') { if (wasSpace && c >= 'a' && c <= 'z') { c &= ~0x20; if (lowercaseLength > 0) { lowercaseLength--; } } else if (lowercaseLength > 0) { if (c >= 'A' && c <= 'Z') { c |= 0x20; } lowercaseLength--; } dst[dstLength++] = c; } wasSpace = (c == ' '); } }; auto sdkVersion = Backends::AndroidJniHelper::SdkVersion(); auto androidId = Backends::AndroidJniWrap_Secure::getAndroidId(); auto deviceBrand = Backends::AndroidJniClass_Version::deviceBrand(); auto deviceModel = Backends::AndroidJniClass_Version::deviceModel(); char deviceName[64]; std::size_t deviceNameLength = 0; if (deviceModel.empty()) { sanitizeName(deviceName, arraySize(deviceName) - 1, deviceNameLength, deviceBrand, false); } else if (deviceModel.hasPrefix(deviceBrand)) { sanitizeName(deviceName, arraySize(deviceName) - 1, deviceNameLength, deviceModel, true); } else { if (!deviceBrand.empty()) { sanitizeName(deviceName, arraySize(deviceName) - 8, deviceNameLength, deviceBrand, true); deviceName[deviceNameLength++] = ' '; } sanitizeName(deviceName, arraySize(deviceName) - 1, deviceNameLength, deviceModel, false); } deviceName[deviceNameLength] = '\0'; char DeviceDesc[128]; std::int32_t DeviceDescLength = formatInto(DeviceDesc, "{}|Android {}|{}|2|{}", androidId, sdkVersion, deviceName, arch); #elif defined(DEATH_TARGET_APPLE) char DeviceDesc[256] {}; std::int32_t DeviceDescLength; if (::gethostname(DeviceDesc, arraySize(DeviceDesc)) == 0) { DeviceDesc[arraySize(DeviceDesc) - 1] = '\0'; DeviceDescLength = std::strlen(DeviceDesc); } else { DeviceDescLength = 0; } String appleVersion = Environment::GetAppleVersion(); DeviceDescLength += formatInto({ DeviceDesc + DeviceDescLength, arraySize(DeviceDesc) - DeviceDescLength }, "|macOS {}||5|{}", appleVersion, arch); #elif defined(DEATH_TARGET_SWITCH) std::uint32_t switchVersion = Environment::GetSwitchVersion(); bool isAtmosphere = Environment::HasSwitchAtmosphere(); char DeviceDesc[128]; std::int32_t DeviceDescLength = formatInto(DeviceDesc, "|Nintendo Switch {}.{}.{}{}||9|{}", ((switchVersion >> 16) & 0xFF), ((switchVersion >> 8) & 0xFF), (switchVersion & 0xFF), isAtmosphere ? " (Atmosphère)"_s : ""_s, arch); #elif defined(DEATH_TARGET_UNIX) # if defined(DEATH_TARGET_CLANG) arch |= 0x100000; # endif char DeviceDesc[256] {}; std::int32_t DeviceDescLength; if (::gethostname(DeviceDesc, arraySize(DeviceDesc)) == 0) { DeviceDesc[arraySize(DeviceDesc) - 1] = '\0'; DeviceDescLength = std::strlen(DeviceDesc); } else { DeviceDescLength = 0; } String unixFlavor = Environment::GetUnixFlavor(); DeviceDescLength += formatInto({ DeviceDesc + DeviceDescLength, arraySize(DeviceDesc) - DeviceDescLength }, "|{}||4|{}", unixFlavor.empty() ? "Unix"_s : StringView(unixFlavor), arch); #elif defined(DEATH_TARGET_WINDOWS) || defined(DEATH_TARGET_WINDOWS_RT) # if defined(DEATH_TARGET_CLANG) arch |= 0x100000; # endif auto osVersion = Environment::WindowsVersion; wchar_t deviceNameW[128]; DWORD DeviceDescLength = DWORD(arraySize(deviceNameW)); if (!::GetComputerNameW(deviceNameW, &DeviceDescLength)) { DeviceDescLength = 0; } char DeviceDesc[256]; DeviceDescLength = Utf8::FromUtf16(DeviceDesc, deviceNameW, DeviceDescLength); # if defined(DEATH_TARGET_WINDOWS_RT) const char* deviceType; switch (Environment::CurrentDeviceType) { case DeviceType::Desktop: deviceType = "Desktop"; break; case DeviceType::Mobile: deviceType = "Mobile"; break; case DeviceType::Iot: deviceType = "Iot"; break; case DeviceType::Xbox: deviceType = "Xbox"; break; default: deviceType = "Unknown"; break; } DeviceDescLength += DWORD(formatInto(MutableStringView(DeviceDesc + DeviceDescLength, arraySize(DeviceDesc) - DeviceDescLength), "|Windows {}.{}.{} ({})||7|{}", std::int32_t((osVersion >> 48) & 0xffffu), std::int32_t((osVersion >> 32) & 0xffffu), std::int32_t(osVersion & 0xffffffffu), deviceType, arch)); # else bool isWine = Environment::IsWine(); DeviceDescLength += DWORD(formatInto({ DeviceDesc + DeviceDescLength, arraySize(DeviceDesc) - DeviceDescLength }, isWine ? "|Windows {}.{}.{} (Wine)||3|{}" : "|Windows {}.{}.{}||3|{}", std::int32_t((osVersion >> 48) & 0xffffu), std::int32_t((osVersion >> 32) & 0xffffu), std::int32_t(osVersion & 0xffffffffu), arch)); # endif #else static const char DeviceDesc[] = "||||"; std::int32_t DeviceDescLength = sizeof(DeviceDesc) - 1; #endif return toBase64Url(DeviceDesc, DeviceDesc + DeviceDescLength); } String PreferencesCache::GetEffectivePlayerName() { // Discord display name has the highest priority, then the player name set in the preferences, and finally the system username String playerName; #if (defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT)) || defined(DEATH_TARGET_UNIX) if (PreferencesCache::EnableDiscordIntegration && UI::DiscordRpcClient::Get().IsSupported()) { playerName = UI::DiscordRpcClient::Get().GetUserDisplayName(); } #endif if (playerName.empty()) { playerName = PreferencesCache::PlayerName; if (playerName.empty()) { playerName = theApplication().GetUserName(); } } return playerName; } EpisodeContinuationState* PreferencesCache::GetEpisodeEnd(StringView episodeName, bool createIfNotFound) { auto it = _episodeEnd.find(String::nullTerminatedView(episodeName)); if (it == _episodeEnd.end()) { if (createIfNotFound) { return &_episodeEnd.emplace(String(episodeName), EpisodeContinuationState()).first->second; } else { return nullptr; } } return &it->second; } EpisodeContinuationStateWithLevel* PreferencesCache::GetEpisodeContinue(StringView episodeName, bool createIfNotFound) { auto it = _episodeContinue.find(String::nullTerminatedView(episodeName)); if (it == _episodeContinue.end()) { if (createIfNotFound) { return &_episodeContinue.emplace(String(episodeName), EpisodeContinuationStateWithLevel()).first->second; } else { return nullptr; } } return &it->second; } void PreferencesCache::RemoveEpisodeContinue(StringView episodeName) { if (episodeName.empty() || episodeName == "unknown"_s) { return; } _episodeContinue.erase(String::nullTerminatedView(episodeName)); } void PreferencesCache::TryLoadPreferredLanguage() { auto& i18n = I18n::Get(); auto& resolver = ContentResolver::Get(); Array languages = I18n::GetPreferredLanguages(); for (String language : languages) { if (!language.empty() && language.size() < sizeof(PreferencesCache::Language)) { if (language == "en"_s) { break; } if (i18n.LoadFromFile(fs::CombinePath({ resolver.GetCachePath(), "Translations"_s, String(language + ".mo"_s) })) || i18n.LoadFromFile(fs::CombinePath({ resolver.GetContentPath(), "Translations"_s, String(language + ".mo"_s) }))) { std::memcpy(PreferencesCache::Language, language.data(), language.size()); std::memset(PreferencesCache::Language + language.size(), '\0', sizeof(PreferencesCache::Language) - language.size()); break; } } StringView baseLanguage = I18n::TryRemoveLanguageSpecifiers(language); if (baseLanguage != language && !baseLanguage.empty() && baseLanguage.size() < sizeof(PreferencesCache::Language)) { if (baseLanguage == "en"_s) { break; } if (i18n.LoadFromFile(fs::CombinePath({ resolver.GetCachePath(), "Translations"_s, String(baseLanguage + ".mo"_s) })) || i18n.LoadFromFile(fs::CombinePath({ resolver.GetContentPath(), "Translations"_s, String(baseLanguage + ".mo"_s) }))) { std::memcpy(PreferencesCache::Language, baseLanguage.data(), baseLanguage.size()); std::memset(PreferencesCache::Language + baseLanguage.size(), '\0', sizeof(PreferencesCache::Language) - baseLanguage.size()); break; } } } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/PreferencesCache.h000066400000000000000000000244531512772601700251010ustar00rootroot00000000000000#pragma once #include "../Main.h" #include "WeaponType.h" #include "../nCine/AppConfiguration.h" #include "../nCine/Base/HashMap.h" #include using namespace Death::Containers; using namespace nCine; namespace Jazz2 { /** @brief Universally unique identifier (16 bytes) */ using Uuid = StaticArray<16, std::uint8_t>; /** @brief Rescale mode */ enum class RescaleMode { None, /**< None/Pixel-perfect */ HQ2x, /**< HQ2× */ _3xBrz, /**< 3×BRZ */ CrtScanlines, /**< CRT Scanlines */ CrtShadowMask, /**< CRT Shadow Mask */ CrtApertureGrille, /**< CRT Aperture Grille */ Monochrome, /**< Monochrome */ Sabr, /**< SABR */ CleanEdge, /**< CleanEdge */ Count, /**< Count of rescale modes */ TypeMask = 0x0f, UseAntialiasing = 0x80 }; DEATH_ENUM_FLAGS(RescaleMode); /** @brief Weapon wheel style */ enum class WeaponWheelStyle : std::uint8_t { Disabled, /**< Disabled */ Enabled, /**< Enabled */ EnabledWithAmmoCount /**< Enabled with Ammo count */ }; /** @brief Gamepad button labels */ enum class GamepadType : std::uint8_t { Xbox, /**< Xbox */ PlayStation, /**< PlayStation */ Steam, /**< Steam */ Switch /**< Switch */ }; /** @brief Episode completion overwrite mode */ enum class EpisodeEndOverwriteMode : std::uint8_t { Always, /**< Always */ NoCheatsOnly, /**< No cheats only */ HigherScoreOnly /**< Higher score only */ }; /** @brief Unlockable episodes, mainly used if compiled with `SHAREWARE_DEMO_ONLY` */ enum class UnlockableEpisodes : std::uint32_t { None = 0x00, FormerlyAPrince = 0x01, JazzInTime = 0x02, Flashback = 0x04, FunkyMonkeys = 0x08, ChristmasChronicles = 0x10, TheSecretFiles = 0x20, }; DEATH_ENUM_FLAGS(UnlockableEpisodes); /** @brief Episode continuation flags, supports a bitwise combination of its member values */ enum class EpisodeContinuationFlags : std::uint8_t { None = 0x00, /**< None */ IsCompleted = 0x01, /**< Episode is complete */ CheatsUsed = 0x02 /**< Cheats have been used */ }; DEATH_ENUM_FLAGS(EpisodeContinuationFlags); # pragma pack(push, 1) /** @brief Continuation state between two episodes */ // These structures are aligned manually, because they are serialized and it should work cross-platform struct EpisodeContinuationState { /** @brief Flags */ EpisodeContinuationFlags Flags; /** @brief Difficulty and player type */ std::uint8_t DifficultyAndPlayerType; /** @brief Lives */ std::uint8_t Lives; std::uint8_t Unused1; /** @brief Score */ std::int32_t Score; std::uint16_t Unused2; /** @brief Elapsed game time in milliseconds */ std::uint64_t ElapsedMilliseconds; /** @brief Gems collected */ std::int32_t Gems[4]; /** @brief Weapon ammo */ StaticArray<(std::int32_t)WeaponType::Count, std::uint16_t> Ammo; /** @brief Weapon upgrades */ StaticArray<(std::int32_t)WeaponType::Count, std::uint8_t> WeaponUpgrades; }; /** @brief Continuation state between two levels in episode */ struct EpisodeContinuationStateWithLevel { /** @brief Base continuation state */ EpisodeContinuationState State; /** @brief Last level name */ String LevelName; }; # pragma pack(pop) /** @brief Provides access to a user preferences */ class PreferencesCache { public: /** @{ @name Constants */ /** @brief Value of @ref MaxFps that specifies unlimited frame rate */ static constexpr std::int32_t UnlimitedFps = 0; /** @brief Value of @ref MaxFps that specifies the frame rate of the monitor being used */ static constexpr std::int32_t UseVsync = -1; /** @} */ /** @brief Whether the application is running for the first time */ static bool FirstRun; #if defined(DEATH_TARGET_EMSCRIPTEN) || defined(DOXYGEN_GENERATING_OUTPUT) /** * @brief Whether the application is running as progressive web app (PWA) * * @partialsupport Available only on @ref DEATH_TARGET_EMSCRIPTEN "Emscripten" platform. */ static bool IsStandalone; #endif /** @brief Currently unlocked episodes if compiled with `SHAREWARE_DEMO_ONLY` */ static UnlockableEpisodes UnlockedEpisodes; // Graphics /** @brief Active rescale mode */ static RescaleMode ActiveRescaleMode; /** @brief Whether the application is running in fullscreen */ static bool EnableFullscreen; /** @brief Maximum frace rate */ static std::int32_t MaxFps; /** @brief Whether performance metrics (FPS counter) are visible */ static bool ShowPerformanceMetrics; /** @brief Whether cinematics should keep original aspect ratio */ static bool KeepAspectRatioInCinematics; /** @brief Whether player trails are visible */ static bool ShowPlayerTrails; /** @brief Whether low quality water effects are enabled */ static bool LowWaterQuality; /** @brief Whether viewport should be unaligned */ static bool UnalignedViewport; /** @brief Whether vertical splitscreen is preferred */ static bool PreferVerticalSplitscreen; /** @brief Whether viewport zoom out is preferred */ static bool PreferZoomOut; /** @brief Whether background dithering should be used */ static bool BackgroundDithering; // Gameplay /** @brief Whether reforged gameplay is enabled */ static bool EnableReforgedGameplay; /** @brief Whether reforged HUD is enabled */ static bool EnableReforgedHUD; /** @brief Whether reforged main menu is enabled */ static bool EnableReforgedMainMenu; #if defined(DEATH_TARGET_ANDROID) // Used to swap Android activity icons on exit/suspend static bool EnableReforgedMainMenuInitial; #endif /** @brief Whether continuous jump is enabled */ static bool EnableContinuousJump; /** @brief Whether ledge climbing is enabled */ static bool EnableLedgeClimb; /** @brief Current weapon wheel style */ static WeaponWheelStyle WeaponWheel; /** @brief Whether a newly acquired weapon is automatically selected */ static bool SwitchToNewWeapon; /** @brief Whether RGB light effects are enabled */ static bool EnableRgbLights; /** @brief Whether unsigned scripts can be loaded */ static bool AllowUnsignedScripts; /** @brief Whether tutorial is completed */ static bool TutorialCompleted; /** @brief Whether the last state should be resumed on start */ static bool ResumeOnStart; /** @brief Whether cheats are enabled by user */ static bool AllowCheats; /** @brief Whether unlimited lives are enabled */ static bool AllowCheatsLives; /** @brief Whether all episodes are unlocked */ static bool AllowCheatsUnlock; /** @brief Whether the last progress is overwritten on the end of episode */ static EpisodeEndOverwriteMode OverwriteEpisodeEnd; /** @brief Current language */ static char Language[6]; /** @brief Whether the cache should be bypassed */ static bool BypassCache; // Sounds /** @brief Master sound volume */ static float MasterVolume; /** @brief SFX volume */ static float SfxVolume; /** @brief Music volume */ static float MusicVolume; // Controls /** @brief Whether toggle Run action is enabled */ static bool ToggleRunAction; /** @brief Active gamepad button labels */ static GamepadType GamepadButtonLabels; /** @brief Gamepad rumble intensity */ static std::uint8_t GamepadRumble; /** @brief Whether PlayStation controller extended support is enabled */ static bool PlayStationExtendedSupport; /** * @brief Whether native Back button should be used * * @partialsupport Available only on @ref DEATH_TARGET_ANDROID "Android" platform. */ static bool UseNativeBackButton; /** @brief Touch controls left padding */ static Vector2f TouchLeftPadding; /** @brief Touch controls right padding */ static Vector2f TouchRightPadding; // User Profile /** @brief Unique player ID */ static Uuid UniquePlayerID; /** @brief Unique server ID */ static Uuid UniqueServerID; /** @brief Player display name */ static String PlayerName; /** @brief Whether Discord integration is enabled */ static bool EnableDiscordIntegration; /** @brief Initializes preferences cache from a given application configuration */ static void Initialize(AppConfiguration& config); /** @brief Serializes current preferences to file */ static void Save(); /** @brief Returns directory path of the preferences file */ static StringView GetDirectory(); /** @brief Returns device ID of the device currently running this application */ static String GetDeviceID(); /** @brief Returns effective player name */ static String GetEffectivePlayerName(); /** @brief Returns information about episode completion */ static EpisodeContinuationState* GetEpisodeEnd(StringView episodeName, bool createIfNotFound = false); /** @brief Returns information about episode continuation */ static EpisodeContinuationStateWithLevel* GetEpisodeContinue(StringView episodeName, bool createIfNotFound = false); /** @brief Removes information about episode continuation (resets progress) */ static void RemoveEpisodeContinue(StringView episodeName); private: enum class BoolOptions : uint64_t { None = 0x00, EnableFullscreen = 0x01, ShowPerformanceMetrics = 0x02, KeepAspectRatioInCinematics = 0x04, ShowPlayerTrails = 0x08, LowWaterQuality = 0x10, UnalignedViewport = 0x20, PreferVerticalSplitscreen = 0x40, PreferZoomOut = 0x80, EnableReforgedGameplay = 0x100, EnableLedgeClimb = 0x200, EnableWeaponWheel = 0x400, EnableRgbLights = 0x800, AllowUnsignedScripts = 0x1000, UseNativeBackButton = 0x2000, EnableDiscordIntegration = 0x4000, ShowWeaponWheelAmmoCount = 0x8000, TutorialCompleted = 0x10000, SetLanguage = 0x20000, ResumeOnStart = 0x40000, EnableReforgedHUD = 0x100000, EnableReforgedMainMenu = 0x200000, ToggleRunAction = 0x400000, AllowCheats = 0x1000000, BackgroundDithering = 0x2000000, PlayStationExtendedSupport = 0x4000000, SwitchToNewWeapon = 0x8000000, EnableContinuousJump = 0x10000000 }; DEATH_PRIVATE_ENUM_FLAGS(BoolOptions); static constexpr std::uint8_t FileVersion = 12; static constexpr float TouchPaddingMultiplier = 0.003f; PreferencesCache(const PreferencesCache&) = delete; PreferencesCache& operator=(const PreferencesCache&) = delete; static String _configPath; static HashMap _episodeEnd; static HashMap _episodeContinue; static void TryLoadPreferredLanguage(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/000077500000000000000000000000001512772601700234505ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/BlurRenderPass.cpp000066400000000000000000000053471512772601700270600ustar00rootroot00000000000000#include "BlurRenderPass.h" #include "PlayerViewport.h" #include "../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Rendering { void BlurRenderPass::Initialize(Texture* source, std::int32_t width, std::int32_t height, Vector2f direction) { _source = source; _downsampleOnly = (direction.X <= std::numeric_limits::epsilon() && direction.Y <= std::numeric_limits::epsilon()); _direction = direction; bool notInitialized = (_view == nullptr); if (notInitialized) { _camera = std::make_unique(); } _camera->SetOrthoProjection(0.0f, (float)width, (float)height, 0.0f); _camera->SetView(0.0f, 0.0f, 0.0f, 1.0f); if (notInitialized) { _target = std::make_unique(nullptr, Texture::Format::RGB8, width, height); _target->SetWrap(SamplerWrapping::ClampToEdge); _view = std::make_unique(_target.get(), Viewport::DepthStencilFormat::None); _view->SetRootNode(this); _view->SetCamera(_camera.get()); //_view->setClearMode(Viewport::ClearMode::Never); } else { _view->RemoveAllTextures(); _target->Init(nullptr, Texture::Format::RGB8, width, height); _view->SetTexture(_target.get()); } _target->SetMagFiltering(SamplerFilter::Linear); Shader* shader = _downsampleOnly ? _owner->_levelHandler->_downsampleShader : _owner->_levelHandler->_blurShader; // Prepare render command _renderCommand.GetMaterial().SetShader(shader); //_renderCommand.GetMaterial().SetBlendingEnabled(true); _renderCommand.GetMaterial().ReserveUniformsDataMemory(); _renderCommand.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = _renderCommand.GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } void BlurRenderPass::Register() { Viewport::GetChain().push_back(_view.get()); } bool BlurRenderPass::OnDraw(RenderQueue& renderQueue) { Vector2i size = _target->GetSize(); auto* instanceBlock = _renderCommand.GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, 1.0f, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(static_cast(size.X), static_cast(size.Y)); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); _renderCommand.GetMaterial().Uniform("uPixelOffset")->SetFloatValue(1.0f / size.X, 1.0f / size.Y); if (!_downsampleOnly) { _renderCommand.GetMaterial().Uniform("uDirection")->SetFloatValue(_direction.X, _direction.Y); } _renderCommand.GetMaterial().SetTexture(0, *_source); renderQueue.AddCommand(&_renderCommand); return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/BlurRenderPass.h000066400000000000000000000017571512772601700265260ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "../../nCine/Graphics/Camera.h" #include "../../nCine/Graphics/SceneNode.h" #include "../../nCine/Graphics/Texture.h" #include "../../nCine/Graphics/Viewport.h" #include using namespace nCine; namespace Jazz2::Rendering { class PlayerViewport; /** @brief Applies blur to a scene */ class BlurRenderPass : public SceneNode { public: BlurRenderPass(PlayerViewport* owner) : _owner(owner) { setVisitOrderState(SceneNode::VisitOrderState::Disabled); } void Initialize(Texture* source, std::int32_t width, std::int32_t height, Vector2f direction); void Register(); bool OnDraw(RenderQueue& renderQueue) override; Texture* GetTarget() const { return _target.get(); } private: PlayerViewport* _owner; std::unique_ptr _target; std::unique_ptr _view; std::unique_ptr _camera; RenderCommand _renderCommand; Texture* _source; bool _downsampleOnly; Vector2f _direction; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/CombineRenderer.cpp000066400000000000000000000113671512772601700272270ustar00rootroot00000000000000#include "CombineRenderer.h" #include "PlayerViewport.h" #include "../PreferencesCache.h" #include "../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Rendering { CombineRenderer::CombineRenderer(PlayerViewport* owner) : _owner(owner) { setVisitOrderState(SceneNode::VisitOrderState::Disabled); } void CombineRenderer::Initialize(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height) { _bounds = Rectf(static_cast(x), static_cast(y), static_cast(width), static_cast(height)); if (_renderCommand.GetMaterial().SetShader(_owner->_levelHandler->_combineShader)) { _renderCommand.GetMaterial().ReserveUniformsDataMemory(); _renderCommand.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = _renderCommand.GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } auto* lightTexUniform = _renderCommand.GetMaterial().Uniform("uTextureLighting"); if (lightTexUniform && lightTexUniform->GetIntValue(0) != 1) { lightTexUniform->SetIntValue(1); // GL_TEXTURE1 } auto* blurHalfTexUniform = _renderCommand.GetMaterial().Uniform("uTextureBlurHalf"); if (blurHalfTexUniform && blurHalfTexUniform->GetIntValue(0) != 2) { blurHalfTexUniform->SetIntValue(2); // GL_TEXTURE2 } auto* blurQuarterTexUniform = _renderCommand.GetMaterial().Uniform("uTextureBlurQuarter"); if (blurQuarterTexUniform && blurQuarterTexUniform->GetIntValue(0) != 3) { blurQuarterTexUniform->SetIntValue(3); // GL_TEXTURE3 } } if (_renderCommandWithWater.GetMaterial().SetShader(_owner->_levelHandler->_combineWithWaterShader)) { _renderCommandWithWater.GetMaterial().ReserveUniformsDataMemory(); _renderCommandWithWater.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = _renderCommandWithWater.GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } auto* lightTexUniform = _renderCommandWithWater.GetMaterial().Uniform("uTextureLighting"); if (lightTexUniform && lightTexUniform->GetIntValue(0) != 1) { lightTexUniform->SetIntValue(1); // GL_TEXTURE1 } auto* blurHalfTexUniform = _renderCommandWithWater.GetMaterial().Uniform("uTextureBlurHalf"); if (blurHalfTexUniform && blurHalfTexUniform->GetIntValue(0) != 2) { blurHalfTexUniform->SetIntValue(2); // GL_TEXTURE2 } auto* blurQuarterTexUniform = _renderCommandWithWater.GetMaterial().Uniform("uTextureBlurQuarter"); if (blurQuarterTexUniform && blurQuarterTexUniform->GetIntValue(0) != 3) { blurQuarterTexUniform->SetIntValue(3); // GL_TEXTURE3 } auto* noiseTexUniform = _renderCommandWithWater.GetMaterial().Uniform("uTextureNoise"); if (noiseTexUniform && noiseTexUniform->GetIntValue(0) != 4) { noiseTexUniform->SetIntValue(4); // GL_TEXTURE4 } } _renderCommand.SetTransformation(Matrix4x4f::Translation((float)x, (float)y, 0.0f)); _renderCommandWithWater.SetTransformation(Matrix4x4f::Translation((float)x, (float)y, 0.0f)); } Rectf CombineRenderer::GetBounds() const { return _bounds; } bool CombineRenderer::OnDraw(RenderQueue& renderQueue) { float viewWaterLevel = _owner->_levelHandler->_waterLevel - _owner->_cameraPos.Y + _bounds.H * 0.5f; bool viewHasWater = (viewWaterLevel < _bounds.H); auto& command = (viewHasWater ? _renderCommandWithWater : _renderCommand); command.GetMaterial().SetTexture(0, *_owner->_viewTexture); command.GetMaterial().SetTexture(1, *_owner->_lightingBuffer); command.GetMaterial().SetTexture(2, *_owner->_blurPass2.GetTarget()); command.GetMaterial().SetTexture(3, *_owner->_blurPass4.GetTarget()); if (viewHasWater && !PreferencesCache::LowWaterQuality) { command.GetMaterial().SetTexture(4, *_owner->_levelHandler->_noiseTexture); } auto* instanceBlock = command.GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, 1.0f, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(_bounds.W, _bounds.H); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); command.GetMaterial().Uniform("uAmbientColor")->SetFloatVector(_owner->_ambientLight.Data()); command.GetMaterial().Uniform("uTime")->SetFloatValue(_owner->_levelHandler->_elapsedFrames * 0.0018f); if (viewHasWater) { command.GetMaterial().Uniform("uWaterLevel")->SetFloatValue(viewWaterLevel / _bounds.H); command.GetMaterial().Uniform("uCameraPos")->SetFloatVector(_owner->_cameraPos.Data()); } renderQueue.AddCommand(&command); return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/CombineRenderer.h000066400000000000000000000014101512772601700266600ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "../../nCine/Graphics/RenderCommand.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Graphics/SceneNode.h" #include "../../nCine/Primitives/Rect.h" using namespace nCine; namespace Jazz2::Rendering { class PlayerViewport; /** @brief Combines all previous passes of a scene into a resulting image */ class CombineRenderer : public SceneNode { public: CombineRenderer(PlayerViewport* owner); void Initialize(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height); Rectf GetBounds() const; bool OnDraw(RenderQueue& renderQueue) override; private: PlayerViewport* _owner; RenderCommand _renderCommand; RenderCommand _renderCommandWithWater; Rectf _bounds; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/LightingRenderer.cpp000066400000000000000000000045161512772601700274160ustar00rootroot00000000000000#include "LightingRenderer.h" #include "PlayerViewport.h" #include "../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Rendering { LightingRenderer::LightingRenderer(PlayerViewport* owner) : _owner(owner), _renderCommandsCount(0) { _emittedLightsCache.reserve(32); setVisitOrderState(SceneNode::VisitOrderState::Disabled); } bool LightingRenderer::OnDraw(RenderQueue& renderQueue) { _renderCommandsCount = 0; _emittedLightsCache.clear(); // Collect all active light emitters auto actors = _owner->_levelHandler->GetActors(); std::size_t actorsCount = actors.size(); for (std::size_t i = 0; i < actorsCount; i++) { actors[i]->OnEmitLights(_emittedLightsCache); } for (auto& light : _emittedLightsCache) { auto command = RentRenderCommand(); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(light.Pos.X, light.Pos.Y, light.RadiusNear / light.RadiusFar, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(light.RadiusFar * 2.0f, light.RadiusFar * 2.0f); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(light.Intensity, light.Brightness, 0.0f, 0.0f); command->SetTransformation(Matrix4x4f::Translation(light.Pos.X, light.Pos.Y, 0)); renderQueue.AddCommand(command); } return true; } RenderCommand* LightingRenderer::RentRenderCommand() { if (_renderCommandsCount < _renderCommands.size()) { RenderCommand* command = _renderCommands[_renderCommandsCount].get(); _renderCommandsCount++; return command; } else { std::unique_ptr& command = _renderCommands.emplace_back(std::make_unique(RenderCommand::Type::Lighting)); _renderCommandsCount++; command->GetMaterial().SetShader(_owner->_levelHandler->_lightingShader); command->GetMaterial().SetBlendingEnabled(true); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE); command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } return command.get(); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/LightingRenderer.h000066400000000000000000000013761512772601700270640ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "../LightEmitter.h" #include "../../nCine/Graphics/RenderCommand.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Graphics/SceneNode.h" #include using namespace nCine; namespace Jazz2::Rendering { class PlayerViewport; /** @brief Processes all lights in a scene into an intermediate target */ class LightingRenderer : public SceneNode { public: LightingRenderer(PlayerViewport* owner); bool OnDraw(RenderQueue& renderQueue) override; private: PlayerViewport* _owner; SmallVector, 0> _renderCommands; std::int32_t _renderCommandsCount; SmallVector _emittedLightsCache; RenderCommand* RentRenderCommand(); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/PlayerViewport.cpp000066400000000000000000000222771512772601700271620ustar00rootroot00000000000000#include "PlayerViewport.h" #include "../PreferencesCache.h" #include "../Actors/Player.h" #include "../../nCine/tracy.h" #include "../../nCine/Base/Random.h" #include "../../nCine/Graphics/RenderQueue.h" namespace Jazz2::Rendering { PlayerViewport::PlayerViewport(LevelHandler* levelHandler, Actors::ActorBase* targetActor) : _levelHandler(levelHandler), _targetActor(targetActor), _downsamplePass(this), _blurPass1(this), _blurPass2(this), _blurPass3(this), _blurPass4(this), _cameraResponsiveness(ResponsivenessMax, ResponsivenessMax), _shakeDuration(0.0f) { _ambientLight = levelHandler->_defaultAmbientLight; _ambientLightTarget = _ambientLight.W; } bool PlayerViewport::Initialize(SceneNode* sceneNode, SceneNode* outputNode, Recti bounds, bool useHalfRes) { bool notInitialized = (_view == nullptr); std::int32_t w = bounds.W; std::int32_t h = bounds.H; if (useHalfRes) { w *= 2; h *= 2; } if (notInitialized) { _viewTexture = std::make_unique(nullptr, Texture::Format::RGB8, w, h); _view = std::make_unique(_viewTexture.get(), Viewport::DepthStencilFormat::None); _camera = std::make_unique(); _view->SetCamera(_camera.get()); _view->SetRootNode(sceneNode); } else { _view->RemoveAllTextures(); _viewTexture->Init(nullptr, Texture::Format::RGB8, w, h); _view->SetTexture(_viewTexture.get()); } _viewTexture->SetMagFiltering(SamplerFilter::Nearest); _viewTexture->SetWrap(SamplerWrapping::ClampToEdge); _camera->SetOrthoProjection(0.0f, (float)w, (float)h, 0.0f); if (notInitialized) { _lightingRenderer = std::make_unique(this); _lightingBuffer = std::make_unique(nullptr, Texture::Format::RG8, w, h); _lightingView = std::make_unique(_lightingBuffer.get(), Viewport::DepthStencilFormat::None); _lightingView->SetRootNode(_lightingRenderer.get()); _lightingView->SetCamera(_camera.get()); } else { _lightingView->RemoveAllTextures(); _lightingBuffer->Init(nullptr, Texture::Format::RG8, w, h); _lightingView->SetTexture(_lightingBuffer.get()); } _lightingBuffer->SetMagFiltering(SamplerFilter::Nearest); _lightingBuffer->SetWrap(SamplerWrapping::ClampToEdge); _downsamplePass.Initialize(_viewTexture.get(), w / 2, h / 2, Vector2f(0.0f, 0.0f)); _blurPass1.Initialize(_downsamplePass.GetTarget(), w / 2, h / 2, Vector2f(1.0f, 0.0f)); _blurPass2.Initialize(_blurPass1.GetTarget(), w / 2, h / 2, Vector2f(0.0f, 1.0f)); _blurPass3.Initialize(_blurPass2.GetTarget(), w / 4, h / 4, Vector2f(1.0f, 0.0f)); _blurPass4.Initialize(_blurPass3.GetTarget(), w / 4, h / 4, Vector2f(0.0f, 1.0f)); if (notInitialized) { _combineRenderer = std::make_unique(this); _combineRenderer->setParent(outputNode); } _combineRenderer->Initialize(bounds.X, bounds.Y, bounds.W, bounds.H); return notInitialized; } void PlayerViewport::Register() { _blurPass4.Register(); _blurPass3.Register(); _blurPass2.Register(); _blurPass1.Register(); _downsamplePass.Register(); auto& chain = Viewport::GetChain(); chain.push_back(_lightingView.get()); chain.push_back(_view.get()); } Rectf PlayerViewport::GetBounds() const { return _combineRenderer->GetBounds(); } Vector2i PlayerViewport::GetViewportSize() const { return _viewTexture->GetSize(); } Actors::ActorBase* PlayerViewport::GetTargetActor() const { return _targetActor; } void PlayerViewport::OnEndFrame() { _lightingView->SetClearColor(_ambientLight.W, 0.0f, 0.0f, 1.0f); } void PlayerViewport::UpdateCamera(float timeMult) { ZoneScopedC(0x4876AF); // Ambient Light Transition if (_ambientLight.W != _ambientLightTarget) { float step = timeMult * 0.012f; if (std::abs(_ambientLight.W - _ambientLightTarget) < step) { _ambientLight.W = _ambientLightTarget; } else { _ambientLight.W += step * ((_ambientLightTarget < _ambientLight.W) ? -1.0f : 1.0f); } } // Viewport bounds animation if (_viewBounds != _levelHandler->_viewBoundsTarget) { if (std::abs(_viewBounds.X - _levelHandler->_viewBoundsTarget.X) < 2.0f) { _viewBounds = _levelHandler->_viewBoundsTarget; } else { constexpr float TransitionSpeed = 0.02f; float dx = (_levelHandler->_viewBoundsTarget.X - _viewBounds.X) * TransitionSpeed * timeMult; _viewBounds.X += dx; _viewBounds.W -= dx; } } // The position to focus on Vector2i halfView = _view->GetSize() / 2; Vector2f focusPos = _targetActor->GetPos(); bool overridePosX = false, overridePosY = false; // TODO: Not working correctly on some platforms /*if (!std::isinf(_cameraOverridePos.X)) { overridePosX = true; focusPos.X = _cameraOverridePos.X; if (focusPos.X <= 0.0f) { focusPos.X = halfView.X - focusPos.X; } } if (!std::isinf(_cameraOverridePos.Y)) { overridePosY = true; focusPos.Y = _cameraOverridePos.Y; if (focusPos.Y <= 0.0f) { focusPos.Y = halfView.Y - 1.0f - focusPos.Y; } }*/ // If player doesn't move but has some speed, it's probably stuck, so reset the speed Vector2f focusSpeed = _targetActor->GetSpeed(); if (overridePosX || std::abs(_cameraLastPos.X - focusPos.X) < 1.0f) { focusSpeed.X = 0.0f; } if (overridePosY || std::abs(_cameraLastPos.Y - focusPos.Y) < 1.0f) { focusSpeed.Y = 0.0f; } Vector2f focusVelocity = Vector2f(std::abs(focusSpeed.X), std::abs(focusSpeed.Y)); // Camera responsiveness (smoothing unexpected movements) if (focusVelocity.X <= 3.0f) { if (_cameraResponsiveness.X > ResponsivenessMin) { _cameraResponsiveness.X = std::max(_cameraResponsiveness.X - ResponsivenessChange * timeMult, ResponsivenessMin); } } else { if (_cameraResponsiveness.X < ResponsivenessMax) { _cameraResponsiveness.X = std::min(_cameraResponsiveness.X + ResponsivenessChange * timeMult, ResponsivenessMax); } } if (focusVelocity.Y <= 3.0f) { if (_cameraResponsiveness.Y > ResponsivenessMin) { _cameraResponsiveness.Y = std::max(_cameraResponsiveness.Y - ResponsivenessChange * timeMult, ResponsivenessMin); } } else { if (_cameraResponsiveness.Y < ResponsivenessMax) { _cameraResponsiveness.Y = std::min(_cameraResponsiveness.Y + ResponsivenessChange * timeMult, ResponsivenessMax); } } _cameraLastPos.X = lerpByTime(_cameraLastPos.X, focusPos.X, std::min(_cameraResponsiveness.X, 1.0f), timeMult); _cameraLastPos.Y = lerpByTime(_cameraLastPos.Y, focusPos.Y, std::min(_cameraResponsiveness.Y, 1.0f), timeMult); _cameraDistanceFactor.X = lerpByTime(_cameraDistanceFactor.X, focusSpeed.X * 8.0f, (focusVelocity.X < 2.0f ? SlowRatioX : FastRatioX), timeMult); _cameraDistanceFactor.Y = lerpByTime(_cameraDistanceFactor.Y, focusSpeed.Y * 5.0f, (focusVelocity.Y < 2.0f ? SlowRatioY : FastRatioY), timeMult); if (_shakeDuration > 0.0f) { _shakeDuration -= timeMult; if (_shakeDuration <= 0.0f) { _shakeOffset = Vector2f::Zero; } else { float shakeFactor = 0.1f * timeMult; _shakeOffset.X = lerp(_shakeOffset.X, nCine::Random().NextFloat(-0.2f, 0.2f) * halfView.X, shakeFactor) * std::min(_shakeDuration * 0.1f, 1.0f); _shakeOffset.Y = lerp(_shakeOffset.Y, nCine::Random().NextFloat(-0.2f, 0.2f) * halfView.Y, shakeFactor) * std::min(_shakeDuration * 0.1f, 1.0f); } } // Clamp camera position to level bounds if (overridePosX) { _cameraPos.X = _cameraLastPos.X + _shakeOffset.X; } else if (_viewBounds.W > halfView.X * 2) { _cameraPos.X = std::clamp(_cameraLastPos.X + _cameraDistanceFactor.X, _viewBounds.X + halfView.X, _viewBounds.X + _viewBounds.W - halfView.X) + _shakeOffset.X; if (!PreferencesCache::UnalignedViewport || std::abs(_cameraDistanceFactor.X) < 1.0f) { _cameraPos.X = std::floor(_cameraPos.X); } } else { _cameraPos.X = std::floor(_viewBounds.X + _viewBounds.W * 0.5f + _shakeOffset.X); } if (overridePosY) { _cameraPos.Y = _cameraLastPos.Y + _shakeOffset.Y; } else if (_viewBounds.H > halfView.Y * 2) { _cameraPos.Y = std::clamp(_cameraLastPos.Y + _cameraDistanceFactor.Y, _viewBounds.Y + halfView.Y - 1.0f, _viewBounds.Y + _viewBounds.H - halfView.Y - 2.0f) + _shakeOffset.Y; if (!PreferencesCache::UnalignedViewport || std::abs(_cameraDistanceFactor.Y) < 1.0f) { _cameraPos.Y = std::floor(_cameraPos.Y); } } else { _cameraPos.Y = std::floor(_viewBounds.Y + _viewBounds.H * 0.5f + _shakeOffset.Y); } _camera->SetView(_cameraPos - halfView.As(), 0.0f, 1.0f); } void PlayerViewport::ShakeCameraView(float duration) { if (_shakeDuration < duration) { _shakeDuration = duration; } } void PlayerViewport::OverrideCamera(float x, float y, bool topLeft) { // TODO: Not working correctly on some platforms /*if (topLeft) { // Use negative values for top-left alignment _cameraOverridePos.X = -x; _cameraOverridePos.Y = -y; } else { _cameraOverridePos.X = x; _cameraOverridePos.Y = y; }*/ } void PlayerViewport::WarpCameraToTarget(bool fast) { Vector2f focusPos = _targetActor->GetPos(); if (!fast) { _cameraPos = focusPos; _cameraLastPos = _cameraPos; _cameraDistanceFactor = Vector2f(0.0f, 0.0f); _cameraResponsiveness = Vector2f(ResponsivenessMax, ResponsivenessMax); } else { Vector2f diff = _cameraLastPos - _cameraPos; _cameraPos = focusPos; _cameraLastPos = _cameraPos + diff; } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/PlayerViewport.h000066400000000000000000000036401512772601700266200ustar00rootroot00000000000000#pragma once #include "../LevelHandler.h" #include "LightingRenderer.h" #include "CombineRenderer.h" #include "BlurRenderPass.h" namespace Jazz2::Rendering { /** @brief Player viewport */ class PlayerViewport { public: #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring LevelHandler* _levelHandler; Actors::ActorBase* _targetActor; std::unique_ptr _lightingRenderer; std::unique_ptr _combineRenderer; std::unique_ptr _lightingView; std::unique_ptr _lightingBuffer; BlurRenderPass _downsamplePass; BlurRenderPass _blurPass2; BlurRenderPass _blurPass1; BlurRenderPass _blurPass3; BlurRenderPass _blurPass4; std::unique_ptr _view; std::unique_ptr _viewTexture; std::unique_ptr _camera; Rectf _viewBounds; Vector2f _cameraPos; Vector2f _cameraLastPos; Vector2f _cameraDistanceFactor; Vector2f _cameraResponsiveness; float _shakeDuration; Vector2f _shakeOffset; float _ambientLightTarget; Vector4f _ambientLight; #endif PlayerViewport(LevelHandler* levelHandler, Actors::ActorBase* targetActor); bool Initialize(SceneNode* sceneNode, SceneNode* outputNode, Recti bounds, bool useHalfRes); void Register(); Rectf GetBounds() const; Vector2i GetViewportSize() const; Actors::ActorBase* GetTargetActor() const; void OnEndFrame(); void UpdateCamera(float timeMult); void ShakeCameraView(float duration); void OverrideCamera(float x, float y, bool topLeft = false); void WarpCameraToTarget(bool fast); private: static constexpr float ResponsivenessChange = 0.04f; static constexpr float ResponsivenessMin = 0.3f; static constexpr float ResponsivenessMax = 1.2f; static constexpr float SlowRatioX = 0.3f; static constexpr float SlowRatioY = 0.3f; static constexpr float FastRatioX = 0.2f; static constexpr float FastRatioY = 0.04f; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/UpscaleRenderPass.cpp000066400000000000000000000232751512772601700275500ustar00rootroot00000000000000#include "UpscaleRenderPass.h" #include "../ContentResolver.h" #include "../PreferencesCache.h" #include "../../nCine/Application.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Graphics/Viewport.h" namespace Jazz2::Rendering { void UpscaleRenderPass::Initialize(std::int32_t width, std::int32_t height, std::int32_t targetWidth, std::int32_t targetHeight) { _targetSize = Vector2f((float)targetWidth, (float)targetHeight); if ((PreferencesCache::ActiveRescaleMode & RescaleMode::UseAntialiasing) == RescaleMode::UseAntialiasing) { float widthRatio, heightRatio; float widthFrac = modff((float)targetWidth / width, &widthRatio); float heightFrac = modff((float)targetHeight / height, &heightRatio); std::int32_t requiredWidth = (std::int32_t)(widthFrac > 0.004f ? (width * (widthRatio + 1)) : targetWidth); std::int32_t requiredHeight = (std::int32_t)(heightFrac > 0.004f ? (height * (heightRatio + 1)) : targetHeight); if (std::abs(requiredWidth - targetWidth) > 2 || std::abs(requiredHeight - targetHeight) > 2) { if (_antialiasing._target == nullptr) { _antialiasing._target = std::make_unique(nullptr, Texture::Format::RGB8, requiredWidth, requiredHeight); } else { _antialiasing._target->Init(nullptr, Texture::Format::RGB8, requiredWidth, requiredHeight); } _antialiasing._target->SetMinFiltering(SamplerFilter::Linear); _antialiasing._target->SetMagFiltering(SamplerFilter::Linear); _antialiasing._target->SetWrap(SamplerWrapping::ClampToEdge); _antialiasing._targetSize = _targetSize; _targetSize = Vector2f((float)requiredWidth, (float)requiredHeight); } else { _antialiasing._target = nullptr; } } else { _antialiasing._target = nullptr; } if (_camera == nullptr) { _camera = std::make_unique(); } _camera->SetOrthoProjection(0.0f, static_cast(width), static_cast(height), 0.0f); _camera->SetView(0, 0, 0, 1); if (_view == nullptr) { _node = std::make_unique(); _node->setVisitOrderState(SceneNode::VisitOrderState::Disabled); _target = std::make_unique(nullptr, Texture::Format::RGB8, width, height); _view = std::make_unique(_target.get(), Viewport::DepthStencilFormat::None); _view->SetRootNode(_node.get()); _view->SetCamera(_camera.get()); _view->SetClearMode(Viewport::ClearMode::Never); } else { _view->RemoveAllTextures(); _target->Init(nullptr, Texture::Format::RGB8, width, height); _view->SetTexture(_target.get()); } _target->SetMinFiltering(SamplerFilter::Nearest); _target->SetMagFiltering(SamplerFilter::Nearest); _target->SetWrap(SamplerWrapping::ClampToEdge); if (_antialiasing._target != nullptr) { if (_antialiasing._camera == nullptr) { _antialiasing._camera = std::make_unique(); } _antialiasing._camera->SetOrthoProjection(0.0f, _targetSize.X, _targetSize.Y, 0.0f); _antialiasing._camera->SetView(0, 0, 0, 1); _antialiasing._view = std::make_unique(_antialiasing._target.get(), Viewport::DepthStencilFormat::None); _antialiasing._view->SetRootNode(this); _antialiasing._view->SetCamera(_antialiasing._camera.get()); //_antialiasing._view->setClearMode(Viewport::ClearMode::Never); SceneNode& rootNode = theApplication().GetRootNode(); _antialiasing.setParent(&rootNode); setParent(nullptr); if (_antialiasing._renderCommand.GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::Antialiasing))) { _antialiasing._renderCommand.GetMaterial().ReserveUniformsDataMemory(); _antialiasing._renderCommand.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); // Required to reset render command properly _antialiasing._renderCommand.SetTransformation(_antialiasing._renderCommand.GetTransformation()); auto* textureUniform = _antialiasing._renderCommand.GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } else { _antialiasing._camera = nullptr; _antialiasing._view = nullptr; SceneNode& rootNode = theApplication().GetRootNode(); _antialiasing.setParent(nullptr); setParent(&rootNode); } // Prepare render command #if !defined(DISABLE_RESCALE_SHADERS) switch (PreferencesCache::ActiveRescaleMode & RescaleMode::TypeMask) { case RescaleMode::HQ2x: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeHQ2x); break; case RescaleMode::_3xBrz: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::Resize3xBrz); break; case RescaleMode::CrtScanlines: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeCrtScanlines); break; case RescaleMode::CrtShadowMask: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeCrtShadowMask); break; case RescaleMode::CrtApertureGrille: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeCrtApertureGrille); break; case RescaleMode::Monochrome: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeMonochrome); break; case RescaleMode::Sabr: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeSabr); break; case RescaleMode::CleanEdge: _resizeShader = ContentResolver::Get().GetShader(PrecompiledShader::ResizeCleanEdge); break; default: _resizeShader = nullptr; break; } bool shaderChanged = (_resizeShader != nullptr ? _renderCommand.GetMaterial().SetShader(_resizeShader) : _renderCommand.GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite)); #else bool shaderChanged = _renderCommand.GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); #endif if (shaderChanged) { _renderCommand.GetMaterial().ReserveUniformsDataMemory(); _renderCommand.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); // Required to reset render command properly _renderCommand.SetTransformation(_renderCommand.GetTransformation()); auto* textureUniform = _renderCommand.GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } void UpscaleRenderPass::Register() { _antialiasing.Register(); Viewport::GetChain().push_back(_view.get()); } bool UpscaleRenderPass::OnDraw(RenderQueue& renderQueue) { auto instanceBlock = _renderCommand.GetMaterial().UniformBlock(Material::InstanceBlockName); #if !defined(DISABLE_RESCALE_SHADERS) if (_resizeShader != nullptr) { // TexRectUniformName is reused for input texture size Vector2i size = _target->GetSize(); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue((float)size.X, (float)size.Y, 0.0f, 0.0f); } else #endif { instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, 1.0f, 0.0f); } instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatVector(_targetSize.Data()); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf(1.0f, 1.0f, 1.0f, 1.0f).Data()); _renderCommand.GetMaterial().SetTexture(0, *_target); renderQueue.AddCommand(&_renderCommand); return true; } UpscaleRenderPass::AntialiasingSubpass::AntialiasingSubpass() { setVisitOrderState(SceneNode::VisitOrderState::Disabled); } void UpscaleRenderPass::AntialiasingSubpass::Register() { if (_view != nullptr) { Viewport::GetChain().push_back(_view.get()); } } bool UpscaleRenderPass::AntialiasingSubpass::OnDraw(RenderQueue& renderQueue) { Vector2i size = _target->GetSize(); auto instanceBlock = _renderCommand.GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue((float)size.X, (float)size.Y, 0.0f, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatVector(_targetSize.Data()); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf(1.0f, 1.0f, 1.0f, 1.0f).Data()); _renderCommand.GetMaterial().SetTexture(0, *_target); renderQueue.AddCommand(&_renderCommand); return true; } UpscaleRenderPassWithClipping::UpscaleRenderPassWithClipping() : UpscaleRenderPass() { } void UpscaleRenderPassWithClipping::Initialize(std::int32_t width, std::int32_t height, std::int32_t targetWidth, std::int32_t targetHeight) { if (_clippedView != nullptr) { _clippedView->RemoveAllTextures(); } if (_overlayView != nullptr) { _overlayView->RemoveAllTextures(); } UpscaleRenderPass::Initialize(width, height, targetWidth, targetHeight); if (_clippedView == nullptr) { _clippedNode = std::make_unique(); _clippedNode->setVisitOrderState(SceneNode::VisitOrderState::Disabled); _clippedView = std::make_unique(_target.get(), Viewport::DepthStencilFormat::None); _clippedView->SetRootNode(_clippedNode.get()); _clippedView->SetCamera(_camera.get()); _clippedView->SetClearMode(Viewport::ClearMode::Never); } else { _clippedView->SetTexture(_target.get()); } if (_overlayView == nullptr) { _overlayNode = std::make_unique(); _overlayNode->setVisitOrderState(SceneNode::VisitOrderState::Disabled); _overlayView = std::make_unique(_target.get(), Viewport::DepthStencilFormat::None); _overlayView->SetRootNode(_overlayNode.get()); _overlayView->SetCamera(_camera.get()); _overlayView->SetClearMode(Viewport::ClearMode::Never); } else { _overlayView->SetTexture(_target.get()); } } void UpscaleRenderPassWithClipping::Register() { _antialiasing.Register(); auto& chain = Viewport::GetChain(); chain.push_back(_overlayView.get()); chain.push_back(_clippedView.get()); chain.push_back(_view.get()); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Rendering/UpscaleRenderPass.h000066400000000000000000000052041512772601700272050ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "../../nCine/Graphics/RenderCommand.h" #include "../../nCine/Graphics/SceneNode.h" #include "../../nCine/Graphics/Camera.h" #include "../../nCine/Graphics/Viewport.h" using namespace nCine; namespace Jazz2::Rendering { /** @brief Upscales input image usually to a native resolution */ class UpscaleRenderPass : public SceneNode { public: UpscaleRenderPass() : _resizeShader(nullptr) { setVisitOrderState(SceneNode::VisitOrderState::Disabled); } virtual void Initialize(std::int32_t width, std::int32_t height, std::int32_t targetWidth, std::int32_t targetHeight); virtual void Register(); bool OnDraw(RenderQueue& renderQueue) override; SceneNode* GetNode() const { return _node.get(); } Vector2i GetViewSize() const { return _view->GetSize(); } Vector2f GetTargetSize() const { return (_antialiasing._target != nullptr ? _antialiasing._targetSize : _targetSize); } protected: /** @brief Optional antialiasing subpass */ class AntialiasingSubpass : public SceneNode { friend class UpscaleRenderPass; public: AntialiasingSubpass(); void Register(); bool OnDraw(RenderQueue& renderQueue) override; private: std::unique_ptr _target; std::unique_ptr _view; std::unique_ptr _camera; RenderCommand _renderCommand; Vector2f _targetSize; }; #ifndef DOXYGEN_GENERATING_OUTPUT // Hide these members from documentation before refactoring std::unique_ptr _view; std::unique_ptr _camera; std::unique_ptr _target; Vector2f _targetSize; AntialiasingSubpass _antialiasing; #endif private: std::unique_ptr _node; #if !defined(DISABLE_RESCALE_SHADERS) Shader* _resizeShader; #endif RenderCommand _renderCommand; }; /** @brief Upscales input image usually to a native resolution, additionaly supports 3 independent layers (background layer, clipped main layer, overlay layer) */ class UpscaleRenderPassWithClipping : public UpscaleRenderPass { public: UpscaleRenderPassWithClipping(); void Initialize(std::int32_t width, std::int32_t height, std::int32_t targetWidth, std::int32_t targetHeight) override; void Register() override; SceneNode* GetClippedNode() const { return _clippedNode.get(); } SceneNode* GetOverlayNode() const { return _overlayNode.get(); } void SetClipRectangle(const Recti& scissorRect) { _clippedView->SetScissorRect(scissorRect); } private: std::unique_ptr _clippedView; std::unique_ptr _overlayView; std::unique_ptr _clippedNode; std::unique_ptr _overlayNode; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Resources.cpp000066400000000000000000000017171512772601700242170ustar00rootroot00000000000000#include "Resources.h" namespace Jazz2::Resources { GenericGraphicResource::GenericGraphicResource() noexcept : Flags(GenericGraphicResourceFlags::None) { } GraphicResource::GraphicResource() noexcept { } bool GraphicResource::operator<(const GraphicResource& p) const noexcept { return State < p.State; } GenericSoundResource::GenericSoundResource(std::unique_ptr stream, StringView filename) noexcept : Buffer(std::move(stream), filename), Flags(GenericSoundResourceFlags::None) { } SoundResource::SoundResource() noexcept { } Metadata::Metadata() noexcept : Flags(MetadataFlags::None) { } GraphicResource* Metadata::FindAnimation(AnimState state) noexcept { auto it = std::lower_bound(Animations.begin(), Animations.end(), state, [](const GraphicResource& x, AnimState value) { return x.State < value; }); return (it != Animations.end() && it->State == state ? it : nullptr); } Episode::Episode() noexcept { } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Resources.h000066400000000000000000000120101512772601700236500ustar00rootroot00000000000000#pragma once #include "../Main.h" #include "AnimationLoopMode.h" #include "AnimState.h" #include "../nCine/Audio/AudioBuffer.h" #include "../nCine/Base/HashMap.h" #include "../nCine/Graphics/Texture.h" #include "../nCine/Primitives/Vector2.h" #include #include #include #include using namespace Death::Containers; using namespace Death::IO; using namespace nCine; namespace Jazz2::Resources { /** @brief Flags for @ref GenericGraphicResource, supports a bitwise combination of its member values */ enum class GenericGraphicResourceFlags { None = 0x00, Referenced = 0x01 }; DEATH_ENUM_FLAGS(GenericGraphicResourceFlags); /** @brief Shared graphic resource */ struct GenericGraphicResource { /** @brief Resource flags */ GenericGraphicResourceFlags Flags; /** @brief Diffuse texture */ std::unique_ptr TextureDiffuse; //std::unique_ptr TextureNormal; /** @brief Collision mask */ std::unique_ptr Mask; /** @brief Frame dimensions */ Vector2i FrameDimensions; /** @brief Frame configuration */ Vector2i FrameConfiguration; /** @brief Animation duration (in normalized frames) */ float AnimDuration; /** @brief Frame count */ std::int32_t FrameCount; /** @brief Hotspot */ Vector2i Hotspot; /** @brief Optional coldspot */ Vector2i Coldspot; /** @brief Optional gunspot */ Vector2i Gunspot; GenericGraphicResource() noexcept; }; /** @brief Specific graphic resource (from metadata) */ struct GraphicResource { /** @brief Underlying generic resource */ GenericGraphicResource* Base; /** @brief Animation state */ AnimState State; /** @brief Animation duration (in normalized frames) */ float AnimDuration; /** @brief Frame count */ std::int32_t FrameCount; /** @brief Frame offset */ std::int32_t FrameOffset; /** @brief Animation loop mode */ AnimationLoopMode LoopMode; GraphicResource() noexcept; bool operator<(const GraphicResource& p) const noexcept; }; /** @brief Flags for @ref GenericSoundResource, supports a bitwise combination of its member values */ enum class GenericSoundResourceFlags { None = 0x00, Referenced = 0x01 }; DEATH_ENUM_FLAGS(GenericSoundResourceFlags); /** @brief Shared sound resource */ struct GenericSoundResource { /** @brief Audio buffer */ AudioBuffer Buffer; /** @brief Resource flags */ GenericSoundResourceFlags Flags; GenericSoundResource(std::unique_ptr stream, StringView filename) noexcept; }; /** @brief Specific sound resource (from metadata) */ struct SoundResource { /** @brief List of underlying generic resources */ SmallVector Buffers; SoundResource() noexcept; }; /** @brief Flags for @ref Metadata, supports a bitwise combination of its member values */ enum class MetadataFlags { None = 0x00, Referenced = 0x01, AsyncFinalizingRequired = 0x02 }; DEATH_ENUM_FLAGS(MetadataFlags); /** @brief Contains assets for specific object type */ struct Metadata { /** @brief Metadata path */ String Path; /** @brief Metadata flags */ MetadataFlags Flags; /** @brief Animations */ SmallVector Animations; /** @brief Sounds */ HashMap Sounds; /** @brief Bounding box */ Vector2i BoundingBox; Metadata() noexcept; /** @brief Finds specified animation state */ GraphicResource* FindAnimation(AnimState state) noexcept; }; /** @brief Describes an episode */ struct Episode { /** @brief Internal name */ String Name; /** @brief Display name */ String DisplayName; /** @brief Name of the first level in the episode */ String FirstLevel; /** @brief Name of the previous episode */ String PreviousEpisode; /** @brief Name of the next episode */ String NextEpisode; /** @brief Position in episode selection list */ std::uint16_t Position; /** @brief Texture for title image */ std::unique_ptr TitleImage; /** @brief Texture for background image */ std::unique_ptr BackgroundImage; Episode() noexcept; }; /** @brief Font type */ enum class FontType { Small, /**< Small */ Medium, /**< Medium */ Count /**< Count of supported font types */ }; /** @brief Precompiled shader */ enum class PrecompiledShader { Lighting, BatchedLighting, Blur, Downsample, Combine, CombineWithWater, CombineWithWaterLow, TexturedBackground, TexturedBackgroundDither, TexturedBackgroundCircle, TexturedBackgroundCircleDither, Colorized, BatchedColorized, Tinted, BatchedTinted, Outline, BatchedOutline, WhiteMask, BatchedWhiteMask, PartialWhiteMask, BatchedPartialWhiteMask, FrozenMask, BatchedFrozenMask, ShieldFire, BatchedShieldFire, ShieldLightning, BatchedShieldLightning, #if !defined(DISABLE_RESCALE_SHADERS) ResizeHQ2x, Resize3xBrz, ResizeCrtScanlines, ResizeCrtShadowMask, ResizeCrtApertureGrille, ResizeMonochrome, ResizeSabr, ResizeCleanEdge, #endif Antialiasing, Transition, Count }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/000077500000000000000000000000001512772601700234755ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/JJ2PlusDefinitions.cpp000066400000000000000000002437541512772601700276450ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "JJ2PlusDefinitions.h" #include "LevelScriptLoader.h" #include "../LevelHandler.h" #include "../Actors/ActorBase.h" #include "../Actors/Player.h" #include "../Compatibility/JJ2Strings.h" #include "../UI/HUD.h" #include "../UI/InGameConsole.h" #include "../../nCine/Application.h" #include "../../nCine/Base/Random.h" #include #include #include using namespace Jazz2::Tiles; namespace Jazz2::Scripting { static void Unimplemented(const char* sourceName) { auto ctx = asGetActiveContext(); if (ctx != nullptr) { const char* sectionName; std::int32_t lineNumber = ctx->GetLineNumber(0, nullptr, §ionName); LOGW("{} (called from \"{}:{}\")", sourceName, sectionName, lineNumber); } else { LOGW("{}", sourceName); } } #if defined(DEATH_TRACE) # define noop() Unimplemented(NCINE_CURRENT_FUNCTION) #else # define noop() do {} while (false) #endif namespace Legacy { void jjTEXTAPPEARANCE::constructor(void* self) { noop(); new(self) jjTEXTAPPEARANCE{}; } void jjTEXTAPPEARANCE::constructorMode(std::uint32_t mode, void* self) { noop(); new(self) jjTEXTAPPEARANCE{}; } jjTEXTAPPEARANCE& jjTEXTAPPEARANCE::operator=(std::uint32_t other) { noop(); return *this; } void jjPALCOLOR::Create(void* self) { noop(); new(self) jjPALCOLOR{}; } void jjPALCOLOR::CreateFromRgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue, void* self) { noop(); new(self) jjPALCOLOR{red, green, blue}; } std::uint8_t jjPALCOLOR::getHue() { noop(); return 0; } std::uint8_t jjPALCOLOR::getSat() { noop(); return 0; } std::uint8_t jjPALCOLOR::getLight() { noop(); return 0; } void jjPALCOLOR::swizzle(std::uint32_t redc, std::uint32_t greenc, std::uint32_t bluec) { noop(); std::uint8_t r = red; std::uint8_t g = green; std::uint8_t b = blue; switch (redc) { case 1: red = g; break; case 2: red = b; break; } switch (greenc) { case 0: green = r; break; case 2: green = b; break; } switch (bluec) { case 0: blue = r; break; case 1: blue = g; break; } } void jjPALCOLOR::setHSL(std::int32_t hue, std::uint8_t sat, std::uint8_t light) { noop(); } jjPALCOLOR& jjPALCOLOR::operator=(const jjPALCOLOR& other) { noop(); red = other.red; green = other.green; blue = other.blue; return *this; } bool jjPALCOLOR::operator==(const jjPALCOLOR& other) { noop(); return (red == other.red && green == other.green && blue == other.blue); } jjPAL::jjPAL() : _refCount(1) { noop(); } jjPAL::~jjPAL() { noop(); } jjPAL* jjPAL::Create(jjPAL* self) { noop(); return new(self) jjPAL(); } void jjPAL::AddRef() { _refCount++; } void jjPAL::Release() { if (--_refCount == 0) { this->~jjPAL(); asFreeMem(this); } } jjPAL& jjPAL::operator=(const jjPAL& o) { noop(); return *this; } bool jjPAL::operator==(const jjPAL& o) { noop(); return (this == &o); } jjPALCOLOR& jjPAL::getColor(std::uint8_t idx) { noop(); return _palette[idx]; } const jjPALCOLOR& jjPAL::getConstColor(std::uint8_t idx) const { noop(); return _palette[idx]; } jjPALCOLOR& jjPAL::setColorEntry(std::uint8_t idx, jjPALCOLOR& value) { noop(); _palette[idx] = value; return _palette[idx]; } void jjPAL::reset() { noop(); } void jjPAL::apply() { noop(); } bool jjPAL::load(const String& filename) { noop(); return false; } void jjPAL::fill(std::uint8_t red, std::uint8_t green, std::uint8_t blue, float opacity) { noop(); } void jjPAL::fillTint(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t start, std::uint8_t length, float opacity) { noop(); } void jjPAL::fillFromColor(jjPALCOLOR color, float opacity) { noop(); } void jjPAL::fillTintFromColor(jjPALCOLOR color, std::uint8_t start, std::uint8_t length, float opacity) { noop(); } void jjPAL::gradient(std::uint8_t red1, std::uint8_t green1, std::uint8_t blue1, std::uint8_t red2, std::uint8_t green2, std::uint8_t blue2, std::uint8_t start, std::uint8_t length, float opacity, bool inclusive) { noop(); } void jjPAL::gradientFromColor(jjPALCOLOR color1, jjPALCOLOR color2, std::uint8_t start, std::uint8_t length, float opacity, bool inclusive) { noop(); } void jjPAL::copyFrom(std::uint8_t start, std::uint8_t length, std::uint8_t start2, const jjPAL& source, float opacity) { noop(); } std::uint8_t jjPAL::findNearestColor(jjPALCOLOR color) { noop(); return 0; } jjSTREAM::jjSTREAM() : _refCount(1) { noop(); } jjSTREAM::~jjSTREAM() { noop(); } jjSTREAM* jjSTREAM::Create() { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjSTREAM)); return new(mem) jjSTREAM(); } jjSTREAM* jjSTREAM::CreateFromFile(const String& filename) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjSTREAM)); return new(mem) jjSTREAM(); } void jjSTREAM::AddRef() { _refCount++; } void jjSTREAM::Release() { if (--_refCount == 0) { this->~jjSTREAM(); asFreeMem(this); } } // Assignment operator jjSTREAM& jjSTREAM::operator=(const jjSTREAM& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } std::uint32_t jjSTREAM::getSize() const { noop(); return 0; } bool jjSTREAM::isEmpty() const { noop(); return false; } bool jjSTREAM::save(const String& tilename) const { noop(); return true; } void jjSTREAM::clear() { noop(); } bool jjSTREAM::discard(std::uint32_t count) { return 0; } bool jjSTREAM::write(const String& value) { noop(); return true; } bool jjSTREAM::write(const jjSTREAM& value) { noop(); return true; } bool jjSTREAM::get(String& value, std::uint32_t count) { noop(); return false; } bool jjSTREAM::get(jjSTREAM& value, std::uint32_t count) { noop(); return false; } bool jjSTREAM::getLine(String& value, const String& delim) { noop(); return false; } bool jjSTREAM::push(bool value) { noop(); return false; } bool jjSTREAM::push(std::uint8_t value) { noop(); return false; } bool jjSTREAM::push(std::int8_t value) { noop(); return false; } bool jjSTREAM::push(std::uint16_t value) { noop(); return false; } bool jjSTREAM::push(std::int16_t value) { noop(); return false; } bool jjSTREAM::push(std::uint32_t value) { noop(); return false; } bool jjSTREAM::push(std::int32_t value) { noop(); return false; } bool jjSTREAM::push(std::uint64_t value) { noop(); return false; } bool jjSTREAM::push(std::int64_t value) { noop(); return false; } bool jjSTREAM::push(float value) { noop(); return false; } bool jjSTREAM::push(double value) { noop(); return false; } bool jjSTREAM::push(const String& value) { noop(); return false; } bool jjSTREAM::push(const jjSTREAM& value) { noop(); return false; } bool jjSTREAM::pop(bool& value) { noop(); return false; } bool jjSTREAM::pop(std::uint8_t& value) { noop(); return false; } bool jjSTREAM::pop(std::int8_t& value) { noop(); return false; } bool jjSTREAM::pop(std::uint16_t& value) { noop(); return false; } bool jjSTREAM::pop(std::int16_t& value) { noop(); return false; } bool jjSTREAM::pop(std::uint32_t& value) { noop(); return false; } bool jjSTREAM::pop(std::int32_t& value) { noop(); return false; } bool jjSTREAM::pop(std::uint64_t& value) { noop(); return false; } bool jjSTREAM::pop(std::int64_t& value) { noop(); return false; } bool jjSTREAM::pop(float& value) { noop(); return false; } bool jjSTREAM::pop(double& value) { noop(); return false; } bool jjSTREAM::pop(String& value) { noop(); return false; } bool jjSTREAM::pop(jjSTREAM& value) { noop(); return false; } jjRNG::jjRNG(std::uint64_t seed) : _refCount(1) { noop(); } jjRNG::~jjRNG() { noop(); } jjRNG* jjRNG::Create(std::uint64_t seed) { noop(); void* mem = asAllocMem(sizeof(jjRNG)); return new(mem) jjRNG(seed); } void jjRNG::AddRef() { _refCount++; } void jjRNG::Release() { if (--_refCount == 0) { this->~jjRNG(); asFreeMem(this); } } std::uint64_t jjRNG::operator()() { // Join two 32-bit values to single 64-bit value return _random.Next() | ((std::uint64_t)_random.Next() << 32ull); } jjRNG& jjRNG::operator=(const jjRNG& o) { _random = o._random; return *this; } bool jjRNG::operator==(const jjRNG& o) const { return (this == &o); } void jjRNG::seed(std::uint64_t value) { _random.Init(value, DefaultInitSequence); } void jjRNG::discard(std::uint64_t count) { for (std::uint64_t i = 0; i < count; i++) { _random.Next(); } } jjBEHAVIOR* jjBEHAVIOR::Create(jjBEHAVIOR* self) { noop(); return new(self) jjBEHAVIOR(); } jjBEHAVIOR* jjBEHAVIOR::CreateFromBehavior(std::uint32_t behavior, jjBEHAVIOR* self) { noop(); return new(self) jjBEHAVIOR(); } void jjBEHAVIOR::Destroy(jjBEHAVIOR* self) { noop(); } jjBEHAVIOR& jjBEHAVIOR::operator=(const jjBEHAVIOR& other) { noop(); return *this; } jjBEHAVIOR& jjBEHAVIOR::operator=(std::uint32_t other) { noop(); return *this; } jjBEHAVIOR& jjBEHAVIOR::operator=(asIScriptFunction* other) { noop(); return *this; } jjBEHAVIOR& jjBEHAVIOR::operator=(asIScriptObject* other) { noop(); return *this; } bool jjBEHAVIOR::operator==(const jjBEHAVIOR& other) const { noop(); return false; } bool jjBEHAVIOR::operator==(std::uint32_t other) const { noop(); return false; } bool jjBEHAVIOR::operator==(const asIScriptFunction* other) const { noop(); return false; } jjBEHAVIOR::operator std::uint32_t() { noop(); return 0; } jjBEHAVIOR::operator asIScriptFunction* () { noop(); return nullptr; } jjBEHAVIOR::operator asIScriptObject* () { noop(); return nullptr; } jjANIMFRAME::jjANIMFRAME() : _refCount(1) { noop(); } jjANIMFRAME::~jjANIMFRAME() { noop(); } void jjANIMFRAME::AddRef() { _refCount++; } void jjANIMFRAME::Release() { if (--_refCount == 0) { this->~jjANIMFRAME(); asFreeMem(this); } } // Assignment operator jjANIMFRAME& jjANIMFRAME::operator=(const jjANIMFRAME& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } jjANIMFRAME* jjANIMFRAME::get_jjAnimFrames(std::uint32_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjANIMFRAME)); return new(mem) jjANIMFRAME(); } bool jjANIMFRAME::get_transparent() const { noop(); return false; } bool jjANIMFRAME::set_transparent(bool value) const { noop(); return false; } bool jjANIMFRAME::doesCollide(std::int32_t xPos, std::int32_t yPos, std::int32_t direction, const jjANIMFRAME* frame2, std::int32_t xPos2, std::int32_t yPos2, std::int32_t direction2, bool always) const { noop(); return false; } jjANIMATION::jjANIMATION(std::uint32_t index) : _refCount(1), _index(index) { noop(); } jjANIMATION::~jjANIMATION() { noop(); } void jjANIMATION::AddRef() { _refCount++; } void jjANIMATION::Release() { if (--_refCount == 0) { this->~jjANIMATION(); asFreeMem(this); } } // Assignment operator jjANIMATION& jjANIMATION::operator=(const jjANIMATION& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } bool jjANIMATION::save(const String& filename, const jjPAL& palette) const { noop(); return false; } bool jjANIMATION::load(const String& filename, std::int32_t hotSpotX, std::int32_t hotSpotY, std::int32_t coldSpotYOffset, std::int32_t firstFrameToOverwrite) { noop(); return false; } jjANIMATION* jjANIMATION::get_jjAnimations(std::uint32_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjANIMATION)); return new(mem) jjANIMATION(index); } std::uint32_t jjANIMATION::get_firstFrame() const { noop(); return 0; } std::uint32_t jjANIMATION::set_firstFrame(std::uint32_t index) const { noop(); return 0; } std::uint32_t jjANIMATION::getAnimFirstFrame() { noop(); return 0; } jjANIMSET::jjANIMSET(std::uint32_t index) : _refCount(1), _index(index) { noop(); } jjANIMSET::~jjANIMSET() { noop(); } void jjANIMSET::AddRef() { _refCount++; } void jjANIMSET::Release() { if (--_refCount == 0) { this->~jjANIMSET(); asFreeMem(this); } } jjANIMSET* jjANIMSET::get_jjAnimSets(std::uint32_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjANIMSET)); return new(mem) jjANIMSET(index); } std::uint32_t jjANIMSET::convertAnimSetToUint() { noop(); return _index; } jjANIMSET* jjANIMSET::load(std::uint32_t fileSetID, const String& filename, std::int32_t firstAnimToOverwrite, std::int32_t firstFrameToOverwrite) { noop(); return this; } jjANIMSET* jjANIMSET::allocate(const CScriptArray& frameCounts) { noop(); return this; } jjCANVAS::jjCANVAS(UI::HUD* hud, const Rectf& view) : Hud(hud), View(view) { } void jjCANVAS::DrawPixel(std::int32_t xPixel, int32_t yPixel, std::uint8_t color, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawRectangle(std::int32_t xPixel, int32_t yPixel, int32_t width, int32_t height, std::uint8_t color, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawSprite(std::int32_t xPixel, int32_t yPixel, int32_t setID, std::uint8_t animation, std::uint8_t frame, int8_t direction, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawCurFrameSprite(std::int32_t xPixel, int32_t yPixel, std::uint32_t sprite, int8_t direction, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawResizedSprite(std::int32_t xPixel, int32_t yPixel, int32_t setID, std::uint8_t animation, std::uint8_t frame, float xScale, float yScale, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawResizedCurFrameSprite(std::int32_t xPixel, int32_t yPixel, std::uint32_t sprite, float xScale, float yScale, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawTransformedSprite(std::int32_t xPixel, int32_t yPixel, int32_t setID, std::uint8_t animation, std::uint8_t frame, int32_t angle, float xScale, float yScale, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawTransformedCurFrameSprite(std::int32_t xPixel, int32_t yPixel, std::uint32_t sprite, int32_t angle, float xScale, float yScale, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::DrawSwingingVine(std::int32_t xPixel, int32_t yPixel, std::uint32_t sprite, int32_t length, int32_t curvature, std::uint32_t mode, std::uint8_t param) { noop(); } void jjCANVAS::ExternalDrawTile(std::int32_t xPixel, int32_t yPixel, std::uint16_t tile, std::uint32_t tileQuadrant) { noop(); } void jjCANVAS::DrawTextBasicSize(std::int32_t xPixel, int32_t yPixel, const String& text, std::uint32_t size, std::uint32_t mode, std::uint8_t param) { noop(); // TODO float scale; switch (size) { default: case 0: // MEDIUM scale = 0.8f; case 1: // SMALL scale = 0.6f; case 2: // LARGE scale = 1.1f; } std::int32_t charOffset = 0; auto recodedText = Compatibility::JJ2Strings::RecodeString(text); Hud->_smallFont->DrawString(Hud, recodedText, charOffset, xPixel, yPixel, UI::HUD::MainLayer, UI::Alignment::TopLeft, UI::Font::DefaultColor, scale, 0.0f, 0.0f, 0.0f, 0.0f); } void jjCANVAS::DrawTextExtSize(std::int32_t xPixel, int32_t yPixel, const String& text, std::uint32_t size, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, std::uint32_t mode, std::uint8_t param) { noop(); // TODO Colorf color = UI::Font::DefaultColor; if (mode == spriteType_BLENDNORMAL) { color.A = param / 255.0f; } float scale; switch (size) { default: case 0: // MEDIUM scale = 0.8f; case 1: // SMALL scale = 0.6f; case 2: // LARGE scale = 1.1f; } std::int32_t charOffset = 0; auto recodedText = Compatibility::JJ2Strings::RecodeString(text); Hud->_smallFont->DrawString(Hud, recodedText, charOffset, xPixel, yPixel, UI::HUD::MainLayer, UI::Alignment::TopLeft, color, scale, 0.0f, 0.0f, 0.0f, 0.0f); } void jjCANVAS::drawString(std::int32_t xPixel, std::int32_t yPixel, const String& text, const jjANIMATION& animation, std::uint32_t mode, std::uint8_t param) { noop(); // TODO std::int32_t charOffset = 0; auto recodedText = Compatibility::JJ2Strings::RecodeString(text); Hud->_smallFont->DrawString(Hud, recodedText, charOffset, xPixel, yPixel, UI::HUD::MainLayer, UI::Alignment::TopLeft, UI::Font::DefaultColor, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f); } void jjCANVAS::drawStringEx(std::int32_t xPixel, std::int32_t yPixel, const String& text, const jjANIMATION& animation, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, std::uint32_t spriteMode, std::uint8_t param2) { noop(); } void jjCANVAS::jjDrawString(float xPixel, float yPixel, const String& text, const jjANIMATION& animation, std::uint32_t mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void jjCANVAS::jjDrawStringEx(float xPixel, float yPixel, const String& text, const jjANIMATION& animation, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, std::uint32_t spriteMode, std::uint8_t param2, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } int jjCANVAS::jjGetStringWidth(const String& text, const jjANIMATION& animation, const jjTEXTAPPEARANCE& style) { noop(); // TODO return text.size() * 12.0f; } jjOBJ::jjOBJ() : _refCount(1) { noop(); } jjOBJ::~jjOBJ() { noop(); } void jjOBJ::AddRef() { _refCount++; } void jjOBJ::Release() { if (--_refCount == 0) { this->~jjOBJ(); asFreeMem(this); } } bool jjOBJ::get_isActive() const { noop(); return true; } std::uint32_t jjOBJ::get_lightType() const { noop(); return 0; } std::uint32_t jjOBJ::set_lightType(std::uint32_t value) const { noop(); return 0; } jjOBJ* jjOBJ::objectHit(jjOBJ* target, std::uint32_t playerHandling) { noop(); return nullptr; } void jjOBJ::blast(std::int32_t maxDistance, bool blastObjects) { noop(); } void jjOBJ::behave1(std::uint32_t behavior, bool draw) { noop(); } void jjOBJ::behave2(jjBEHAVIOR behavior, bool draw) { noop(); } void jjOBJ::behave3(jjVOIDFUNCOBJ behavior, bool draw) { noop(); } std::int32_t jjOBJ::jjAddObject(std::uint8_t eventID, float xPixel, float yPixel, std::uint16_t creatorID, std::uint32_t creatorType, std::uint32_t behavior) { noop(); return 0; } std::int32_t jjOBJ::jjAddObjectEx(std::uint8_t eventID, float xPixel, float yPixel, std::uint16_t creatorID, std::uint32_t creatorType, jjVOIDFUNCOBJ behavior) { noop(); return 0; } void jjOBJ::jjDeleteObject(std::int32_t objectID) { noop(); } void jjOBJ::jjKillObject(std::int32_t objectID) { noop(); } std::uint32_t jjOBJ::determineCurFrame(bool change) { noop(); return 0; } std::uint16_t jjOBJ::get_creatorID() const { noop(); return 0; } std::uint16_t jjOBJ::set_creatorID(std::uint16_t value) const { noop(); return 0; } std::uint32_t jjOBJ::get_creatorType() const { noop(); return 0; } std::uint32_t jjOBJ::set_creatorType(std::uint32_t value) const { noop(); return 0; } int16_t jjOBJ::determineCurAnim(std::uint8_t setID, std::uint8_t animation, bool change) { noop(); return 0; } std::uint32_t jjOBJ::get_bulletHandling() { noop(); return 0; } std::uint32_t jjOBJ::set_bulletHandling(std::uint32_t value) { noop(); return 0; } bool jjOBJ::get_ricochet() { noop(); return 0; } bool jjOBJ::set_ricochet(bool value) { noop(); return 0; } bool jjOBJ::get_freezable() { noop(); return 0; } bool jjOBJ::set_freezable(bool value) { noop(); return 0; } bool jjOBJ::get_blastable() { noop(); return 0; } bool jjOBJ::set_blastable(bool value) { noop(); return 0; } std::uint32_t jjOBJ::get_playerHandling() { noop(); return false; } std::uint32_t jjOBJ::set_playerHandling(std::uint32_t value) { noop(); return false; } bool jjOBJ::get_isTarget() { noop(); return false; } bool jjOBJ::set_isTarget(bool value) { noop(); return false; } bool jjOBJ::get_triggersTNT() { noop(); return false; } bool jjOBJ::set_triggersTNT(bool value) { noop(); return false; } bool jjOBJ::get_deactivates() { noop(); return false; } bool jjOBJ::set_deactivates(bool value) { noop(); return false; } bool jjOBJ::get_scriptedCollisions() { noop(); return false; } bool jjOBJ::set_scriptedCollisions(bool value) { noop(); return false; } std::int32_t jjOBJ::get_var(std::uint8_t x) { noop(); return 0; } std::int32_t jjOBJ::set_var(std::uint8_t x, std::int32_t value) { noop(); return 0; } std::int32_t jjOBJ::draw() { noop(); return 0; } std::int32_t jjOBJ::beSolid(bool shouldCheckForStompingLocalPlayers) { noop(); return 0; } void jjOBJ::bePlatform(float xOld, float yOld, std::int32_t width, std::int32_t height) { noop(); } void jjOBJ::clearPlatform() { noop(); } void jjOBJ::putOnGround(bool precise) { noop(); } bool jjOBJ::ricochet() { noop(); return false; } std::int32_t jjOBJ::unfreeze(std::int32_t style) { noop(); return 0; } void jjOBJ::deleteObject() { noop(); } void jjOBJ::deactivate() { noop(); } void jjOBJ::pathMovement() { noop(); } int32_t jjOBJ::fireBullet(std::uint8_t eventID) { noop(); return 0; } void jjOBJ::particlePixelExplosion(std::int32_t style) { noop(); } void jjOBJ::grantPickup(jjPLAYER* player, std::int32_t frequency) { noop(); } std::int32_t jjOBJ::findNearestPlayer(std::int32_t maxDistance) const { noop(); return 0; } std::int32_t jjOBJ::findNearestPlayerEx(std::int32_t maxDistance, std::int32_t& foundDistance) const { noop(); return 0; } bool jjOBJ::doesCollide(const jjOBJ* object, bool always) const { noop(); return false; } bool jjOBJ::doesCollidePlayer(const jjPLAYER* object, bool always) const { noop(); return false; } std::uint8_t jjPARTICLEPIXEL::get_color(std::int32_t i) const { noop(); return 0; } std::uint8_t jjPARTICLEPIXEL::set_color(std::int32_t i, std::uint8_t value) { noop(); return 0; } String jjPARTICLESTRING::get_text() const { noop(); return {}; } void jjPARTICLESTRING::set_text(String text) { noop(); } jjPARTICLE::jjPARTICLE() : _refCount(1) { noop(); } jjPARTICLE::~jjPARTICLE() { noop(); } void jjPARTICLE::AddRef() { _refCount++; } void jjPARTICLE::Release() { if (--_refCount == 0) { this->~jjPARTICLE(); asFreeMem(this); } } jjPLAYER::jjPLAYER(LevelScriptLoader* levelScripts, Actors::Player* player) : _levelScriptLoader(levelScripts), _player(player), _timerCallback(nullptr), _timerState(0), _timerLeft(0.0f), _timerPersists(false) { noop(); } jjPLAYER::~jjPLAYER() { noop(); } jjPLAYER& jjPLAYER::operator=(const jjPLAYER& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } std::int32_t jjPLAYER::get_score() const { noop(); return _player->_score; } std::int32_t jjPLAYER::set_score(std::int32_t value) { noop(); _player->_score = value; return _player->_score; } std::int32_t jjPLAYER::get_scoreDisplayed() const { noop(); return _player->_score; } std::int32_t jjPLAYER::set_scoreDisplayed(std::int32_t value) { noop(); _player->_score = value; return _player->_score; } std::int32_t jjPLAYER::setScore(std::int32_t value) { noop(); _player->_score = value; return _player->_score; } float jjPLAYER::get_xPos() const { noop(); return _player->_pos.X; } float jjPLAYER::set_xPos(float value) { noop(); _player->_pos.X = value; return _player->_pos.X; } float jjPLAYER::get_yPos() const { noop(); return _player->_pos.Y; } float jjPLAYER::set_yPos(float value) { noop(); _player->_pos.Y = value; return _player->_pos.Y; } float jjPLAYER::get_xAcc() const { noop(); return _player->_externalForce.X; } float jjPLAYER::set_xAcc(float value) { noop(); _player->_externalForce.X = value; return _player->_externalForce.X; } float jjPLAYER::get_yAcc() const { noop(); return _player->_externalForce.Y; } float jjPLAYER::set_yAcc(float value) { noop(); _player->_externalForce.Y = value; return _player->_externalForce.Y; } float jjPLAYER::get_xOrg() const { noop(); // TODO return _player->_pos.X; } float jjPLAYER::set_xOrg(float value) { noop(); // TODO return _player->_pos.X; } float jjPLAYER::get_yOrg() const { noop(); // TODO return _player->_pos.Y; } float jjPLAYER::set_yOrg(float value) { noop(); // TODO return _player->_pos.Y; } float jjPLAYER::get_xSpeed() { noop(); return _player->_speed.X; } float jjPLAYER::set_xSpeed(float value) { noop(); _player->_speed.X = value; return value; } float jjPLAYER::get_ySpeed() { noop(); return _player->_speed.Y; } float jjPLAYER::set_ySpeed(float value) { noop(); _player->_speed.Y = value; return value; } void jjPLAYER::freeze(bool frozen) { noop(); if (frozen) { _player->_frozenTimeLeft = 180.0f; _player->_renderer.AnimPaused = true; } else if (_player->_frozenTimeLeft > 0.01f) { _player->_frozenTimeLeft = 0.01f; } } std::int32_t jjPLAYER::get_currTile() { noop(); auto pos = _player->_pos; return ((std::int32_t)pos.X / 32) + ((std::int32_t)pos.Y / 32 * 65536); } bool jjPLAYER::startSugarRush(std::int32_t time) { noop(); // TODO: if boss active, return false _player->ActivateSugarRush((float)time * 60.0f / 70.0f); return true; } std::int8_t jjPLAYER::get_health() const { noop(); return (std::int8_t)_player->_health; } std::int8_t jjPLAYER::set_health(std::int8_t value) { noop(); _player->SetHealth(value); return value; } std::int32_t jjPLAYER::get_fastfire() const { noop(); return (_player->_weaponUpgrades[(std::int32_t)WeaponType::Blaster] >> 1); } std::int32_t jjPLAYER::set_fastfire(std::int32_t value) { noop(); _player->_weaponUpgrades[(std::int32_t)WeaponType::Blaster] = (std::uint8_t)((_player->_weaponUpgrades[(std::int32_t)WeaponType::Blaster] & 0x1) | (value << 1)); return value; } std::int8_t jjPLAYER::get_currWeapon() const { noop(); return (std::int8_t)_player->_currentWeapon; } std::int8_t jjPLAYER::set_currWeapon(std::int8_t value) { noop(); if (value < 0 || value >= (int8_t)WeaponType::Count) { return (std::int8_t)_player->_currentWeapon; } _player->_currentWeapon = (WeaponType)value; return value; } /*std::int32_t jjPLAYER::get_lives() const { noop(); return _player->_lives; } std::int32_t jjPLAYER::set_lives(std::int32_t value) { noop(); _player->_lives = value; return _player->_lives; }*/ std::int32_t jjPLAYER::get_invincibility() const { noop(); return 0; } std::int32_t jjPLAYER::set_invincibility(std::int32_t value) { noop(); return 0; } std::int32_t jjPLAYER::get_blink() const { noop(); return 0; } std::int32_t jjPLAYER::set_blink(std::int32_t value) { noop(); return 0; } std::int32_t jjPLAYER::extendInvincibility(std::int32_t duration) { noop(); return 0; } std::int32_t jjPLAYER::get_food() const { noop(); return _player->_foodEaten; } std::int32_t jjPLAYER::set_food(std::int32_t value) { noop(); _player->_foodEaten = value; return _player->_foodEaten; } std::int32_t jjPLAYER::get_coins() const { noop(); return _player->_coins; } std::int32_t jjPLAYER::set_coins(std::int32_t value) { noop(); _player->_coins = value; return _player->_coins; } bool jjPLAYER::testForCoins(std::int32_t numberOfCoins) { noop(); if (numberOfCoins > _player->_coins) { return false; } _player->AddCoins(-numberOfCoins); return true; } std::int32_t jjPLAYER::get_gems(std::uint32_t type) const { noop(); return (type < arraySize(_player->_gems) ? _player->_gems[type] : 0); } std::int32_t jjPLAYER::set_gems(std::uint32_t type, std::int32_t value) { noop(); if (type < arraySize(_player->_gems)) { _player->_gems[type] = value; } return (type < arraySize(_player->_gems) ? _player->_gems[type] : 0); } bool jjPLAYER::testForGems(std::int32_t numberOfGems, std::uint32_t type) { noop(); std::uint8_t currentCount = (type < arraySize(_player->_gems) ? _player->_gems[type] : 0); if (numberOfGems <= currentCount) { if (numberOfGems > 0) { _player->_gems[type] -= numberOfGems; } return true; } // TODO: Show warning message return false; } std::int32_t jjPLAYER::get_shieldType() const { noop(); return (std::int32_t)_player->_activeShield; } std::int32_t jjPLAYER::set_shieldType(std::int32_t value) { noop(); if (value >= 0 && value < (std::int32_t)ShieldType::Count) { _player->_activeShield = (ShieldType)value; } return value; } std::int32_t jjPLAYER::get_shieldTime() const { noop(); return (std::int32_t)(_player->_activeShieldTime * 70.0f / FrameTimer::FramesPerSecond); } std::int32_t jjPLAYER::set_shieldTime(std::int32_t value) { noop(); _player->_activeShieldTime = value * FrameTimer::FramesPerSecond / 70.0f; return value; } std::int32_t jjPLAYER::get_rolling() const { noop(); return (std::int32_t)(_player->_inTubeTime * 70.0f / FrameTimer::FramesPerSecond); } std::int32_t jjPLAYER::set_rolling(std::int32_t value) { noop(); if (value > 0) { if (_player->_inTubeTime <= 0) { _player->EndDamagingMove(); _player->SetAnimation(AnimState::Dash | AnimState::Jump); _player->_controllable = false; _player->SetState(Actors::ActorState::CanJump | Actors::ActorState::ApplyGravitation, false); } _player->_inTubeTime = value * FrameTimer::FramesPerSecond / 70.0f; } else { if (_player->_inTubeTime > 0) { _player->_controllable = true; _player->SetState(Actors::ActorState::ApplyGravitation | Actors::ActorState::CollideWithTileset, true); _player->_inTubeTime = 0.0f; } } return value; } std::int8_t jjPLAYER::get_playerID() const { noop(); return (std::int8_t)_player->_playerIndex; } std::int32_t jjPLAYER::get_localPlayerID() const { noop(); return _player->_playerIndex; } bool jjPLAYER::get_running() const { noop(); return _player->_isRunPressed; } bool jjPLAYER::set_running(bool value) { noop(); _player->_isRunPressed = value; return _player->_isRunPressed; } std::int32_t jjPLAYER::get_stoned() { noop(); return (std::int32_t)(_player->_dizzyTime * 70.0f / FrameTimer::FramesPerSecond); } std::int32_t jjPLAYER::set_stoned(std::int32_t value) { noop(); _player->SetDizzy(value * FrameTimer::FramesPerSecond / 70.0f); return value; } void jjPLAYER::suckerTube(std::int32_t xSpeed, std::int32_t ySpeed, bool center, bool noclip, bool trigSample) { noop(); } void jjPLAYER::poleSpin(float xSpeed, float ySpeed, std::uint32_t delay) { noop(); } void jjPLAYER::spring(float xSpeed, float ySpeed, bool keepZeroSpeeds, bool sample) { noop(); } bool jjPLAYER::get_isConnecting() const { noop(); // TODO Whether the player is a client who has not finished joining the current online server yet. return false; } bool jjPLAYER::get_isIdle() const { noop(); // TODO: Whether the player is idle and does not appear in the level or player list. Currently this can only ever be true of the server. return false; } bool jjPLAYER::get_isOut() const { noop(); // TODO: Equals true if the player has lost all their lives (or joined too late) in an LRS-based gamemode. return false; } bool jjPLAYER::get_isSpectating() const { noop(); // TODO: Equals true if the player is spectating normally, i.e. not forced into spectating by being out or an idle server. return false; } bool jjPLAYER::get_isInGame() const { noop(); // TODO: Equals true if isActive is true but isConnecting, isIdle, isOut, and isSpectating are all false. return true; } String jjPLAYER::get_name() const { noop(); return {}; } String jjPLAYER::get_nameUnformatted() const { noop(); return {}; } bool jjPLAYER::setName(const String& name) { noop(); return false; } std::int8_t jjPLAYER::get_light() const { noop(); return 0; } std::int8_t jjPLAYER::set_light(std::int8_t value) { noop(); return 0; } std::uint32_t jjPLAYER::get_fur() const { noop(); return 0; } std::uint32_t jjPLAYER::set_fur(std::uint32_t value) { noop(); return 0; } void jjPLAYER::getFur(std::uint8_t& a, std::uint8_t& b, std::uint8_t& c, std::uint8_t& d) const { noop(); } void jjPLAYER::setFur(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d) { noop(); } bool jjPLAYER::get_noFire() const { noop(); return !_player->_weaponAllowed; } bool jjPLAYER::set_noFire(bool value) { noop(); _player->_weaponAllowed = !value; return value; } bool jjPLAYER::get_antiGrav() const { noop(); return false; } bool jjPLAYER::set_antiGrav(bool value) { noop(); return false; } bool jjPLAYER::get_invisibility() const { noop(); return _player->GetState(Actors::ActorState::IsInvulnerable); } bool jjPLAYER::set_invisibility(bool value) { noop(); return false; } bool jjPLAYER::get_noclipMode() const { noop(); return false; } bool jjPLAYER::set_noclipMode(bool value) { noop(); return false; } std::uint8_t jjPLAYER::get_lighting() const { noop(); // TODO: Viewports return (std::uint8_t)(_levelScriptLoader->_levelHandler->GetAmbientLight(_player) * 64.0f); } std::uint8_t jjPLAYER::set_lighting(std::uint8_t value) { noop(); _levelScriptLoader->_levelHandler->SetAmbientLight(_player, value / 64.0f); return value; } std::uint8_t jjPLAYER::resetLight() { noop(); return 0; } bool jjPLAYER::get_playerKeyLeftPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Left); } bool jjPLAYER::get_playerKeyRightPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Right); } bool jjPLAYER::get_playerKeyUpPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Up); } bool jjPLAYER::get_playerKeyDownPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Down); } bool jjPLAYER::get_playerKeyFirePressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Fire); } bool jjPLAYER::get_playerKeySelectPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::ChangeWeapon); } bool jjPLAYER::get_playerKeyJumpPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Jump); } bool jjPLAYER::get_playerKeyRunPressed() { noop(); return _player->_levelHandler->PlayerActionPressed(_player, PlayerAction::Run); } void jjPLAYER::set_playerKeyLeftPressed(bool value) { noop(); } void jjPLAYER::set_playerKeyRightPressed(bool value) { noop(); } void jjPLAYER::set_playerKeyUpPressed(bool value) { noop(); } void jjPLAYER::set_playerKeyDownPressed(bool value) { noop(); } void jjPLAYER::set_playerKeyFirePressed(bool value) { noop(); } void jjPLAYER::set_playerKeySelectPressed(bool value) { noop(); } void jjPLAYER::set_playerKeyJumpPressed(bool value) { noop(); } void jjPLAYER::set_playerKeyRunPressed(bool value) { noop(); } bool jjPLAYER::get_powerup(std::uint8_t index) { noop(); if (index < 0 || index >= (std::int8_t)WeaponType::Count) { return 0; } return (_player->_weaponUpgrades[index] & 0x01) == 0x01; } bool jjPLAYER::set_powerup(std::uint8_t index, bool value) { noop(); if (index < 0 || index >= (std::int8_t)WeaponType::Count) { return 0; } _player->_weaponUpgrades[index] = (value ? 0x01 : 0x00); return value; } std::int32_t jjPLAYER::get_ammo(std::uint8_t index) const { noop(); if (index < 0 || index >= (std::int8_t)WeaponType::Count) { return 0; } return _player->_weaponAmmo[index]; } std::int32_t jjPLAYER::set_ammo(std::uint8_t index, std::int32_t value) { noop(); if (index < 0 || index >= (std::int8_t)WeaponType::Count) { return 0; } _player->_weaponAmmo[index] = value * 256; return value; } bool jjPLAYER::offsetPosition(std::int32_t xPixels, std::int32_t yPixels) { noop(); Vector2f pos = _player->GetPos(); _player->WarpToPosition(Vector2f(pos.X + xPixels, pos.Y + yPixels), WarpFlags::Fast); return true; } bool jjPLAYER::warpToTile(std::int32_t xTile, std::int32_t yTile, bool fast) { noop(); _player->WarpToPosition(Vector2f(xTile * TileSet::DefaultTileSize + Tiles::TileSet::DefaultTileSize / 2, yTile * TileSet::DefaultTileSize + Tiles::TileSet::DefaultTileSize / 2), fast ? WarpFlags::Fast : WarpFlags::Default); return true; } bool jjPLAYER::warpToID(std::uint8_t warpID, bool fast) { noop(); auto events = _levelScriptLoader->_levelHandler->EventMap(); Vector2f c = events->GetWarpTarget(warpID); if (c.X >= 0.0f && c.Y >= 0.0f) { _player->WarpToPosition(c, fast ? WarpFlags::Fast : WarpFlags::Default); return true; } return false; } std::uint32_t jjPLAYER::morph(bool rabbitsOnly, bool morphEffect) { noop(); return 0; } std::uint32_t jjPLAYER::morphTo(std::uint32_t charNew, bool morphEffect) { noop(); // TODO: morphEffect _player->MorphTo((PlayerType)charNew); return (std::uint32_t)_player->_playerType; } std::uint32_t jjPLAYER::revertMorph(bool morphEffect) { noop(); // TODO: morphEffect _player->MorphRevert(); return (std::uint32_t)_player->_playerType; } std::uint32_t jjPLAYER::get_charCurr() const { noop(); return (std::uint32_t)_player->_playerType; } void jjPLAYER::kill() { noop(); _player->DecreaseHealth(INT32_MAX); } bool jjPLAYER::hurt(std::int8_t damage, bool forceHurt, jjPLAYER* attacker) { noop(); // TODO: forceHurt and return value _player->TakeDamage(damage); return false; } std::uint32_t jjPLAYER::get_timerState() const { noop(); return _timerState; } bool jjPLAYER::get_timerPersists() const { noop(); return _timerPersists; } bool jjPLAYER::set_timerPersists(bool value) { noop(); _timerPersists = value; return value; } std::uint32_t jjPLAYER::timerStart(std::int32_t ticks, bool startPaused) { noop(); _timerLeft = (float)ticks * FrameTimer::FramesPerSecond / 70; _timerState = (startPaused ? 2 : 1); return _timerState; } std::uint32_t jjPLAYER::timerPause() { noop(); _timerState = 2; // PAUSED return _timerState; } std::uint32_t jjPLAYER::timerResume() { noop(); _timerState = 1; // STARTED return _timerState; } std::uint32_t jjPLAYER::timerStop() { noop(); _timerLeft = 0.0f; _timerState = 0; // STOPPED return _timerState; } std::int32_t jjPLAYER::get_timerTime() const { noop(); std::int32_t jjTicksLeft = (std::int32_t)(_timerLeft * 70 / FrameTimer::FramesPerSecond); return jjTicksLeft; } std::int32_t jjPLAYER::set_timerTime(std::int32_t value) { noop(); _timerLeft = (float)value * FrameTimer::FramesPerSecond / 70; return value; } void jjPLAYER::timerFunction(const String& functionName) { noop(); asIScriptFunction* func = _levelScriptLoader->GetMainModule()->GetFunctionByName(String::nullTerminatedView(functionName).data()); _timerCallback = func; } void jjPLAYER::timerFunctionPtr(void* function) { noop(); _timerCallback = function; } void jjPLAYER::timerFunctionFuncPtr(void* function) { noop(); _timerCallback = function; } bool jjPLAYER::activateBoss(bool activate) { noop(); // TODO: activate _levelScriptLoader->_levelHandler->BroadcastTriggeredEvent(_player, EventType::AreaActivateBoss, nullptr); return true; } bool jjPLAYER::limitXScroll(std::uint16_t left, std::uint16_t width) { noop(); _levelScriptLoader->_levelHandler->LimitCameraView(_player, _player->_pos, left * Tiles::TileSet::DefaultTileSize, width * Tiles::TileSet::DefaultTileSize); return true; } void jjPLAYER::cameraFreezeFF(float xPixel, float yPixel, bool centered, bool instant) { noop(); // TODO: instant _levelScriptLoader->_levelHandler->OverrideCameraView(_player, xPixel, yPixel, !centered); } void jjPLAYER::cameraFreezeBF(bool xUnfreeze, float yPixel, bool centered, bool instant) { noop(); // TODO: instant _levelScriptLoader->_levelHandler->OverrideCameraView(_player, INFINITY, yPixel, !centered); } void jjPLAYER::cameraFreezeFB(float xPixel, bool yUnfreeze, bool centered, bool instant) { noop(); // TODO: instant _levelScriptLoader->_levelHandler->OverrideCameraView(_player, xPixel, INFINITY, !centered); } void jjPLAYER::cameraFreezeBB(bool xUnfreeze, bool yUnfreeze, bool centered, bool instant) { noop(); // TODO: instant, centered _levelScriptLoader->_levelHandler->OverrideCameraView(_player, INFINITY, INFINITY, false); } void jjPLAYER::cameraUnfreeze(bool instant) { noop(); // TODO: instant _levelScriptLoader->_levelHandler->OverrideCameraView(_player, INFINITY, INFINITY, false); } void jjPLAYER::showText(const String& text, std::uint32_t size) { noop(); // TODO: size // Input string must be recoded in Legacy context auto recodedText = Compatibility::JJ2Strings::RecodeString(text); _levelScriptLoader->_levelHandler->ShowLevelText(recodedText); } void jjPLAYER::showTextByID(std::uint32_t textID, std::uint32_t offset, std::uint32_t size) { noop(); // TODO: size auto text = _levelScriptLoader->_levelHandler->GetLevelText(textID, offset, '|'); _levelScriptLoader->_levelHandler->ShowLevelText(text); } std::uint32_t jjPLAYER::get_fly() const { noop(); return 0; } std::uint32_t jjPLAYER::set_fly(std::uint32_t value) { noop(); return 0; } std::int32_t jjPLAYER::fireBulletDirection(std::uint8_t gun, bool depleteAmmo, bool requireAmmo, std::uint32_t direction) { noop(); return 0; } std::int32_t jjPLAYER::fireBulletAngle(std::uint8_t gun, bool depleteAmmo, bool requireAmmo, float angle) { noop(); return 0; } float jjPLAYER::get_cameraX() const { noop(); return _levelScriptLoader->_levelHandler->GetCameraPos(_player).X; } float jjPLAYER::get_cameraY() const { noop(); return _levelScriptLoader->_levelHandler->GetCameraPos(_player).Y; } std::int32_t jjPLAYER::get_deaths() const { noop(); return 0; } bool jjPLAYER::get_isJailed() const { noop(); return false; } bool jjPLAYER::get_isZombie() const { noop(); return false; } std::int32_t jjPLAYER::get_lrsLives() const { noop(); return 0; } std::int32_t jjPLAYER::get_roasts() const { noop(); return 0; } std::int32_t jjPLAYER::get_laps() const { noop(); return 0; } std::int32_t jjPLAYER::get_lapTimeCurrent() const { noop(); return 0; } std::int32_t jjPLAYER::get_lapTimes(std::uint32_t index) const { noop(); return 0; } std::int32_t jjPLAYER::get_lapTimeBest() const { noop(); return 0; } bool jjPLAYER::get_isAdmin() const { noop(); return false; } bool jjPLAYER::hasPrivilege(const String& privilege, std::uint32_t moduleID) const { noop(); return false; } bool jjPLAYER::doesCollide(const jjOBJ* object, bool always) const { noop(); return false; } int jjPLAYER::getObjectHitForce(const jjOBJ& target) const { noop(); return 0; } bool jjPLAYER::objectHit(jjOBJ* target, std::int32_t force, std::uint32_t playerHandling) { noop(); return false; } bool jjPLAYER::isEnemy(const jjPLAYER* victim) const { noop(); return false; } void jjPLAYER::SyncPropertiesToBackingStore() { // TODO _backingStoreDirty = true; lives = _player->_lives; } void jjPLAYER::SyncPropertiesFromBackingStore() { // TODO if (!_backingStoreDirty) { return; } if (lives != _player->_lives) { LOGW("SYNC"); } lives = _player->_lives; } jjPIXELMAP::jjPIXELMAP() : _refCount(1) { noop(); } jjPIXELMAP::~jjPIXELMAP() { noop(); } jjPIXELMAP* jjPIXELMAP::CreateFromTile() { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } jjPIXELMAP* jjPIXELMAP::CreateFromSize(std::uint32_t width, std::uint32_t height) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } jjPIXELMAP* jjPIXELMAP::CreateFromFrame(const jjANIMFRAME* animFrame) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } jjPIXELMAP* jjPIXELMAP::CreateFromLayer(std::uint32_t left, std::uint32_t top, std::uint32_t width, std::uint32_t height, std::uint32_t layer) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } jjPIXELMAP* jjPIXELMAP::CreateFromLayerObject(std::uint32_t left, std::uint32_t top, std::uint32_t width, std::uint32_t height, const jjLAYER* layer) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } jjPIXELMAP* jjPIXELMAP::CreateFromTexture(std::uint32_t animFrame) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } jjPIXELMAP* jjPIXELMAP::CreateFromFilename(const String& filename, const jjPAL* palette, std::uint8_t threshold) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPIXELMAP)); return new(mem) jjPIXELMAP(); } void jjPIXELMAP::AddRef() { _refCount++; } void jjPIXELMAP::Release() { if (--_refCount == 0) { this->~jjPIXELMAP(); asFreeMem(this); } } // Assignment operator jjPIXELMAP& jjPIXELMAP::operator=(const jjPIXELMAP& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } // TODO: return type std::uint8_t& instead? std::uint8_t jjPIXELMAP::GetPixel(std::uint32_t x, std::uint32_t y) { noop(); return 0; } bool jjPIXELMAP::saveToTile(std::uint16_t tileID, bool hFlip) const { noop(); return false; } bool jjPIXELMAP::saveToFrame(jjANIMFRAME* frame) const { noop(); return false; } bool jjPIXELMAP::saveToFile(const String& filename, const jjPAL& palette) const { noop(); return false; } jjMASKMAP::jjMASKMAP() : _refCount(1) { noop(); } jjMASKMAP::~jjMASKMAP() { noop(); } jjMASKMAP* jjMASKMAP::CreateFromBool(bool filled) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjMASKMAP)); return new(mem) jjMASKMAP(); } jjMASKMAP* jjMASKMAP::CreateFromTile(std::uint16_t tileID) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjMASKMAP)); return new(mem) jjMASKMAP(); } void jjMASKMAP::AddRef() { _refCount++; } void jjMASKMAP::Release() { if (--_refCount == 0) { this->~jjMASKMAP(); asFreeMem(this); } } jjMASKMAP& jjMASKMAP::operator=(const jjMASKMAP& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } // TODO: return type bool& instead? bool jjMASKMAP::GetPixel(std::uint32_t x, std::uint32_t y) { noop(); return false; } bool jjMASKMAP::save(std::uint16_t tileID, bool hFlip) const { noop(); return false; } jjLAYER::jjLAYER() : _refCount(1) { noop(); } jjLAYER::~jjLAYER() { noop(); } jjLAYER* jjLAYER::CreateFromSize(std::uint32_t width, std::uint32_t height) { noop(); void* mem = asAllocMem(sizeof(jjLAYER)); return new(mem) jjLAYER(); } jjLAYER* jjLAYER::CreateCopy(jjLAYER* other) { noop(); void* mem = asAllocMem(sizeof(jjLAYER)); return new(mem) jjLAYER(); } void jjLAYER::AddRef() { _refCount++; } void jjLAYER::Release() { if (--_refCount == 0) { this->~jjLAYER(); asFreeMem(this); } } jjLAYER& jjLAYER::operator=(const jjLAYER& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } jjLAYER* jjLAYER::get_jjLayers(std::int32_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjLAYER)); return new(mem) jjLAYER(); } std::uint32_t jjLAYER::get_spriteMode() const { noop(); return 0; } std::uint32_t jjLAYER::set_spriteMode(std::uint32_t value) const { noop(); return 0; } std::uint8_t jjLAYER::get_spriteParam() const { noop(); return 0; } std::uint8_t jjLAYER::set_spriteParam(std::uint8_t value) const { noop(); return 0; } void jjLAYER::setXSpeed(float newspeed, bool newSpeedIsAnAutoSpeed) const { noop(); } void jjLAYER::setYSpeed(float newspeed, bool newSpeedIsAnAutoSpeed) const { noop(); } float jjLAYER::getXPosition(const jjPLAYER* play) const { noop(); return 0; } float jjLAYER::getYPosition(const jjPLAYER* play) const { noop(); return 0; } std::int32_t jjLAYER::GetTextureMode() const { noop(); return 0; } void jjLAYER::SetTextureMode(std::int32_t value) const { noop(); } std::int32_t jjLAYER::GetTexture() const { noop(); return 0; } void jjLAYER::SetTexture(std::int32_t value) const { noop(); } CScriptArray* jjLAYER::jjLayerOrderGet() { noop(); auto owner = ScriptLoader::FromActiveContext(); return CScriptArray::Create(owner->GetEngine()->GetTypeInfoByDecl("array"), 16); } bool jjLAYER::jjLayerOrderSet(const CScriptArray& order) { noop(); return false; } CScriptArray* jjLAYER::jjLayersFromLevel(const String& filename, const CScriptArray& layerIDs, std::int32_t tileIDAdjustmentFactor) { noop(); auto owner = ScriptLoader::FromActiveContext(); return CScriptArray::Create(owner->GetEngine()->GetTypeInfoByDecl("array"), 16); } bool jjLAYER::jjTilesFromTileset(const String& filename, std::uint32_t firstTileID, std::uint32_t tileCount, const CScriptArray* paletteColorMapping) { noop(); return false; } std::uint16_t jjLAYER::tileGet(int xTile, int yTile) { noop(); return 0; } std::uint16_t jjLAYER::tileSet(int xTile, int yTile, std::uint16_t newTile) { noop(); return 0; } void jjLAYER::generateSettableTileArea(int xTile, int yTile, int width, int height) { noop(); } void jjLAYER::generateSettableTileAreaAll() { noop(); } bool jjPLAYERDRAW::get_shield(std::int32_t shield) const { noop(); return false; } bool jjPLAYERDRAW::set_shield(std::int32_t shield, bool enable) { noop(); return false; } jjPLAYER* jjPLAYERDRAW::get_player() const { noop(); return nullptr; } } jjOBJ* get_jjObjects(std::int32_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjOBJ)); return new(mem) jjOBJ(); } jjOBJ* get_jjObjectPresets(std::int8_t id) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjOBJ)); return new(mem) jjOBJ(); } void jjAddParticleTileExplosion(std::uint16_t xTile, std::uint16_t yTile, std::uint16_t tile, bool collapseSceneryStyle) { noop(); } void jjAddParticlePixelExplosion(float xPixel, float yPixel, int curFrame, int direction, int mode) { noop(); } jjPARTICLE* GetParticle(std::int32_t index) { noop(); // TODO auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPARTICLE)); return new(mem) jjPARTICLE(); } jjPARTICLE* AddParticle(std::int32_t particleType) { noop(); auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(jjPARTICLE)); return new(mem) jjPARTICLE(); } std::int32_t get_jjPlayerCount() { // TODO auto owner = ScriptLoader::FromActiveContext(); return owner->GetPlayers().size(); } std::int32_t get_jjLocalPlayerCount() { // TODO auto owner = ScriptLoader::FromActiveContext(); return owner->GetPlayers().size(); } jjPLAYER* get_jjP() { noop(); auto owner = ScriptLoader::FromActiveContext(); return owner->GetPlayerBackingStore(0); } jjPLAYER* get_jjPlayers(std::uint8_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); return owner->GetPlayerBackingStore(index); } jjPLAYER* get_jjLocalPlayers(std::uint8_t index) { noop(); auto owner = ScriptLoader::FromActiveContext(); return owner->GetPlayerBackingStore(index); } bool mlleSetup() { noop(); return true; } void mlleReapplyPalette() { noop(); } void mlleSpawnOffgrids() { noop(); } void mlleSpawnOffgridsLocal() { noop(); } float get_sinTable(std::uint32_t angle) { noop(); return sinf(angle * fTwoPi / 1024.0f); }; float get_cosTable(std::uint32_t angle) { noop(); return cosf(angle * fTwoPi / 1024.0f); }; std::uint32_t RandWord32() { noop(); return Random().Next(); } std::uint64_t unixTimeSec() { noop(); return Containers::DateTime::UtcNow().ToUnixMilliseconds(); } std::uint64_t unixTimeMs() { noop(); return Containers::DateTime::UtcNow().ToUnixMilliseconds() / 1000; } bool jjRegexIsValid(const String& expression) { noop(); try { std::regex r(expression.begin(), expression.end()); return true; } catch (std::regex_error& e) { return false; } } bool jjRegexMatch(const String& text, const String& expression, bool ignoreCase) { noop(); try { auto flags = std::regex_constants::ECMAScript; if (ignoreCase) { flags |= std::regex_constants::icase; } std::regex r(expression.begin(), expression.end(), flags); return std::regex_match(text.begin(), text.end(), r); } catch (std::regex_error& e) { LOGE("Failed to process regular expression: {}", e.what()); return false; } } bool jjRegexMatchWithResults(const String& text, const String& expression, CScriptArray& results, bool ignoreCase) { noop(); try { auto flags = std::regex_constants::ECMAScript; if (ignoreCase) { flags |= std::regex_constants::icase; } std::match_results m; std::regex r(expression.begin(), expression.end(), flags); bool success = std::regex_match(text.begin(), text.end(), m, r); // TODO: Results return success; } catch (std::regex_error& e) { LOGE("Failed to process regular expression: {}", e.what()); return false; } } bool jjRegexSearch(const String& text, const String& expression, bool ignoreCase) { noop(); try { auto flags = std::regex_constants::ECMAScript; if (ignoreCase) { flags |= std::regex_constants::icase; } std::regex r(expression.begin(), expression.end(), flags); return std::regex_search(text.begin(), text.end(), r); } catch (std::regex_error& e) { LOGE("Failed to process regular expression: {}", e.what()); return false; } } bool jjRegexSearchWithResults(const String& text, const String& expression, CScriptArray& results, bool ignoreCase) { noop(); try { auto flags = std::regex_constants::ECMAScript; if (ignoreCase) { flags |= std::regex_constants::icase; } std::match_results m; std::regex r(expression.begin(), expression.end(), flags); bool success = std::regex_search(text.begin(), text.end(), m, r); // TODO: Results return success; } catch (std::regex_error& e) { LOGE("Failed to process regular expression: {}", e.what()); return false; } } String jjRegexReplace(const String& text, const String& expression, const String& replacement, bool ignoreCase) { noop(); try { auto flags = std::regex_constants::ECMAScript; if (ignoreCase) { flags |= std::regex_constants::icase; } std::regex r(expression.begin(), expression.end(), flags); std::string textStr = text; std::string replacementStr = replacement; return std::regex_replace(textStr, r, replacementStr); } catch (std::regex_error& e) { LOGE("Failed to process regular expression: {}", e.what()); return text; } } std::int32_t GetFPS() { noop(); return (std::int32_t)theApplication().GetFrameTimer().GetAverageFps(); } bool isAdmin() { noop(); return false; } std::int32_t LevelScriptLoader::GetDifficulty() { noop(); auto _this = ScriptLoader::FromActiveContext(); switch (_this->_levelHandler->_difficulty) { case GameDifficulty::Easy: return 0; default: case GameDifficulty::Normal: return 1; case GameDifficulty::Hard: return 2; } } std::int32_t LevelScriptLoader::SetDifficulty(std::int32_t value) { noop(); if (value >= 0 && value <= 2) { auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->_difficulty = (GameDifficulty)value; } return value; } String getLevelFileName() { noop(); return ""; } String getCurrLevelName() { noop(); return ""; } void setCurrLevelName(const String& in) { noop(); } String LevelScriptLoader::get_jjMusicFileName() { noop(); auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_musicCurrentPath; } String get_jjTilesetFileName() { noop(); return ""; } String LevelScriptLoader::get_jjHelpStrings(std::uint32_t index) { noop(); auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->GetLevelText(index); } void LevelScriptLoader::set_jjHelpStrings(std::uint32_t index, const String& text) { noop(); auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->OverrideLevelText(index, text); } std::int32_t get_gameState() { noop(); return 0; } // TODO void LevelScriptLoader::jjAlert(const String& text, bool sendToAll, std::uint32_t size) { auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->ShowLevelText(text); } void LevelScriptLoader::jjPrint(const String& text, bool timestamp) { LOGW("{}", text); } void LevelScriptLoader::jjDebug(const String& text, bool timestamp) { LOGD("{}", text); } void LevelScriptLoader::jjChat(const String& text, bool teamchat) { LOGW("{}", text); if (text.hasPrefix('/')) { // TODO: Process command return; } // TODO: teamchat auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->_console->WriteLine(UI::MessageLevel::Info, text); } void LevelScriptLoader::jjConsole(const String& text, bool sendToAll) { LOGW("{}", text); // TODO: sendToAll auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->_console->WriteLine(UI::MessageLevel::Important, text); } void LevelScriptLoader::jjSpy(const String& text) { LOGD("{}", text); } // TODO std::int32_t getBorderWidth() { noop(); return 0; } std::int32_t getBorderHeight() { noop(); return 0; } bool getSplitscreenType() { noop(); return false; } bool setSplitscreenType() { noop(); return false; } // TODO std::int32_t get_teamScore(std::int32_t color) { noop(); return 0; } std::int32_t GetMaxHealth() { noop(); return 0; } std::int32_t GetStartHealth() { noop(); return 0; } // TODO float LevelScriptLoader::get_layerXOffset(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].Description.OffsetX : 0); } float LevelScriptLoader::set_layerXOffset(std::uint8_t id, float value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.OffsetX = value; return value; } else { return 0.0f; } } float LevelScriptLoader::get_layerYOffset(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].Description.OffsetY : 0); } float LevelScriptLoader::set_layerYOffset(std::uint8_t id, float value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.OffsetY = value; return value; } else { return 0.0f; } } std::int32_t LevelScriptLoader::get_layerWidth(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].LayoutSize.X : 0); } std::int32_t LevelScriptLoader::get_layerRealWidth(std::uint8_t id) { noop(); return get_layerWidth(id); } std::int32_t LevelScriptLoader::get_layerRoundedWidth(std::uint8_t id) { noop(); return (get_layerWidth(id) + 3) / 4; } std::int32_t LevelScriptLoader::get_layerHeight(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].LayoutSize.Y : 0); } float LevelScriptLoader::get_layerXSpeed(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].Description.SpeedX : 0.0f); } float LevelScriptLoader::set_layerXSpeed(std::uint8_t id, float value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.SpeedX = value; return value; } else { return 0.0f; } } float LevelScriptLoader::get_layerYSpeed(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].Description.SpeedY : 0.0f); } float LevelScriptLoader::set_layerYSpeed(std::uint8_t id, float value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.SpeedY = value; return value; } else { return 0.0f; } } float LevelScriptLoader::get_layerXAutoSpeed(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].Description.AutoSpeedX : 0.0f); } float LevelScriptLoader::set_layerXAutoSpeed(std::uint8_t id, float value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.AutoSpeedX = value; return value; } else { return 0.0f; } } float LevelScriptLoader::get_layerYAutoSpeed(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() ? tileMap->_layers[id].Description.AutoSpeedY : 0.0f); } float LevelScriptLoader::set_layerYAutoSpeed(std::uint8_t id, float value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.AutoSpeedY = value; return value; } else { return 0.0f; } } bool LevelScriptLoader::get_layerHasTiles(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() && tileMap->_layers[id].Visible); } bool LevelScriptLoader::set_layerHasTiles(std::uint8_t id, bool value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Visible = value; return value; } else { return false; } } bool LevelScriptLoader::get_layerTileHeight(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() && tileMap->_layers[id].Description.RepeatY); } bool LevelScriptLoader::set_layerTileHeight(std::uint8_t id, bool value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.RepeatY = value; return value; } else { return false; } } bool LevelScriptLoader::get_layerTileWidth(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() && tileMap->_layers[id].Description.RepeatX); } bool LevelScriptLoader::set_layerTileWidth(std::uint8_t id, bool value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.RepeatX = value; return value; } else { return false; } } bool LevelScriptLoader::get_layerLimitVisibleRegion(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); return (id < tileMap->_layers.size() && tileMap->_layers[id].Description.UseInherentOffset); } bool LevelScriptLoader::set_layerLimitVisibleRegion(std::uint8_t id, bool value) { noop(); auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { tileMap->_layers[id].Description.UseInherentOffset = value; return value; } else { return false; } } void LevelScriptLoader::setLayerXSpeedSeamlessly(std::uint8_t id, float newspeed, bool newSpeedIsAnAutoSpeed) { noop(); // TODO: Shortcut global functions for setXSpeed and setYSpeed on the same-indexed jjLayers objects. // TODO: Changes the X or Y speed.Unlike the basic properties like xSpeed and yAutoSpeed, // these functions will ensure that the layer remains in the same position it was before its speeds were changed. auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { if (newSpeedIsAnAutoSpeed) { tileMap->_layers[id].Description.AutoSpeedX = newspeed; } else { tileMap->_layers[id].Description.SpeedX = newspeed; } } } void LevelScriptLoader::setLayerYSpeedSeamlessly(std::uint8_t id, float newspeed, bool newSpeedIsAnAutoSpeed) { noop(); // TODO: Shortcut global functions for setXSpeed and setYSpeed on the same-indexed jjLayers objects. // TODO: Changes the X or Y speed.Unlike the basic properties like xSpeed and yAutoSpeed, // these functions will ensure that the layer remains in the same position it was before its speeds were changed. auto _this = ScriptLoader::FromActiveContext(); auto tileMap = _this->_levelHandler->_tileMap.get(); if (id < tileMap->_layers.size()) { if (newSpeedIsAnAutoSpeed) { tileMap->_layers[id].Description.AutoSpeedY = newspeed; } else { tileMap->_layers[id].Description.SpeedY = newspeed; } } } // TODO void LevelScriptLoader::jjDrawPixel(float xPixel, float yPixel, std::uint8_t color, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawRectangle(float xPixel, float yPixel, std::int32_t width, std::int32_t height, std::uint8_t color, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawSprite(float xPixel, float yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, std::int8_t direction, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, std::int8_t direction, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawResizedSprite(float xPixel, float yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawResizedSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawRotatedSprite(float xPixel, float yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, std::int32_t angle, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawRotatedSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, std::int32_t angle, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawSwingingVineSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, std::int32_t length, std::int32_t curvature, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawTile(float xPixel, float yPixel, std::uint16_t tile, std::uint32_t tileQuadrant, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawString(float xPixel, float yPixel, const String& text, std::uint32_t size, std::uint32_t mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } void LevelScriptLoader::jjDrawStringEx(float xPixel, float yPixel, const String& text, std::uint32_t size, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, spriteType spriteMode, std::uint8_t param2, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID) { noop(); } std::int32_t LevelScriptLoader::jjGetStringWidth(const String& text, std::uint32_t size, const jjTEXTAPPEARANCE& style) { //noop(); float scale; switch (size) { default: case 0: // MEDIUM scale = 0.8f; case 1: // SMALL scale = 0.6f; case 2: // LARGE scale = 1.1f; } auto _this = ScriptLoader::FromActiveContext(); auto measuredSize = _this->_levelHandler->_hud->_smallFont->MeasureString(text, scale); return (std::int32_t)measuredSize.X; } void jjSetDarknessColor(jjPALCOLOR color) { noop(); } void jjSetFadeColors(std::uint8_t red, std::uint8_t green, std::uint8_t blue) { noop(); } void jjSetFadeColorsFromPalette(std::uint8_t paletteColorID) { noop(); } void jjSetFadeColorsFromPalcolor(jjPALCOLOR color) { noop(); } jjPALCOLOR jjGetFadeColors() { noop(); return {}; } void jjUpdateTexturedBG() { noop(); } std::int32_t get_jjTexturedBGTexture(jjPALCOLOR color) { noop(); return 0; } std::int32_t set_jjTexturedBGTexture(std::int32_t texture) { noop(); return 0; } std::int32_t get_jjTexturedBGStyle() { noop(); return 0; } std::int32_t set_jjTexturedBGStyle(std::int32_t style) { noop(); return 0; } bool get_jjTexturedBGUsed(jjPALCOLOR color) { noop(); return false; } bool set_jjTexturedBGUsed(bool used) { noop(); return false; } bool get_jjTexturedBGStars(bool used) { noop(); return false; } bool set_jjTexturedBGStars(bool used) { noop(); return false; } float get_jjTexturedBGFadePositionX() { noop(); return 0.0f; } float set_jjTexturedBGFadePositionX(float value) { noop(); return 0.0f; } float get_jjTexturedBGFadePositionY() { noop(); return 0.0f; } float set_jjTexturedBGFadePositionY(float value) { noop(); return 0.0f; } bool LevelScriptLoader::get_jjTriggers(std::uint8_t id) { //noop(); auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->GetTrigger(id); } bool LevelScriptLoader::set_jjTriggers(std::uint8_t id, bool value) { //noop(); auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->SetTrigger(id, value); return value; } bool LevelScriptLoader::jjSwitchTrigger(std::uint8_t id) { //noop(); auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->SetTrigger(id, !_this->_levelHandler->GetTrigger(id)); return _this->_levelHandler->GetTrigger(id); } bool LevelScriptLoader::isNumberedASFunctionEnabled(std::uint8_t id) { //noop(); auto _this = ScriptLoader::FromActiveContext(); return _this->_enabledCallbacks[id]; } bool LevelScriptLoader::setNumberedASFunctionEnabled(std::uint8_t id, bool value) { //noop(); auto _this = ScriptLoader::FromActiveContext(); _this->_enabledCallbacks.set(id, value); return value; } void LevelScriptLoader::reenableAllNumberedASFunctions() { //noop(); auto _this = ScriptLoader::FromActiveContext(); _this->_enabledCallbacks.setAll(); } float LevelScriptLoader::getWaterLevel() { noop(); // TODO auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_waterLevel; } float LevelScriptLoader::getWaterLevel2() { noop(); // TODO auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_waterLevel; } float LevelScriptLoader::setWaterLevel(float value, bool instant) { noop(); // TODO: instant auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->_waterLevel = value; return value; } float LevelScriptLoader::get_waterChangeSpeed() { noop(); return 0; } float LevelScriptLoader::set_waterChangeSpeed(float value) { noop(); return 0; } std::int32_t LevelScriptLoader::get_waterLayer() { noop(); return 0; } std::int32_t LevelScriptLoader::set_waterLayer(std::int32_t value) { noop(); return 0; } void LevelScriptLoader::setWaterGradient(std::uint8_t red1, std::uint8_t green1, std::uint8_t blue1, std::uint8_t red2, std::uint8_t green2, std::uint8_t blue2) { noop(); } void LevelScriptLoader::setWaterGradientFromColors(jjPALCOLOR color1, jjPALCOLOR color2) { noop(); } void LevelScriptLoader::setWaterGradientToTBG() { noop(); } void LevelScriptLoader::resetWaterGradient() { noop(); } void LevelScriptLoader::triggerRock(std::uint8_t id) { noop(); auto _this = ScriptLoader::FromActiveContext(); std::uint8_t eventParams[] = { id }; _this->_levelHandler->BroadcastTriggeredEvent(nullptr, EventType::RollingRockTrigger, eventParams); } void LevelScriptLoader::cycleTo(const String& filename, bool warp, bool fast) { noop(); } void LevelScriptLoader::jjNxt(bool warp, bool fast) { ExitType exitType = (warp ? ExitType::Warp : ExitType::Normal); if (fast) { exitType |= ExitType::FastTransition; } auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->BeginLevelChange(nullptr, exitType, {}); } bool getEnabledTeam(std::uint8_t team) { noop(); return false; } bool getKeyDown(std::uint8_t key) { //noop(); return false; } std::int32_t getCursorX() { //noop(); return 0; } std::int32_t getCursorY() { //noop(); return 0; } bool LevelScriptLoader::jjMusicLoad(const String& filename, bool forceReload, bool temporary) { noop(); #if defined(WITH_AUDIO) auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->BeginPlayMusic(filename, !temporary, forceReload); #endif return false; } void LevelScriptLoader::jjMusicStop() { noop(); #if defined(WITH_AUDIO) auto _this = ScriptLoader::FromActiveContext(); if (_this->_levelHandler->_music != nullptr) { _this->_levelHandler->_music->stop(); } #endif } void LevelScriptLoader::jjMusicPlay() { noop(); #if defined(WITH_AUDIO) auto _this = ScriptLoader::FromActiveContext(); if (_this->_levelHandler->_music != nullptr) { _this->_levelHandler->_music->play(); } #endif } void LevelScriptLoader::jjMusicPause() { noop(); #if defined(WITH_AUDIO) auto _this = ScriptLoader::FromActiveContext(); if (_this->_levelHandler->_music != nullptr) { _this->_levelHandler->_music->stop(); } #endif } void LevelScriptLoader::jjMusicResume() { noop(); #if defined(WITH_AUDIO) auto _this = ScriptLoader::FromActiveContext(); if (_this->_levelHandler->_music != nullptr && _this->_levelHandler->_music->isPaused()) { _this->_levelHandler->_music->play(); } #endif } void playSample(float xPixel, float yPixel, std::int32_t sample, std::int32_t volume, std::int32_t frequency) { noop(); } int32_t playLoopedSample(float xPixel, float yPixel, std::int32_t sample, std::int32_t volume, std::int32_t frequency) { noop(); return 0; } void playPrioritySample(std::int32_t sample) { noop(); } bool isSampleLoaded(std::int32_t sample) { noop(); return false; } bool loadSample(std::int32_t sample, const String& filename) { noop(); return false; } bool getUseLayer8Speeds() { noop(); return false; } bool setUseLayer8Speeds(bool value) { noop(); return false; } jjWEAPON* get_jjWEAPON(int index) { noop(); static jjWEAPON dummy; return &dummy; } jjCHARACTER* get_jjCHARACTER(int index) { noop(); static jjCHARACTER dummy; return &dummy; } std::int32_t GetEvent(std::uint16_t tx, std::uint16_t ty) { noop(); return 0; } std::int32_t GetEventParamWrapper(std::uint16_t tx, std::uint16_t ty, std::int32_t offset, std::int32_t length) { noop(); return 0; } void SetEventByte(std::uint16_t tx, std::uint16_t ty, std::uint8_t newEventId) { noop(); } void SetEventParam(std::uint16_t tx, std::uint16_t ty, std::int8_t offset, std::int8_t length, std::int32_t newValue) { noop(); } std::int8_t GetTileType(std::uint16_t tile) { noop(); return 0; } std::int8_t SetTileType(std::uint16_t tile, std::uint16_t value) { noop(); return 0; } // TODO bool LevelScriptLoader::jjSendPacket(const jjSTREAM& packet, std::int32_t toClientID, std::uint32_t toScriptModuleID) { noop(); return true; } // TODO std::uint16_t jjGetStaticTile(std::uint16_t tileID) { noop(); return 0; } std::uint16_t jjTileGet(std::uint8_t layer, std::int32_t xTile, std::int32_t yTile) { noop(); return 0; } std::uint16_t jjTileSet(std::uint8_t layer, std::int32_t xTile, std::int32_t yTile, std::uint16_t newTile) { noop(); return 0; } void jjGenerateSettableTileArea(std::uint8_t layer, std::int32_t xTile, std::int32_t yTile, std::int32_t width, std::int32_t height) { noop(); } // TODO bool jjMaskedPixel(std::int32_t xPixel, std::int32_t yPixel) { noop(); return false; } bool jjMaskedPixelLayer(std::int32_t xPixel, std::int32_t yPixel, std::uint8_t layer) { noop(); return false; } bool jjMaskedHLine(std::int32_t xPixel, std::int32_t lineLength, std::int32_t yPixel) { noop(); return false; } bool jjMaskedHLineLayer(std::int32_t xPixel, std::int32_t lineLength, std::int32_t yPixel, std::uint8_t layer) { noop(); return false; } bool jjMaskedVLine(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength) { noop(); return false; } bool jjMaskedVLineLayer(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength, std::uint8_t layer) { noop(); return false; } bool jjMaskedTopVLine(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength) { noop(); return false; } bool jjMaskedTopVLineLayer(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength, std::uint8_t layer) { noop(); return false; } // TODO void jjSetModPosition(std::int32_t order, std::int32_t row, bool reset) { noop(); } void jjSlideModChannelVolume(std::int32_t channel, float volume, std::int32_t milliseconds) { noop(); } std::int32_t jjGetModOrder() { noop(); return 0; } std::int32_t jjGetModRow() { noop(); return 0; } std::int32_t jjGetModTempo() { noop(); return 0; } void jjSetModTempo(std::uint8_t speed) { noop(); } std::int32_t jjGetModSpeed() { noop(); return 0; } void jjSetModSpeed(std::uint8_t speed) { noop(); } std::uint32_t getCustomSetID(std::uint8_t index) { noop(); return mCOUNT + index; } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/JJ2PlusDefinitions.h000066400000000000000000001553251512772601700273060ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../Main.h" #include "../../nCine/Primitives/Rect.h" #include "../../nCine/Base/Random.h" #include "RegisterArray.h" #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Actors { class Player; } namespace Jazz2::UI { class HUD; } namespace Jazz2::Scripting { class LevelScriptLoader; namespace Legacy { enum airjump { airjumpNONE, airjumpHELICOPTER, airjumpSPAZ }; enum ambientLighting { ambientLighting_OPTIONAL, ambientLighting_BASIC, ambientLighting_COMPLETE }; enum anim { mAMMO, mBAT, mBEEBOY, mBEES, mBIGBOX, mBIGROCK, mBIGTREE, mBILSBOSS, mBIRD, mBIRD3D, mBOLLPLAT, mBONUS, mBOSS, mBRIDGE, mBUBBA, mBUMBEE, mBUTTERFLY, mCARROTPOLE, mCAT, mCAT2, mCATERPIL, mCHUCK, mCOMMON, mCONTINUE, mDEMON, mDESTSCEN, mDEVAN, mDEVILDEVAN, mDIAMPOLE, mDOG, mDOOR, mDRAGFLY, mDRAGON, mEVA, mFACES, mFATCHK, mFENCER, mFISH, mFLAG, mFLARE, mFONT, mFROG, mFRUITPLAT, mGEMRING, mGLOVE, mGRASSPLAT, mHATTER, mHELMUT, mJAZZ, mJAZZ3D, mJUNGLEPOLE, mLABRAT, mLIZARD, mLORI, mLORI2, mMENU, mMENUFONT, mMONKEY, mMOTH, mPICKUPS, mPINBALL, mPINKPLAT, mPSYCHPOLE, mQUEEN, mRAPIER, mRAVEN, mROBOT, mROCK, mROCKTURT, mSKELETON, mSMALTREE, mSNOW, mSONCSHIP, mSONICPLAT, mSPARK, mSPAZ, mSPAZ2, mSPAZ3D, mSPIKEBOLL, mSPIKEBOLL3D, mSPIKEPLAT, mSPRING, mSTEAM, mSUCKER, mTUBETURT, mTUFBOSS, mTUFTURT, mTURTLE, mTWEEDLE, mUTERUS, mVINE, mWARP10, mWARP100, mWARP20, mWARP50, mWITCH, mXBILSY, mXLIZARD, mXTURTLE, mZDOG, mZSPARK, mZZAMMO, mZZBETA, mZZCOMMON, mZZCONTINUE, mZZFONT, mZZMENUFONT, mZZREPLACEMENTS, mZZRETICLES, mZZSCENERY, mZZWARP, mCOUNT }; enum dir { dirRIGHT, dirLEFT, dirUP, dirCURRENT }; enum gameState { gameSTOPPED, gameSTARTED, gamePAUSED, gamePREGAME, gameOVERTIME }; enum gameConnection { gameLOCAL, gameINTERNET, gameLAN_TCP }; enum GM_ { GM_SP, GM_COOP, GM_BATTLE, GM_CTF, GM_TREASURE, GM_RACE }; enum groundjump { groundjumpNONE, groundjumpREGULARJUMP, groundjumpJAZZ, groundjumpSPAZ, groundjumpLORI }; enum object { aUNKNOWN, aPLAYERBULLET1, aPLAYERBULLET2, aPLAYERBULLET3, aPLAYERBULLET4, aPLAYERBULLET5, aPLAYERBULLET6, aPLAYERBULLET8, aPLAYERBULLET9, aPLAYERBULLETP1, aPLAYERBULLETP2, aPLAYERBULLETP3, aPLAYERBULLETP4, aPLAYERBULLETP5, aPLAYERBULLETP6, aPLAYERBULLETP8, aPLAYERBULLETP9, aPLAYERBULLETC1, aPLAYERBULLETC2, aPLAYERBULLETC3, aBULLET, aCATSMOKE, aSHARD, aEXPLOSION, aBOUNCEONCE, aREDGEMTEMP, aPLAYERLASER, aUTERUSEL, aBIRD, aBUBBLE, aGUN3AMMO3, aGUN2AMMO3, aGUN4AMMO3, aGUN5AMMO3, aGUN6AMMO3, aGUN7AMMO3, aGUN8AMMO3, aGUN9AMMO3, aTURTLESHELL, aSWINGVINE, aBOMB, aSILVERCOIN, aGOLDCOIN, aGUNCRATE, aCARROTCRATE, a1UPCRATE, aGEMBARREL, aCARROTBARREL, a1UPBARREL, aBOMBCRATE, aGUN3AMMO15, aGUN2AMMO15, aGUN4AMMO15, aGUN5AMMO15, aGUN6AMMO15, aTNT, aAIRBOARDGENERATOR, aFROZENGREENSPRING, aGUNFASTFIRE, aSPRINGCRATE, aREDGEM, aGREENGEM, aBLUEGEM, aPURPLEGEM, aSUPERREDGEM, aBIRDCAGE, aGUNBARREL, aGEMCRATE, aMORPHMONITOR, aENERGYUP, aFULLENERGY, aFIRESHIELD, aWATERSHIELD, aLIGHTSHIELD, aFASTFEET, aEXTRALIFE, aENDOFLEVELPOST, aSAVEPOST, aBONUSLEVELPOST, aREDSPRING, aGREENSPRING, aBLUESPRING, aINVINCIBILITY, aEXTRATIME, aFREEZER, aHREDSPRING, aHGREENSPRING, aHBLUESPRING, aBIRDMORPHMONITOR, aTRIGGERCRATE, aFLYCARROT, aRECTREDGEM, aRECTGREENGEM, aRECTBLUEGEM, aTUFTURT, aTUFBOSS, aLABRAT, aDRAGON, aLIZARD, aBUMBEE, aRAPIER, aSPARK, aBAT, aSUCKER, aCATERPILLAR, aCHESHIRE1, aCHESHIRE2, aHATTER, aBILSYBOSS, aSKELETON, aDOGGYDOGG, aNORMTURTLE, aHELMUT, aDEMON, aDRAGONFLY, aMONKEY, aFATCHK, aFENCER, aFISH, aMOTH, aSTEAM, aROCK, aGUN1POWER, aGUN2POWER, aGUN3POWER, aGUN4POWER, aGUN5POWER, aGUN6POWER, aPINLEFTPADDLE, aPINRIGHTPADDLE, aPIN500BUMP, aPINCARROTBUMP, aAPPLE, aBANANA, aCHERRY, aORANGE, aPEAR, aPRETZEL, aSTRAWBERRY, aSTEADYLIGHT, aPULZELIGHT, aFLICKERLIGHT, aQUEENBOSS, aFLOATSUCKER, aBRIDGE, aLEMON, aLIME, aTHING, aWMELON, aPEACH, aGRAPES, aLETTUCE, aEGGPLANT, aCUCUMB, aCOKE, aPEPSI, aMILK, aPIE, aCAKE, aDONUT, aCUPCAKE, aCHIPS, aCANDY1, aCHOCBAR, aICECREAM, aBURGER, aPIZZA, aFRIES, aCHICKLEG, aSANDWICH, aTACOBELL, aWEENIE, aHAM, aCHEESE, aFLOATLIZARD, aSTANDMONKEY, aDESTRUCTSCENERY, aDESTRUCTSCENERYBOMB, aCOLLAPSESCENERY, aSTOMPSCENERY, aGEMSTOMP, aRAVEN, aTUBETURTLE, aGEMRING, aROTSMALLTREE, aAMBIENTSOUND, aUTERUS, aCRAB, aWITCH, aROCKTURT, aBUBBA, aDEVILDEVAN, aDEVANROBOT, aROBOT, aCARROTUSPOLE, aPSYCHPOLE, aDIAMONDUSPOLE, aFRUITPLATFORM, aBOLLPLATFORM, aGRASSPLATFORM, aPINKPLATFORM, aSONICPLATFORM, aSPIKEPLATFORM, aSPIKEBOLL, aGENERATOR, aEVA, aBUBBLER, aTNTPOWER, aGUN8POWER, aGUN9POWER, aSPIKEBOLL3D, aSPRINGCORD, aBEES, aCOPTER, aLASERSHIELD, aSTOPWATCH, aJUNGLEPOLE, aBIGROCK, aBIGBOX, aTRIGGERSCENERY, aSONICBOSS, aBUTTERFLY, aBEEBOY, aSNOW, aTWEEDLEBOSS, aAIRBOARD, aFLAG, aXNORMTURTLE, aXLIZARD, aXFLOATLIZARD, aXBILSYBOSS, aZCAT, aZGHOST, areaONEWAY, areaHURT, areaVINE, areaHOOK, areaSLIDE, areaHPOLE, areaVPOLE, areaFLYOFF, areaRICOCHET, areaBELTRIGHT, areaBELTLEFT, areaBELTACCRIGHT, areaBELTACCLEFT, areaSTOPENEMY, areaWINDLEFT, areaWINDRIGHT, areaEOL, areaWARPEOL, areaENDMORPH, areaFLOATUP, areaROCKTRIGGER, areaDIMLIGHT, areaSETLIGHT, areaLIMITXSCROLL, areaRESETLIGHT, areaWARPSECRET, areaECHO, areaBOSSTRIGGER, areaJAZZLEVELSTART, areaSPAZLEVELSTART, areaMPLEVELSTART, areaLORILEVELSTART, areaWARP, areaWARPTARGET, areaAREAID, areaNOFIREZONE, areaTRIGGERZONE, aSUCKERTUBE, aTEXT, aWATERLEVEL, aMORPHFROG, aWATERBLOCK, aCOUNT }; enum particle { particleNONE, particlePIXEL, particleFIRE, particleSMOKE, particleICETRAIL, particleSPARK, particleSCORE, particleSNOW, particleRAIN, particleFLOWER, particleLEAF, particleSTAR, particleTILE }; enum playerAnim { mJAZZ_AIRBOARD, mJAZZ_AIRBOARDTURN, mJAZZ_BUTTSTOMPLAND, mJAZZ_CORPSE, mJAZZ_DIE, mJAZZ_DIVE, mJAZZ_DIVEFIREQUIT, mJAZZ_DIVEFIRERIGHT, mJAZZ_DIVEUP, mJAZZ_EARBRACHIATE, mJAZZ_ENDOFLEVEL, mJAZZ_FALL, mJAZZ_FALLBUTTSTOMP, mJAZZ_FALLLAND, mJAZZ_FIRE, mJAZZ_FIREUP, mJAZZ_FIREUPQUIT, mJAZZ_FROG, mJAZZ_HANGFIREQUIT, mJAZZ_HANGFIREREST, mJAZZ_HANGFIREUP, mJAZZ_HANGIDLE1, mJAZZ_HANGIDLE2, mJAZZ_HANGINGFIREQUIT, mJAZZ_HANGINGFIRERIGHT, mJAZZ_HELICOPTER, mJAZZ_HELICOPTERFIREQUIT, mJAZZ_HELICOPTERFIRERIGHT, mJAZZ_HPOLE, mJAZZ_HURT, mJAZZ_IDLE1, mJAZZ_IDLE2, mJAZZ_IDLE3, mJAZZ_IDLE4, mJAZZ_IDLE5, mJAZZ_JUMPFIREQUIT, mJAZZ_JUMPFIRERIGHT, mJAZZ_JUMPING1, mJAZZ_JUMPING2, mJAZZ_JUMPING3, mJAZZ_LEDGEWIGGLE, mJAZZ_LIFT, mJAZZ_LIFTJUMP, mJAZZ_LIFTLAND, mJAZZ_LOOKUP, mJAZZ_LOOPY, mJAZZ_PUSH, mJAZZ_QUIT, mJAZZ_REV1, mJAZZ_REV2, mJAZZ_REV3, mJAZZ_RIGHTFALL, mJAZZ_RIGHTJUMP, mJAZZ_ROLLING, mJAZZ_RUN1, mJAZZ_RUN2, mJAZZ_RUN3, mJAZZ_SKID1, mJAZZ_SKID2, mJAZZ_SKID3, mJAZZ_SPRING, mJAZZ_STAND, mJAZZ_STATIONARYJUMP, mJAZZ_STATIONARYJUMPEND, mJAZZ_STATIONARYJUMPSTART, mJAZZ_STONED, mJAZZ_SWIMDOWN, mJAZZ_SWIMRIGHT, mJAZZ_SWIMTURN1, mJAZZ_SWIMTURN2, mJAZZ_SWIMUP, mJAZZ_SWINGINGVINE, mJAZZ_TELEPORT, mJAZZ_TELEPORTFALL, mJAZZ_TELEPORTFALLING, mJAZZ_TELEPORTFALLTELEPORT, mJAZZ_TELEPORTSTAND, mJAZZ_VPOLE }; enum spriteType { spriteType_NORMAL, spriteType_TRANSLUCENT, spriteType_TINTED, spriteType_GEM, spriteType_INVISIBLE, spriteType_SINGLECOLOR, spriteType_RESIZED, spriteType_NEONGLOW, spriteType_FROZEN, spriteType_PLAYER, spriteType_PALSHIFT, spriteType_SHADOW, spriteType_SINGLEHUE, spriteType_BRIGHTNESS, spriteType_TRANSLUCENTCOLOR, spriteType_TRANSLUCENTPLAYER, spriteType_TRANSLUCENTPALSHIFT, spriteType_TRANSLUCENTSINGLEHUE, spriteType_ALPHAMAP, spriteType_MENUPLAYER, spriteType_BLENDNORMAL, spriteType_BLENDDARKEN, spriteType_BLENDLIGHTEN, spriteType_BLENDHUE, spriteType_BLENDSATURATION, spriteType_BLENDCOLOR, spriteType_BLENDLUMINANCE, spriteType_BLENDMULTIPLY, spriteType_BLENDSCREEN, spriteType_BLENDDISSOLVE, spriteType_BLENDOVERLAY, spriteType_BLENDHARDLIGHT, spriteType_BLENDSOFTLIGHT, spriteType_BLENDDIFFERENCE, spriteType_BLENDDODGE, spriteType_BLENDBURN, spriteType_BLENDEXCLUSION, spriteType_TRANSLUCENTTILE, spriteType_CHROMAKEY, spriteType_MAPPING, spriteType_TRANSLUCENTMAPPING }; enum sound { sAMMO_BLUB1, sAMMO_BLUB2, sAMMO_BMP1, sAMMO_BMP2, sAMMO_BMP3, sAMMO_BMP4, sAMMO_BMP5, sAMMO_BMP6, sAMMO_BOEM1, sAMMO_BUL1, sAMMO_BULFL1, sAMMO_BULFL2, sAMMO_BULFL3, sAMMO_FIREGUN1A, sAMMO_FIREGUN2A, sAMMO_FUMP, sAMMO_GUN1, sAMMO_GUN2, sAMMO_GUN3PLOP, sAMMO_GUNFLP, sAMMO_GUNFLP1, sAMMO_GUNFLP2, sAMMO_GUNFLP3, sAMMO_GUNFLP4, sAMMO_GUNFLPL, sAMMO_GUNJAZZ, sAMMO_GUNVELOCITY, sAMMO_ICEGUN, sAMMO_ICEGUN2, sAMMO_ICEGUNPU, sAMMO_ICEPU1, sAMMO_ICEPU2, sAMMO_ICEPU3, sAMMO_ICEPU4, sAMMO_LASER, sAMMO_LASER2, sAMMO_LASER3, sAMMO_LAZRAYS, sAMMO_MISSILE, sAMMO_SPZBL1, sAMMO_SPZBL2, sAMMO_SPZBL3, sBAT_BATFLY1, sBILSBOSS_BILLAPPEAR, sBILSBOSS_FINGERSNAP, sBILSBOSS_FIRE, sBILSBOSS_FIRESTART, sBILSBOSS_SCARY3, sBILSBOSS_THUNDER, sBILSBOSS_ZIP, sBONUS_BONUS1, sBONUS_BONUSBLUB, sBUBBA_BUBBABOUNCE1, sBUBBA_BUBBABOUNCE2, sBUBBA_BUBBAEXPLO, sBUBBA_FROG2, sBUBBA_FROG3, sBUBBA_FROG4, sBUBBA_FROG5, sBUBBA_SNEEZE2, sBUBBA_TORNADOATTACK2, sBUMBEE_BEELOOP, sCATERPIL_RIDOE, sCOMMON_AIRBOARD, sCOMMON_AIRBTURN, sCOMMON_AIRBTURN2, sCOMMON_BASE1, sCOMMON_BELL_FIRE, sCOMMON_BELL_FIRE2, sCOMMON_BENZIN1, sCOMMON_BIRDFLY, sCOMMON_BIRDFLY2, sCOMMON_BLOKPLOP, sCOMMON_BLUB1, sCOMMON_BUBBLGN1, sCOMMON_BURN, sCOMMON_BURNIN, sCOMMON_CANSPS, sCOMMON_CLOCK, sCOMMON_COIN, sCOMMON_COLLAPS, sCOMMON_CUP, sCOMMON_DAMPED1, sCOMMON_DOWN, sCOMMON_DOWNFL2, sCOMMON_DRINKSPAZZ1, sCOMMON_DRINKSPAZZ2, sCOMMON_DRINKSPAZZ3, sCOMMON_DRINKSPAZZ4, sCOMMON_EAT1, sCOMMON_EAT2, sCOMMON_EAT3, sCOMMON_EAT4, sCOMMON_ELECTRIC1, sCOMMON_ELECTRIC2, sCOMMON_ELECTRICHIT, sCOMMON_EXPL_TNT, sCOMMON_EXPSM1, sCOMMON_FLAMER, sCOMMON_FLAP, sCOMMON_FOEW1, sCOMMON_FOEW2, sCOMMON_FOEW3, sCOMMON_FOEW4, sCOMMON_FOEW5, sCOMMON_GEMSMSH1, sCOMMON_GLASS2, sCOMMON_GUNSM1, sCOMMON_HARP1, sCOMMON_HEAD, sCOMMON_HELI1, sCOMMON_HIBELL, sCOMMON_HOLYFLUT, sCOMMON_HORN1, sCOMMON_ICECRUSH, sCOMMON_IMPACT1, sCOMMON_IMPACT2, sCOMMON_IMPACT3, sCOMMON_IMPACT4, sCOMMON_IMPACT5, sCOMMON_IMPACT6, sCOMMON_IMPACT7, sCOMMON_IMPACT8, sCOMMON_IMPACT9, sCOMMON_ITEMTRE, sCOMMON_JUMP, sCOMMON_JUMP2, sCOMMON_LAND, sCOMMON_LAND1, sCOMMON_LAND2, sCOMMON_LANDCAN1, sCOMMON_LANDCAN2, sCOMMON_LANDPOP, sCOMMON_LOADJAZZ, sCOMMON_LOADSPAZ, sCOMMON_METALHIT, sCOMMON_MONITOR, sCOMMON_NOCOIN, sCOMMON_PICKUP1, sCOMMON_PICKUPW1, sCOMMON_PISTOL1, sCOMMON_PLOOP1, sCOMMON_PLOP1, sCOMMON_PLOP2, sCOMMON_PLOP3, sCOMMON_PLOP4, sCOMMON_PLOPKORK, sCOMMON_PREEXPL1, sCOMMON_PREHELI, sCOMMON_REVUP, sCOMMON_RINGGUN, sCOMMON_RINGGUN2, sCOMMON_SHIELD1, sCOMMON_SHIELD4, sCOMMON_SHIELD_ELEC, sCOMMON_SHLDOF3, sCOMMON_SLIP, sCOMMON_SMASH, sCOMMON_SPLAT1, sCOMMON_SPLAT2, sCOMMON_SPLAT3, sCOMMON_SPLAT4, sCOMMON_SPLUT, sCOMMON_SPRING1, sCOMMON_STEAM, sCOMMON_STEP, sCOMMON_STRETCH, sCOMMON_SWISH1, sCOMMON_SWISH2, sCOMMON_SWISH3, sCOMMON_SWISH4, sCOMMON_SWISH5, sCOMMON_SWISH6, sCOMMON_SWISH7, sCOMMON_SWISH8, sCOMMON_TELPORT1, sCOMMON_TELPORT2, sCOMMON_UP, sCOMMON_WATER, sCOMMON_WOOD1, sDEMON_RUN, sDEVILDEVAN_DRAGONFIRE, sDEVILDEVAN_FLAP, sDEVILDEVAN_FROG4, sDEVILDEVAN_JUMPUP, sDEVILDEVAN_LAUGH, sDEVILDEVAN_PHASER2, sDEVILDEVAN_STRECH2, sDEVILDEVAN_STRECHTAIL, sDEVILDEVAN_STRETCH1, sDEVILDEVAN_STRETCH3, sDEVILDEVAN_VANISH1, sDEVILDEVAN_WHISTLEDESCENDING2, sDEVILDEVAN_WINGSOUT, sDOG_AGRESSIV, sDOG_SNIF1, sDOG_WAF1, sDOG_WAF2, sDOG_WAF3, sDRAGFLY_BEELOOP, sENDING_OHTHANK, sENDTUNEJAZZ_TUNE, sENDTUNELORI_CAKE, sENDTUNESPAZ_TUNE, sEPICLOGO_EPIC1, sEPICLOGO_EPIC2, sEVA_KISS1, sEVA_KISS2, sEVA_KISS3, sEVA_KISS4, sFAN_FAN, sFATCHK_HIT1, sFATCHK_HIT2, sFATCHK_HIT3, sFENCER_FENCE1, sFROG_FROG, sFROG_FROG1, sFROG_FROG2, sFROG_FROG3, sFROG_FROG4, sFROG_FROG5, sFROG_JAZZ2FROG, sFROG_TONG, sGLOVE_HIT, sHATTER_CUP, sHATTER_HAT, sHATTER_PTOEI, sHATTER_SPLIN, sHATTER_SPLOUT, sINTRO_BLOW, sINTRO_BOEM1, sINTRO_BOEM2, sINTRO_BRAKE, sINTRO_END, sINTRO_GRAB, sINTRO_GREN1, sINTRO_GREN2, sINTRO_GREN3, sINTRO_GUNM0, sINTRO_GUNM1, sINTRO_GUNM2, sINTRO_HELI, sINTRO_HITSPAZ, sINTRO_HITTURT, sINTRO_IFEEL, sINTRO_INHALE, sINTRO_INSECT, sINTRO_KATROL, sINTRO_LAND, sINTRO_MONSTER, sINTRO_MONSTER2, sINTRO_ROCK, sINTRO_ROPE1, sINTRO_ROPE2, sINTRO_RUN, sINTRO_SHOT1, sINTRO_SHOTGRN, sINTRO_SKI, sINTRO_STRING, sINTRO_SWISH1, sINTRO_SWISH2, sINTRO_SWISH3, sINTRO_SWISH4, sINTRO_UHTURT, sINTRO_UP1, sINTRO_UP2, sINTRO_WIND_01, sJAZZSOUNDS_BALANCE, sJAZZSOUNDS_HEY1, sJAZZSOUNDS_HEY2, sJAZZSOUNDS_HEY3, sJAZZSOUNDS_HEY4, sJAZZSOUNDS_IDLE, sJAZZSOUNDS_JAZZV1, sJAZZSOUNDS_JAZZV2, sJAZZSOUNDS_JAZZV3, sJAZZSOUNDS_JAZZV4, sJAZZSOUNDS_JUMMY, sJAZZSOUNDS_PFOE, sLABRAT_BITE, sLABRAT_EYE2, sLABRAT_EYE3, sLABRAT_MOUSE1, sLABRAT_MOUSE2, sLABRAT_MOUSE3, sLIZARD_LIZ1, sLIZARD_LIZ2, sLIZARD_LIZ4, sLIZARD_LIZ6, sLORISOUNDS_DIE1, sLORISOUNDS_HURT0, sLORISOUNDS_HURT1, sLORISOUNDS_HURT2, sLORISOUNDS_HURT3, sLORISOUNDS_HURT4, sLORISOUNDS_HURT5, sLORISOUNDS_HURT6, sLORISOUNDS_HURT7, sLORISOUNDS_LORI1, sLORISOUNDS_LORI2, sLORISOUNDS_LORIBOOM, sLORISOUNDS_LORIFALL, sLORISOUNDS_LORIJUMP, sLORISOUNDS_LORIJUMP2, sLORISOUNDS_LORIJUMP3, sLORISOUNDS_LORIJUMP4, sLORISOUNDS_TOUCH, sLORISOUNDS_WEHOO, sMENUSOUNDS_SELECT0, sMENUSOUNDS_SELECT1, sMENUSOUNDS_SELECT2, sMENUSOUNDS_SELECT3, sMENUSOUNDS_SELECT4, sMENUSOUNDS_SELECT5, sMENUSOUNDS_SELECT6, sMENUSOUNDS_TYPE, sMENUSOUNDS_TYPEENTER, sMONKEY_SPLUT, sMONKEY_THROW, sMOTH_FLAPMOTH, sORANGE_BOEML, sORANGE_BOEMR, sORANGE_BUBBELSL, sORANGE_BUBBELSR, sORANGE_GLAS1L, sORANGE_GLAS1R, sORANGE_GLAS2L, sORANGE_GLAS2R, sORANGE_MERGE, sORANGE_SWEEP0L, sORANGE_SWEEP0R, sORANGE_SWEEP1L, sORANGE_SWEEP1R, sORANGE_SWEEP2L, sORANGE_SWEEP2R, sP2_CRUNCH, sP2_FART, sP2_FOEW1, sP2_FOEW4, sP2_FOEW5, sP2_FROG1, sP2_FROG2, sP2_FROG3, sP2_FROG4, sP2_FROG5, sP2_KISS4, sP2_OPEN, sP2_PINCH1, sP2_PINCH2, sP2_PLOPSEQ1, sP2_PLOPSEQ2, sP2_PLOPSEQ3, sP2_PLOPSEQ4, sP2_POEP, sP2_PTOEI, sP2_SPLOUT, sP2_SPLUT, sP2_THROW, sP2_TONG, sPICKUPS_BOING_CHECK, sPICKUPS_HELI2, sPICKUPS_STRETCH1A, sPINBALL_BELL, sPINBALL_FLIP1, sPINBALL_FLIP2, sPINBALL_FLIP3, sPINBALL_FLIP4, sQUEEN_LADYUP, sQUEEN_SCREAM, sRAPIER_GOSTDIE, sRAPIER_GOSTLOOP, sRAPIER_GOSTOOOH, sRAPIER_GOSTRIP, sRAPIER_HITCHAR, sROBOT_BIG1, sROBOT_BIG2, sROBOT_CAN1, sROBOT_CAN2, sROBOT_HYDRO, sROBOT_HYDRO2, sROBOT_HYDROFIL, sROBOT_HYDROPUF, sROBOT_IDLE1, sROBOT_IDLE2, sROBOT_JMPCAN1, sROBOT_JMPCAN10, sROBOT_JMPCAN2, sROBOT_JMPCAN3, sROBOT_JMPCAN4, sROBOT_JMPCAN5, sROBOT_JMPCAN6, sROBOT_JMPCAN7, sROBOT_JMPCAN8, sROBOT_JMPCAN9, sROBOT_METAL1, sROBOT_METAL2, sROBOT_METAL3, sROBOT_METAL4, sROBOT_METAL5, sROBOT_OPEN, sROBOT_OUT, sROBOT_POEP, sROBOT_POLE, sROBOT_SHOOT, sROBOT_STEP1, sROBOT_STEP2, sROBOT_STEP3, sROCK_ROCK1, sRUSH_RUSH, sSCIENCE_PLOPKAOS, sSKELETON_BONE1, sSKELETON_BONE2, sSKELETON_BONE3, sSKELETON_BONE5, sSKELETON_BONE6, sSKELETON_BONE7, sSMALTREE_FALL, sSMALTREE_GROUND, sSMALTREE_HEAD, sSONCSHIP_METAL1, sSONCSHIP_MISSILE2, sSONCSHIP_SCRAPE, sSONCSHIP_SHIPLOOP, sSONCSHIP_TARGETLOCK, sSPAZSOUNDS_AUTSCH1, sSPAZSOUNDS_AUTSCH2, sSPAZSOUNDS_BIRDSIT, sSPAZSOUNDS_BURP, sSPAZSOUNDS_CHIRP, sSPAZSOUNDS_EATBIRD, sSPAZSOUNDS_HAHAHA, sSPAZSOUNDS_HAHAHA2, sSPAZSOUNDS_HAPPY, sSPAZSOUNDS_HIHI, sSPAZSOUNDS_HOHOHO1, sSPAZSOUNDS_HOOO, sSPAZSOUNDS_KARATE7, sSPAZSOUNDS_KARATE8, sSPAZSOUNDS_OHOH, sSPAZSOUNDS_OOOH, sSPAZSOUNDS_WOOHOO, sSPAZSOUNDS_YAHOO, sSPAZSOUNDS_YAHOO2, sSPRING_BOING_DOWN, sSPRING_SPRING1, sSTEAM_STEAM, sSTONED_STONED, sSUCKER_FART, sSUCKER_PINCH1, sSUCKER_PINCH2, sSUCKER_PINCH3, sSUCKER_PLOPSEQ1, sSUCKER_PLOPSEQ2, sSUCKER_PLOPSEQ3, sSUCKER_PLOPSEQ4, sSUCKER_UP, sTUFBOSS_CATCH, sTUFBOSS_RELEASE, sTUFBOSS_SWING, sTURTLE_BITE3, sTURTLE_HIDE, sTURTLE_HITSHELL, sTURTLE_IDLE1, sTURTLE_IDLE2, sTURTLE_NECK, sTURTLE_SPK1TURT, sTURTLE_SPK2TURT, sTURTLE_SPK3TURT, sTURTLE_SPK4TURT, sTURTLE_TURN, sUTERUS_CRABCLOSE, sUTERUS_CRABOPEN2, sUTERUS_SCISSORS1, sUTERUS_SCISSORS2, sUTERUS_SCISSORS3, sUTERUS_SCISSORS4, sUTERUS_SCISSORS5, sUTERUS_SCISSORS6, sUTERUS_SCISSORS7, sUTERUS_SCISSORS8, sUTERUS_SCREAM1, sUTERUS_STEP1, sUTERUS_STEP2, sWIND_WIND2A, sWITCH_LAUGH, sWITCH_MAGIC, sXBILSY_BILLAPPEAR, sXBILSY_FINGERSNAP, sXBILSY_FIRE, sXBILSY_FIRESTART, sXBILSY_SCARY3, sXBILSY_THUNDER, sXBILSY_ZIP, sXLIZARD_LIZ1, sXLIZARD_LIZ2, sXLIZARD_LIZ4, sXLIZARD_LIZ6, sXTURTLE_BITE3, sXTURTLE_HIDE, sXTURTLE_HITSHELL, sXTURTLE_IDLE1, sXTURTLE_IDLE2, sXTURTLE_NECK, sXTURTLE_SPK1TURT, sXTURTLE_SPK2TURT, sXTURTLE_SPK3TURT, sXTURTLE_SPK4TURT, sXTURTLE_TURN, sZDOG_AGRESSIV, sZDOG_SNIF1, sZDOG_WAF1, sZDOG_WAF2, sZDOG_WAF3 }; enum state { sSTART, sSLEEP, sWAKE, sKILL, sDEACTIVATE, sWALK, sJUMP, sFIRE, sFLY, sBOUNCE, sEXPLODE, sROCKETFLY, sSTILL, sFLOAT, sHIT, sSPRING, sACTION, sDONE, sPUSH, sFALL, sFLOATFALL, sCIRCLE, sATTACK, sFREEZE, sFADEIN, sFADEOUT, sHIDE, sTURN, sIDLE, sEXTRA, sSTOP, sWAIT, sLAND, sDELAYEDSTART, sROTATE, sDUCK }; enum tbgMode { tbgModeWARPHORIZON, tbgModeTUNNEL, tbgModeMENU, tbgModeTILEMENU, tbgModeWAVE, tbgModeCYLINDER, tbgModeREFLECTION }; struct jjTEXTAPPEARANCE { enum align_ { align_DEFAULT, align_LEFT, align_CENTER, align_RIGHT }; enum ch_ { ch_HIDE, ch_DISPLAY, ch_SPECIAL }; std::int32_t xAmp; std::int32_t yAmp; std::int32_t spacing; bool monospace; bool skipInitialHash; ch_ at = ch_HIDE; ch_ caret = ch_HIDE; ch_ hash = ch_HIDE; ch_ newline = ch_HIDE; ch_ pipe = ch_HIDE; ch_ section = ch_HIDE; ch_ tilde = ch_HIDE; align_ align = align_DEFAULT; static void constructor(void* self); static void constructorMode(std::uint32_t mode, void* self); jjTEXTAPPEARANCE& operator=(std::uint32_t other); }; struct jjPALCOLOR { std::uint8_t red; std::uint8_t green; std::uint8_t blue; static void Create(void* self); static void CreateFromRgb(std::uint8_t red, std::uint8_t green, std::uint8_t blue, void* self); std::uint8_t getHue(); std::uint8_t getSat(); std::uint8_t getLight(); void swizzle(std::uint32_t redc, std::uint32_t greenc, std::uint32_t bluec); void setHSL(std::int32_t hue, std::uint8_t sat, std::uint8_t light); jjPALCOLOR& operator=(const jjPALCOLOR& other); bool operator==(const jjPALCOLOR& other); }; class jjPAL { public: jjPAL(); ~jjPAL(); static jjPAL* Create(jjPAL* self); void AddRef(); void Release(); jjPAL& operator=(const jjPAL& o); bool operator==(const jjPAL& o); jjPALCOLOR& getColor(std::uint8_t idx); const jjPALCOLOR& getConstColor(std::uint8_t idx) const; jjPALCOLOR& setColorEntry(std::uint8_t idx, jjPALCOLOR& value); void reset(); void apply(); bool load(const String& filename); void fill(std::uint8_t red, std::uint8_t green, std::uint8_t blue, float opacity); void fillTint(std::uint8_t red, std::uint8_t green, std::uint8_t blue, std::uint8_t start, std::uint8_t length, float opacity); void fillFromColor(jjPALCOLOR color, float opacity); void fillTintFromColor(jjPALCOLOR color, std::uint8_t start, std::uint8_t length, float opacity); void gradient(std::uint8_t red1, std::uint8_t green1, std::uint8_t blue1, std::uint8_t red2, std::uint8_t green2, std::uint8_t blue2, std::uint8_t start, std::uint8_t length, float opacity, bool inclusive); void gradientFromColor(jjPALCOLOR color1, jjPALCOLOR color2, std::uint8_t start, std::uint8_t length, float opacity, bool inclusive); void copyFrom(std::uint8_t start, std::uint8_t length, std::uint8_t start2, const jjPAL& source, float opacity); std::uint8_t findNearestColor(jjPALCOLOR color); private: std::int32_t _refCount; jjPALCOLOR _palette[256]; }; class jjSTREAM { public: jjSTREAM(); ~jjSTREAM(); static jjSTREAM* Create(); static jjSTREAM* CreateFromFile(const String& filename); void AddRef(); void Release(); jjSTREAM& operator=(const jjSTREAM& o); std::uint32_t getSize() const; bool isEmpty() const; bool save(const String& tilename) const; void clear(); bool discard(std::uint32_t count); bool write(const String& value); bool write(const jjSTREAM& value); bool get(String& value, std::uint32_t count); bool get(jjSTREAM& value, std::uint32_t count); bool getLine(String& value, const String& delim); bool push(bool value); bool push(std::uint8_t value); bool push(std::int8_t value); bool push(std::uint16_t value); bool push(std::int16_t value); bool push(std::uint32_t value); bool push(std::int32_t value); bool push(std::uint64_t value); bool push(std::int64_t value); bool push(float value); bool push(double value); bool push(const String& value); bool push(const jjSTREAM& value); bool pop(bool& value); bool pop(std::uint8_t& value); bool pop(std::int8_t& value); bool pop(std::uint16_t& value); bool pop(std::int16_t& value); bool pop(std::uint32_t& value); bool pop(std::int32_t& value); bool pop(std::uint64_t& value); bool pop(std::int64_t& value); bool pop(float& value); bool pop(double& value); bool pop(String& value); bool pop(jjSTREAM& value); private: std::int32_t _refCount; }; class jjRNG { public: jjRNG(std::uint64_t seed); ~jjRNG(); static jjRNG* Create(std::uint64_t seed); void AddRef(); void Release(); std::uint64_t operator()(); jjRNG& operator=(const jjRNG& o); bool operator==(const jjRNG& o) const; void seed(std::uint64_t value); void discard(std::uint64_t count); private: const std::uint64_t DefaultInitSequence = 0xda3e39cb94b95bdbULL; std::int32_t _refCount; RandomGenerator _random; }; struct jjBEHAVIOR { static jjBEHAVIOR* Create(jjBEHAVIOR* self); static jjBEHAVIOR* CreateFromBehavior(std::uint32_t behavior, jjBEHAVIOR* self); static void Destroy(jjBEHAVIOR* self); jjBEHAVIOR& operator=(const jjBEHAVIOR& other); jjBEHAVIOR& operator=(std::uint32_t other); jjBEHAVIOR& operator=(asIScriptFunction* other); jjBEHAVIOR& operator=(asIScriptObject* other); bool operator==(const jjBEHAVIOR& other) const; bool operator==(std::uint32_t other) const; bool operator==(const asIScriptFunction* other) const; operator std::uint32_t(); operator asIScriptFunction* (); operator asIScriptObject* (); }; class jjANIMFRAME { public: jjANIMFRAME(); ~jjANIMFRAME(); void AddRef(); void Release(); jjANIMFRAME& operator=(const jjANIMFRAME& o); static jjANIMFRAME* get_jjAnimFrames(std::uint32_t index); int16_t hotSpotX = 0; int16_t hotSpotY = 0; int16_t coldSpotX = 0; int16_t coldSpotY = 0; int16_t gunSpotX = 0; int16_t gunSpotY = 0; int16_t width = 0; int16_t height = 0; bool get_transparent() const; bool set_transparent(bool value) const; bool doesCollide(std::int32_t xPos, std::int32_t yPos, std::int32_t direction, const jjANIMFRAME* frame2, std::int32_t xPos2, std::int32_t yPos2, std::int32_t direction2, bool always) const; private: std::int32_t _refCount; }; class jjANIMATION { public: jjANIMATION(std::uint32_t index); ~jjANIMATION(); void AddRef(); void Release(); jjANIMATION& operator=(const jjANIMATION& o); bool save(const String& filename, const jjPAL& palette) const; bool load(const String& filename, std::int32_t hotSpotX, std::int32_t hotSpotY, std::int32_t coldSpotYOffset, std::int32_t firstFrameToOverwrite); static jjANIMATION* get_jjAnimations(std::uint32_t index); std::uint16_t frameCount = 0; std::int16_t fps = 0; std::uint32_t get_firstFrame() const; std::uint32_t set_firstFrame(std::uint32_t index) const; std::uint32_t getAnimFirstFrame(); private: std::int32_t _refCount; std::uint32_t _index; }; class jjANIMSET { public: jjANIMSET(std::uint32_t index); ~jjANIMSET(); void AddRef(); void Release(); static jjANIMSET* get_jjAnimSets(std::uint32_t index); std::uint32_t convertAnimSetToUint(); jjANIMSET* load(std::uint32_t fileSetID, const String& filename, int32_t firstAnimToOverwrite, int32_t firstFrameToOverwrite); jjANIMSET* allocate(const CScriptArray& frameCounts); private: std::int32_t _refCount; std::uint32_t _index; }; struct jjCANVAS { UI::HUD* Hud; Rectf View; jjCANVAS(UI::HUD* hud, const Rectf& view); void DrawPixel(std::int32_t xPixel, std::int32_t yPixel, std::uint8_t color, std::uint32_t mode, std::uint8_t param); void DrawRectangle(std::int32_t xPixel, std::int32_t yPixel, std::int32_t width, std::int32_t height, std::uint8_t color, std::uint32_t mode, std::uint8_t param); void DrawSprite(std::int32_t xPixel, std::int32_t yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, int8_t direction, std::uint32_t mode, std::uint8_t param); void DrawCurFrameSprite(std::int32_t xPixel, std::int32_t yPixel, std::uint32_t sprite, int8_t direction, std::uint32_t mode, std::uint8_t param); void DrawResizedSprite(std::int32_t xPixel, std::int32_t yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, float xScale, float yScale, std::uint32_t mode, std::uint8_t param); void DrawResizedCurFrameSprite(std::int32_t xPixel, std::int32_t yPixel, std::uint32_t sprite, float xScale, float yScale, std::uint32_t mode, std::uint8_t param); void DrawTransformedSprite(std::int32_t xPixel, std::int32_t yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, std::int32_t angle, float xScale, float yScale, std::uint32_t mode, std::uint8_t param); void DrawTransformedCurFrameSprite(std::int32_t xPixel, std::int32_t yPixel, std::uint32_t sprite, std::int32_t angle, float xScale, float yScale, std::uint32_t mode, std::uint8_t param); void DrawSwingingVine(std::int32_t xPixel, std::int32_t yPixel, std::uint32_t sprite, std::int32_t length, std::int32_t curvature, std::uint32_t mode, std::uint8_t param); void ExternalDrawTile(std::int32_t xPixel, std::int32_t yPixel, std::uint16_t tile, std::uint32_t tileQuadrant); void DrawTextBasicSize(std::int32_t xPixel, std::int32_t yPixel, const String& text, std::uint32_t size, std::uint32_t mode, std::uint8_t param); void DrawTextExtSize(std::int32_t xPixel, std::int32_t yPixel, const String& text, std::uint32_t size, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, std::uint32_t mode, std::uint8_t param); void drawString(std::int32_t xPixel, std::int32_t yPixel, const String& text, const jjANIMATION& animation, std::uint32_t mode, std::uint8_t param); void drawStringEx(std::int32_t xPixel, std::int32_t yPixel, const String& text, const jjANIMATION& animation, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, std::uint32_t spriteMode, std::uint8_t param2); static void jjDrawString(float xPixel, float yPixel, const String& text, const jjANIMATION& animation, std::uint32_t mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawStringEx(float xPixel, float yPixel, const String& text, const jjANIMATION& animation, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, std::uint32_t spriteMode, std::uint8_t param2, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static int jjGetStringWidth(const String& text, const jjANIMATION& animation, const jjTEXTAPPEARANCE& style); }; class jjOBJ; class jjPLAYER; using jjVOIDFUNCOBJ = void(*)(jjOBJ* obj); class jjOBJ { public: jjOBJ(); ~jjOBJ(); void AddRef(); void Release(); bool get_isActive() const; std::uint32_t get_lightType() const; std::uint32_t set_lightType(std::uint32_t value) const; jjOBJ* objectHit(jjOBJ* target, std::uint32_t playerHandling); void blast(std::int32_t maxDistance, bool blastObjects); jjBEHAVIOR behavior; void behave1(std::uint32_t behavior, bool draw); void behave2(jjBEHAVIOR behavior, bool draw); void behave3(jjVOIDFUNCOBJ behavior, bool draw); static std::int32_t jjAddObject(std::uint8_t eventID, float xPixel, float yPixel, std::uint16_t creatorID, std::uint32_t creatorType, std::uint32_t behavior); static std::int32_t jjAddObjectEx(std::uint8_t eventID, float xPixel, float yPixel, std::uint16_t creatorID, std::uint32_t creatorType, jjVOIDFUNCOBJ behavior); static void jjDeleteObject(std::int32_t objectID); static void jjKillObject(std::int32_t objectID); float xOrg = 0; float yOrg = 0; float xPos = 0; float yPos = 0; float xSpeed = 0; float ySpeed = 0; float xAcc = 0; float yAcc = 0; std::int32_t counter = 0; std::uint32_t curFrame = 0; std::uint32_t determineCurFrame(bool change); std::int32_t age = 0; std::int32_t creator = 0; std::uint16_t get_creatorID() const; std::uint16_t set_creatorID(std::uint16_t value) const; std::uint32_t get_creatorType() const; std::uint32_t set_creatorType(std::uint32_t value) const; int16_t curAnim = 0; int16_t determineCurAnim(std::uint8_t setID, std::uint8_t animation, bool change); std::uint16_t killAnim = 0; std::uint8_t freeze = 0; std::uint8_t lightType = 0; std::int8_t frameID = 0; std::int8_t noHit = 0; std::uint32_t get_bulletHandling(); std::uint32_t set_bulletHandling(std::uint32_t value); bool get_ricochet(); bool set_ricochet(bool value); bool get_freezable(); bool set_freezable(bool value); bool get_blastable(); bool set_blastable(bool value); std::int8_t energy = 0; std::int8_t light = 0; std::uint8_t objType = 0; std::uint32_t get_playerHandling(); std::uint32_t set_playerHandling(std::uint32_t value); bool get_isTarget(); bool set_isTarget(bool value); bool get_triggersTNT(); bool set_triggersTNT(bool value); bool get_deactivates(); bool set_deactivates(bool value); bool get_scriptedCollisions(); bool set_scriptedCollisions(bool value); std::int8_t state = 0; std::uint16_t points = 0; std::uint8_t eventID = 0; std::int8_t direction = 0; std::uint8_t justHit = 0; std::int8_t oldState = 0; std::int32_t animSpeed = 0; std::int32_t special = 0; std::int32_t get_var(std::uint8_t x); std::int32_t set_var(std::uint8_t x, std::int32_t value); std::uint8_t doesHurt = 0; std::uint8_t counterEnd = 0; std::int16_t objectID = 0; std::int32_t draw(); std::int32_t beSolid(bool shouldCheckForStompingLocalPlayers); void bePlatform(float xOld, float yOld, std::int32_t width, std::int32_t height); void clearPlatform(); void putOnGround(bool precise); bool ricochet(); std::int32_t unfreeze(std::int32_t style); void deleteObject(); void deactivate(); void pathMovement(); std::int32_t fireBullet(std::uint8_t eventID); void particlePixelExplosion(std::int32_t style); void grantPickup(jjPLAYER* player, std::int32_t frequency); std::int32_t findNearestPlayer(std::int32_t maxDistance) const; std::int32_t findNearestPlayerEx(std::int32_t maxDistance, std::int32_t& foundDistance) const; bool doesCollide(const jjOBJ* object, bool always) const; bool doesCollidePlayer(const jjPLAYER* object, bool always) const; private: std::int32_t _refCount; }; struct jjPARTICLEPIXEL { std::uint8_t size; std::uint8_t get_color(std::int32_t i) const; std::uint8_t set_color(std::int32_t i, std::uint8_t value); }; struct jjPARTICLEFIRE { std::uint8_t size; std::uint8_t color; std::uint8_t colorStop; std::int8_t colorDelta; }; struct jjPARTICLESMOKE { std::uint8_t countdown; }; struct jjPARTICLEICETRAIL { std::uint8_t color; std::uint8_t colorStop; std::int8_t colorDelta; }; struct jjPARTICLESPARK { std::uint8_t color; std::uint8_t colorStop; std::int8_t colorDelta; }; struct jjPARTICLESTRING { String get_text() const; void set_text(String text); }; struct jjPARTICLESNOW { std::uint8_t frame; std::uint8_t countup; std::uint8_t countdown; std::uint16_t frameBase; }; struct jjPARTICLERAIN { std::uint8_t frame; std::uint16_t frameBase; }; struct jjPARTICLEFLOWER { std::uint8_t size; std::uint8_t color; std::uint8_t angle; std::int8_t angularSpeed; std::uint8_t petals; }; struct jjPARTICLESTAR { std::uint8_t size; std::uint8_t color; std::uint8_t angle; std::int8_t angularSpeed; std::uint8_t frame; std::uint8_t colorChangeCounter; std::uint8_t colorChangeInterval; }; struct jjPARTICLELEAF { std::uint8_t frame; std::uint8_t countup; bool noclip; std::uint8_t height; std::uint16_t frameBase; }; class jjPARTICLE { public: jjPARTICLE(); ~jjPARTICLE(); void AddRef(); void Release(); float xPos; float yPos; float xSpeed; float ySpeed; std::uint8_t particleType; bool active; union { jjPARTICLEPIXEL pixel; jjPARTICLEFIRE fire; jjPARTICLESMOKE smoke; jjPARTICLEICETRAIL trail; jjPARTICLESPARK spark; jjPARTICLESTRING string; jjPARTICLESNOW snow; jjPARTICLERAIN rain; jjPARTICLEFLOWER flower; jjPARTICLESTAR star; jjPARTICLELEAF leaf; } GENERIC; private: std::int32_t _refCount; }; class jjPLAYER { friend class LevelScriptLoader; public: jjPLAYER(LevelScriptLoader* levelScripts, Actors::Player* player); ~jjPLAYER(); jjPLAYER& operator=(const jjPLAYER& o); std::int32_t get_score() const; std::int32_t set_score(std::int32_t value); std::int32_t get_scoreDisplayed() const; std::int32_t set_scoreDisplayed(std::int32_t value); std::int32_t setScore(std::int32_t value); float get_xPos() const; float set_xPos(float value); float get_yPos() const; float set_yPos(float value); float get_xAcc() const; float set_xAcc(float value); float get_yAcc() const; float set_yAcc(float value); float get_xOrg() const; float set_xOrg(float value); float get_yOrg() const; float set_yOrg(float value); float get_xSpeed(); float set_xSpeed(float value); float get_ySpeed(); float set_ySpeed(float value); float jumpStrength = 0.0f; std::int8_t frozen = 0; void freeze(bool frozen); std::int32_t get_currTile(); bool startSugarRush(std::int32_t time); std::int8_t get_health() const; std::int8_t set_health(std::int8_t value); std::int32_t warpID = 0; std::int32_t get_fastfire() const; std::int32_t set_fastfire(std::int32_t value); std::int8_t get_currWeapon() const; std::int8_t set_currWeapon(std::int8_t value); //std::int32_t get_lives() const; //std::int32_t set_lives(std::int32_t value); std::int32_t lives = 0; std::int32_t get_invincibility() const; std::int32_t set_invincibility(std::int32_t value); std::int32_t get_blink() const; std::int32_t set_blink(std::int32_t value); std::int32_t extendInvincibility(std::int32_t duration); std::int32_t get_food() const; std::int32_t set_food(std::int32_t value); std::int32_t get_coins() const; std::int32_t set_coins(std::int32_t value); bool testForCoins(std::int32_t numberOfCoins); std::int32_t get_gems(std::uint32_t type) const; std::int32_t set_gems(std::uint32_t type, std::int32_t value); bool testForGems(std::int32_t numberOfGems, std::uint32_t type); std::int32_t get_shieldType() const; std::int32_t set_shieldType(std::int32_t value); std::int32_t get_shieldTime() const; std::int32_t set_shieldTime(std::int32_t value); std::int32_t get_rolling() const; std::int32_t set_rolling(std::int32_t value); std::int32_t bossNumber = 0; std::int32_t boss = 0; bool bossActive = false; std::int8_t direction = 0; std::int32_t platform = 0; std::int32_t flag = 0; std::int32_t clientID = 0; std::int8_t get_playerID() const; std::int32_t get_localPlayerID() const; bool team = false; bool get_running() const; bool set_running(bool value); std::int32_t specialJump = 0; std::int32_t get_stoned(); std::int32_t set_stoned(std::int32_t value); std::int32_t buttstomp = 0; std::int32_t helicopter = 0; std::int32_t helicopterElapsed = 0; std::int32_t specialMove = 0; std::int32_t idle = 0; void suckerTube(std::int32_t xSpeed, std::int32_t ySpeed, bool center, bool noclip, bool trigSample); void poleSpin(float xSpeed, float ySpeed, std::uint32_t delay); void spring(float xSpeed, float ySpeed, bool keepZeroSpeeds, bool sample); bool isLocal = true; bool isActive = true; bool get_isConnecting() const; bool get_isIdle() const; bool get_isOut() const; bool get_isSpectating() const; bool get_isInGame() const; String get_name() const; String get_nameUnformatted() const; bool setName(const String& name); std::int8_t get_light() const; std::int8_t set_light(std::int8_t value); std::uint32_t get_fur() const; std::uint32_t set_fur(std::uint32_t value); void getFur(std::uint8_t& a, std::uint8_t& b, std::uint8_t& c, std::uint8_t& d) const; void setFur(std::uint8_t a, std::uint8_t b, std::uint8_t c, std::uint8_t d); bool get_noFire() const; bool set_noFire(bool value); bool get_antiGrav() const; bool set_antiGrav(bool value); bool get_invisibility() const; bool set_invisibility(bool value); bool get_noclipMode() const; bool set_noclipMode(bool value); std::uint8_t get_lighting() const; std::uint8_t set_lighting(std::uint8_t value); std::uint8_t resetLight(); bool get_playerKeyLeftPressed(); bool get_playerKeyRightPressed(); bool get_playerKeyUpPressed(); bool get_playerKeyDownPressed(); bool get_playerKeyFirePressed(); bool get_playerKeySelectPressed(); bool get_playerKeyJumpPressed(); bool get_playerKeyRunPressed(); void set_playerKeyLeftPressed(bool value); void set_playerKeyRightPressed(bool value); void set_playerKeyUpPressed(bool value); void set_playerKeyDownPressed(bool value); void set_playerKeyFirePressed(bool value); void set_playerKeySelectPressed(bool value); void set_playerKeyJumpPressed(bool value); void set_playerKeyRunPressed(bool value); bool get_powerup(std::uint8_t index); bool set_powerup(std::uint8_t index, bool value); std::int32_t get_ammo(std::uint8_t index) const; std::int32_t set_ammo(std::uint8_t index, std::int32_t value); bool offsetPosition(std::int32_t xPixels, std::int32_t yPixels); bool warpToTile(std::int32_t xTile, std::int32_t yTile, bool fast); bool warpToID(std::uint8_t warpID, bool fast); std::uint32_t morph(bool rabbitsOnly, bool morphEffect); std::uint32_t morphTo(std::uint32_t charNew, bool morphEffect); std::uint32_t revertMorph(bool morphEffect); std::uint32_t get_charCurr() const; std::uint32_t charOrig = 0; void kill(); bool hurt(std::int8_t damage, bool forceHurt, jjPLAYER* attacker); std::uint32_t get_timerState() const; bool get_timerPersists() const; bool set_timerPersists(bool value); std::uint32_t timerStart(std::int32_t ticks, bool startPaused); std::uint32_t timerPause(); std::uint32_t timerResume(); std::uint32_t timerStop(); std::int32_t get_timerTime() const; std::int32_t set_timerTime(std::int32_t value); void timerFunction(const String& functionName); void timerFunctionPtr(void* function); void timerFunctionFuncPtr(void* function); bool activateBoss(bool activate); bool limitXScroll(std::uint16_t left, std::uint16_t width); void cameraFreezeFF(float xPixel, float yPixel, bool centered, bool instant); void cameraFreezeBF(bool xUnfreeze, float yPixel, bool centered, bool instant); void cameraFreezeFB(float xPixel, bool yUnfreeze, bool centered, bool instant); void cameraFreezeBB(bool xUnfreeze, bool yUnfreeze, bool centered, bool instant); void cameraUnfreeze(bool instant); void showText(const String& text, std::uint32_t size); void showTextByID(std::uint32_t textID, std::uint32_t offset, std::uint32_t size); std::uint32_t get_fly() const; std::uint32_t set_fly(std::uint32_t value); std::int32_t fireBulletDirection(std::uint8_t gun, bool depleteAmmo, bool requireAmmo, std::uint32_t direction); std::int32_t fireBulletAngle(std::uint8_t gun, bool depleteAmmo, bool requireAmmo, float angle); std::int32_t subscreenX = 0; std::int32_t subscreenY = 0; float get_cameraX() const; float get_cameraY() const; std::int32_t get_deaths() const; bool get_isJailed() const; bool get_isZombie() const; std::int32_t get_lrsLives() const; std::int32_t get_roasts() const; std::int32_t get_laps() const; std::int32_t get_lapTimeCurrent() const; std::int32_t get_lapTimes(std::uint32_t index) const; std::int32_t get_lapTimeBest() const; bool get_isAdmin() const; bool hasPrivilege(const String& privilege, std::uint32_t moduleID) const; bool doesCollide(const jjOBJ* object, bool always) const; std::int32_t getObjectHitForce(const jjOBJ& target) const; bool objectHit(jjOBJ* target, std::int32_t force, std::uint32_t playerHandling); bool isEnemy(const jjPLAYER* victim) const; std::uint32_t charCurr = 0; std::uint16_t curAnim = 0; std::uint32_t curFrame = 0; std::uint8_t frameID = 0; private: LevelScriptLoader* _levelScriptLoader; Actors::Player* _player; void* _timerCallback; std::uint32_t _timerState; float _timerLeft; bool _timerPersists; bool _backingStoreDirty = false; void SyncPropertiesToBackingStore(); void SyncPropertiesFromBackingStore(); }; class jjWEAPON { public: bool infinite; bool replenishes; bool replacedByShield; bool replacedByBubbles; bool comesFromGunCrates; bool gradualAim; int multiplier; int maximum; int gemsLost; int gemsLostPowerup; std::int8_t style; int spread; bool defaultSample; bool allowed; bool allowedPowerup; bool comesFromBirds; bool comesFromBirdsPowerup; }; class jjCHARACTER { public: int airJump; int groundJump; int doubleJumpCountMax; float doubleJumpXSpeed; float doubleJumpYSpeed; int helicopterDurationMax; float helicopterXSpeed; float helicopterYSpeed; bool canHurt; bool canRun; bool morphBoxCycle; }; class jjLAYER; class jjPIXELMAP { public: jjPIXELMAP(); ~jjPIXELMAP(); static jjPIXELMAP* CreateFromTile(); static jjPIXELMAP* CreateFromSize(std::uint32_t width, std::uint32_t height); static jjPIXELMAP* CreateFromFrame(const jjANIMFRAME* animFrame); static jjPIXELMAP* CreateFromLayer(std::uint32_t left, std::uint32_t top, std::uint32_t width, std::uint32_t height, std::uint32_t layer); static jjPIXELMAP* CreateFromLayerObject(std::uint32_t left, std::uint32_t top, std::uint32_t width, std::uint32_t height, const jjLAYER* layer); static jjPIXELMAP* CreateFromTexture(std::uint32_t animFrame); static jjPIXELMAP* CreateFromFilename(const String& filename, const jjPAL* palette, std::uint8_t threshold); void AddRef(); void Release(); jjPIXELMAP& operator=(const jjPIXELMAP& o); // TODO: return type std::uint8_t& instead? std::uint8_t GetPixel(std::uint32_t x, std::uint32_t y); std::uint32_t width = 0; std::uint32_t height = 0; bool saveToTile(std::uint16_t tileID, bool hFlip) const; bool saveToFrame(jjANIMFRAME* frame) const; bool saveToFile(const String& filename, const jjPAL& palette) const; private: std::int32_t _refCount; }; class jjMASKMAP { public: jjMASKMAP(); ~jjMASKMAP(); static jjMASKMAP* CreateFromBool(bool filled); static jjMASKMAP* CreateFromTile(std::uint16_t tileID); void AddRef(); void Release(); jjMASKMAP& operator=(const jjMASKMAP& o); // TODO: return type bool& instead? bool GetPixel(std::uint32_t x, std::uint32_t y); bool save(std::uint16_t tileID, bool hFlip) const; private: std::int32_t _refCount; }; class jjLAYER { public: jjLAYER(); ~jjLAYER(); static jjLAYER* CreateFromSize(std::uint32_t width, std::uint32_t height); static jjLAYER* CreateCopy(jjLAYER* other); void AddRef(); void Release(); jjLAYER& operator=(const jjLAYER& o); static jjLAYER* get_jjLayers(int32_t index); std::int32_t width = 0; std::int32_t widthReal = 0; std::int32_t widthRounded = 0; std::int32_t height = 0; float xSpeed = 0; float ySpeed = 0; float xAutoSpeed = 0; float yAutoSpeed = 0; float xOffset = 0; float yOffset = 0; float xInnerSpeed = 0; float yInnerSpeed = 0; float xInnerAutoSpeed = 0; float yInnerAutoSpeed = 0; std::uint32_t get_spriteMode() const; std::uint32_t set_spriteMode(std::uint32_t value) const; std::uint8_t get_spriteParam() const; std::uint8_t set_spriteParam(std::uint8_t value) const; void setXSpeed(float newspeed, bool newSpeedIsAnAutoSpeed) const; void setYSpeed(float newspeed, bool newSpeedIsAnAutoSpeed) const; float getXPosition(const jjPLAYER* play) const; float getYPosition(const jjPLAYER* play) const; std::int32_t GetTextureMode() const; void SetTextureMode(std::int32_t value) const; std::int32_t GetTexture() const; void SetTexture(std::int32_t value) const; std::int32_t rotationAngle = 0; std::int32_t rotationRadiusMultiplier = 0; bool tileHeight = false; bool tileWidth = false; bool limitVisibleRegion = false; bool hasTileMap = false; bool hasTiles = false; static CScriptArray* jjLayerOrderGet(); static bool jjLayerOrderSet(const CScriptArray& order); static CScriptArray* jjLayersFromLevel(const String& filename, const CScriptArray& layerIDs, int32_t tileIDAdjustmentFactor); static bool jjTilesFromTileset(const String& filename, std::uint32_t firstTileID, std::uint32_t tileCount, const CScriptArray* paletteColorMapping); std::uint16_t tileGet(int xTile, int yTile); std::uint16_t tileSet(int xTile, int yTile, std::uint16_t newTile); void generateSettableTileArea(int xTile, int yTile, int width, int height); void generateSettableTileAreaAll(); std::int32_t SpeedModeX; std::int32_t SpeedModeY; private: std::int32_t _refCount; }; struct jjPLAYERDRAW { bool name; bool sprite; bool sugarRush; bool gunFlash; bool invincibility; bool trail; bool morphingExplosions; bool airboardBouncingMotion; bool airboardPuff; std::int32_t spriteMode; std::uint8_t spriteParam; std::int32_t lightType; std::int8_t light; std::int32_t layer; std::uint32_t curFrame; std::int32_t angle; float xOffset; float yOffset; float xScale; float yScale; std::int32_t flag; bool get_shield(std::int32_t shield) const; bool set_shield(std::int32_t shield, bool enable); jjPLAYER* get_player() const; }; enum waterInteraction_ { waterInteraction_POSITIONBASED, waterInteraction_SWIM, waterInteraction_LOWGRAVITY }; enum ws { wsNORMAL, wsMISSILE, wsPOPCORN, wsCAPPED, }; enum wsp { wspNORMAL, wspNORMALORDIRECTIONANDAIM, wspDIRECTIONANDAIM, wspDOUBLEORTRIPLE, wspDOUBLE, wspTRIPLE, wspREFLECTSFASTFIRE, wspNORMALORBBGUN, wspBBGUN }; } using namespace Legacy; jjOBJ* get_jjObjects(std::int32_t index); jjOBJ* get_jjObjectPresets(std::int8_t id); void jjAddParticleTileExplosion(std::uint16_t xTile, std::uint16_t yTile, std::uint16_t tile, bool collapseSceneryStyle); void jjAddParticlePixelExplosion(float xPixel, float yPixel, int curFrame, int direction, int mode); jjPARTICLE* GetParticle(std::int32_t index); jjPARTICLE* AddParticle(std::int32_t particleType); std::int32_t get_jjPlayerCount(); std::int32_t get_jjLocalPlayerCount(); jjPLAYER* get_jjP(); jjPLAYER* get_jjPlayers(std::uint8_t index); jjPLAYER* get_jjLocalPlayers(std::uint8_t index); bool mlleSetup(); void mlleReapplyPalette(); void mlleSpawnOffgrids(); void mlleSpawnOffgridsLocal(); float get_sinTable(std::uint32_t angle); float get_cosTable(std::uint32_t angle); std::uint32_t RandWord32(); std::uint64_t unixTimeSec(); std::uint64_t unixTimeMs(); bool jjRegexIsValid(const String& expression); bool jjRegexMatch(const String& text, const String& expression, bool ignoreCase); bool jjRegexMatchWithResults(const String& text, const String& expression, CScriptArray& results, bool ignoreCase); bool jjRegexSearch(const String& text, const String& expression, bool ignoreCase); bool jjRegexSearchWithResults(const String& text, const String& expression, CScriptArray& results, bool ignoreCase); String jjRegexReplace(const String& text, const String& expression, const String& replacement, bool ignoreCase); std::int32_t GetFPS(); bool isAdmin(); String getLevelFileName(); String getCurrLevelName(); void setCurrLevelName(const String& in); String get_jjTilesetFileName(); std::int32_t get_gameState(); // TODO std::int32_t getBorderWidth(); std::int32_t getBorderHeight(); bool getSplitscreenType(); bool setSplitscreenType(); // TODO std::int32_t get_teamScore(std::int32_t color); std::int32_t GetMaxHealth(); std::int32_t GetStartHealth(); // TODO void jjSetDarknessColor(jjPALCOLOR color); void jjSetFadeColors(std::uint8_t red, std::uint8_t green, std::uint8_t blue); void jjSetFadeColorsFromPalette(std::uint8_t paletteColorID); void jjSetFadeColorsFromPalcolor(jjPALCOLOR color); jjPALCOLOR jjGetFadeColors(); void jjUpdateTexturedBG(); std::int32_t get_jjTexturedBGTexture(jjPALCOLOR color); std::int32_t set_jjTexturedBGTexture(std::int32_t texture); std::int32_t get_jjTexturedBGStyle(); std::int32_t set_jjTexturedBGStyle(std::int32_t style); bool get_jjTexturedBGUsed(jjPALCOLOR color); bool set_jjTexturedBGUsed(bool used); bool get_jjTexturedBGStars(bool used); bool set_jjTexturedBGStars(bool used); float get_jjTexturedBGFadePositionX(); float set_jjTexturedBGFadePositionX(float value); float get_jjTexturedBGFadePositionY(); float set_jjTexturedBGFadePositionY(float value); bool getEnabledTeam(std::uint8_t team); bool getKeyDown(std::uint8_t key); std::int32_t getCursorX(); std::int32_t getCursorY(); void playSample(float xPixel, float yPixel, std::int32_t sample, std::int32_t volume, std::int32_t frequency); std::int32_t playLoopedSample(float xPixel, float yPixel, std::int32_t sample, std::int32_t volume, std::int32_t frequency); void playPrioritySample(std::int32_t sample); bool isSampleLoaded(std::int32_t sample); bool loadSample(std::int32_t sample, const String& filename); bool getUseLayer8Speeds(); bool setUseLayer8Speeds(bool value); jjWEAPON* get_jjWEAPON(int index); jjCHARACTER* get_jjCHARACTER(int index); std::int32_t GetEvent(std::uint16_t tx, std::uint16_t ty); std::int32_t GetEventParamWrapper(std::uint16_t tx, std::uint16_t ty, std::int32_t offset, std::int32_t length); void SetEventByte(std::uint16_t tx, std::uint16_t ty, std::uint8_t newEventId); void SetEventParam(std::uint16_t tx, std::uint16_t ty, int8_t offset, std::int8_t length, std::int32_t newValue); std::int8_t GetTileType(std::uint16_t tile); std::int8_t SetTileType(std::uint16_t tile, std::uint16_t value); // TODO std::uint16_t jjGetStaticTile(std::uint16_t tileID); std::uint16_t jjTileGet(std::uint8_t layer, std::int32_t xTile, std::int32_t yTile); std::uint16_t jjTileSet(std::uint8_t layer, std::int32_t xTile, std::int32_t yTile, std::uint16_t newTile); void jjGenerateSettableTileArea(std::uint8_t layer, std::int32_t xTile, std::int32_t yTile, std::int32_t width, std::int32_t height); // TODO bool jjMaskedPixel(std::int32_t xPixel, std::int32_t yPixel); bool jjMaskedPixelLayer(std::int32_t xPixel, std::int32_t yPixel, std::uint8_t layer); bool jjMaskedHLine(std::int32_t xPixel, std::int32_t lineLength, std::int32_t yPixel); bool jjMaskedHLineLayer(std::int32_t xPixel, std::int32_t lineLength, std::int32_t yPixel, std::uint8_t layer); bool jjMaskedVLine(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength); bool jjMaskedVLineLayer(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength, std::uint8_t layer); bool jjMaskedTopVLine(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength); bool jjMaskedTopVLineLayer(std::int32_t xPixel, std::int32_t yPixel, std::int32_t lineLength, std::uint8_t layer); // TODO void jjSetModPosition(std::int32_t order, std::int32_t row, bool reset); void jjSlideModChannelVolume(std::int32_t channel, float volume, std::int32_t milliseconds); std::int32_t jjGetModOrder(); std::int32_t jjGetModRow(); std::int32_t jjGetModTempo(); void jjSetModTempo(std::uint8_t speed); std::int32_t jjGetModSpeed(); void jjSetModSpeed(std::uint8_t speed); std::uint32_t getCustomSetID(std::uint8_t index); } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/LevelScriptLoader.cpp000066400000000000000000010104161512772601700275670ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "LevelScriptLoader.h" #include "RegisterMath.h" #include "RegisterRef.h" #include "RegisterString.h" #include "RegisterArray.h" #include "RegisterDictionary.h" #if defined(WITH_IMGUI) # include "RegisterImGuiBindings.h" #endif #include "ScriptActorWrapper.h" #include "ScriptPlayerWrapper.h" #include "../ContentResolver.h" #include "../LevelHandler.h" #include "../PreferencesCache.h" #include "../Actors/ActorBase.h" #include "../Actors/Player.h" #include "../Compatibility/JJ2Strings.h" #include "../UI/InGameConsole.h" #include "../../nCine/Base/Random.h" #if defined(DEATH_TRACE) # define AS_LOG_EXCEPTION(ctx) \ do { \ std::int32_t column = 0; const char* sectionName = nullptr; \ std::int32_t line = ctx->GetExceptionLineNumber(&column, §ionName); \ LOGE("{}:{}({}) ‡ An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", \ sectionName, line, column, ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); \ } while (false) #else # define AS_LOG_EXCEPTION(ctx) do {} while (false) #endif // Without namespace for shorter log messages static void asScript(String& msg) { LOGI("{}", msg.data()); } namespace Jazz2::Scripting { static float asFractionf(float v) { float intPart; return modff(v, &intPart); } static std::int32_t asRandom() { return Random().Next(); } static std::int32_t asRandom(std::int32_t max) { return Random().Fast(0, max); } static float asRandom(float min, float max) { return Random().FastFloat(min, max); } LevelScriptLoader::LevelScriptLoader(LevelHandler* levelHandler, StringView scriptPath) : _levelHandler(levelHandler), _onLevelUpdate(nullptr), _onLevelUpdateLastFrame(-1), _onDrawAmmo(nullptr), _onDrawHealth(nullptr), _onDrawLives(nullptr), _onDrawPlayerTimer(nullptr), _onDrawScore(nullptr), _onDrawGameModeHUD(nullptr), _enabledCallbacks(NoInit, 256) { // Try to load the script HashMap DefinedSymbols = { #if defined(DEATH_TARGET_ANDROID) { "TARGET_ANDROID"_s, true }, #elif defined(DEATH_TARGET_APPLE) { "TARGET_APPLE"_s, true }, # if defined(DEATH_TARGET_IOS) { "TARGET_IOS"_s, true }, # endif #elif defined(DEATH_TARGET_EMSCRIPTEN) { "TARGET_EMSCRIPTEN"_s, true }, #elif defined(DEATH_TARGET_WINDOWS) { "TARGET_WINDOWS"_s, true }, # if defined(DEATH_TARGET_WINDOWS_RT) { "TARGET_WINDOWS_RT"_s, true }, # endif #elif defined(DEATH_TARGET_SWITCH) { "TARGET_SWITCH"_s, true }, #elif defined(DEATH_TARGET_UNIX) { "TARGET_UNIX"_s, true }, #endif #if defined(DEATH_TARGET_BIG_ENDIAN) { "TARGET_BIG_ENDIAN"_s, true }, #endif #if defined(WITH_OPENGLES) { "WITH_OPENGLES"_s, true }, #endif #if defined(WITH_AUDIO) { "WITH_AUDIO"_s, true }, #endif #if defined(WITH_VORBIS) { "WITH_VORBIS"_s, true }, #endif #if defined(WITH_OPENMPT) { "WITH_OPENMPT"_s, true }, #endif #if defined(WITH_IMGUI) { "WITH_IMGUI"_s, true }, #endif #if defined(WITH_THREADS) { "WITH_THREADS"_s, true }, #endif { "Resurrection"_s, true } }; auto contextType = AddScriptFromFile(scriptPath, DefinedSymbols); if (contextType == ScriptContextType::Unknown) { LOGE("Cannot compile the script. Please correct the code and try again."); #if defined(DEATH_DEBUG) levelHandler->_console->WriteLine(UI::MessageLevel::Error, "Cannot compile the script. Please correct the code and try again."_s); #endif return; } SetContextType(contextType); RegisterBuiltInFunctions(GetEngine()); switch (GetContextType()) { case ScriptContextType::Legacy: LOGD("Compiling script with \"Legacy\" context"); #if defined(DEATH_DEBUG) levelHandler->_console->WriteLine(UI::MessageLevel::Debug, "Compiling script with \"Legacy\" context"_s); #endif RegisterLegacyFunctions(GetEngine()); break; case ScriptContextType::Standard: LOGD("Compiling script with \"Standard\" context"); #if defined(DEATH_DEBUG) levelHandler->_console->WriteLine(UI::MessageLevel::Debug, "Compiling script with \"Standard\" context"_s); #endif RegisterStandardFunctions(GetEngine(), GetMainModule()); break; } ScriptBuildResult r = Build(); if (r != ScriptBuildResult::Success) { LOGE("Cannot compile the script. Please correct the code and try again."); #if defined(DEATH_DEBUG) levelHandler->_console->WriteLine(UI::MessageLevel::Error, "Cannot compile the script. Please correct the code and try again."_s); #endif return; } LOGD("Script compiled successfully."); // Enable all callbacks by default _enabledCallbacks.setAll(); switch (GetContextType()) { case ScriptContextType::Legacy: _onLevelUpdate = GetMainModule()->GetFunctionByDecl("void onMain()"); _onDrawAmmo = GetMainModule()->GetFunctionByDecl("bool onDrawAmmo(jjPLAYER@ player, jjCANVAS@ canvas)"); _onDrawHealth = GetMainModule()->GetFunctionByDecl("bool onDrawHealth(jjPLAYER@ player, jjCANVAS@ canvas)"); _onDrawLives = GetMainModule()->GetFunctionByDecl("bool onDrawLives(jjPLAYER@ player, jjCANVAS@ canvas)"); _onDrawPlayerTimer = GetMainModule()->GetFunctionByDecl("bool onDrawPlayerTimer(jjPLAYER@ player, jjCANVAS@ canvas)"); _onDrawScore = GetMainModule()->GetFunctionByDecl("bool onDrawScore(jjPLAYER@ player, jjCANVAS@ canvas)"); _onDrawGameModeHUD = GetMainModule()->GetFunctionByDecl("bool onDrawGameModeHUD(jjPLAYER@ player, jjCANVAS@ canvas)"); break; case ScriptContextType::Standard: _onLevelUpdate = GetMainModule()->GetFunctionByDecl("void onLevelUpdate(float)"); // TODO: Add draw callbacks break; } } String LevelScriptLoader::OnProcessInclude(StringView includePath, StringView scriptPath) { // Skip MLLE files, because it's handled natively if (includePath.hasPrefix("MLLE-Include-"_s) && includePath.hasSuffix(".asc"_s)) { return {}; } // TODO: Allow multiple search paths //return ConstructPath(includePath, path); auto sourcePath = ContentResolver::Get().GetSourcePath(); return fs::FindPathCaseInsensitive(fs::CombinePath(sourcePath, includePath)); } void LevelScriptLoader::OnProcessPragma(StringView content, ScriptContextType& contextType) { // #pragma target Jazz² Resurrection - Changes script context type to Standard if (content == "target Jazz² Resurrection"_s || content == "target Jazz2 Resurrection"_s) { contextType = ScriptContextType::Standard; } } void LevelScriptLoader::OnBeforeScriptCall() { } void LevelScriptLoader::OnAfterScriptCall() { for (auto& p : _playerBackingStore) { p.second->SyncPropertiesFromBackingStore(); } } void LevelScriptLoader::OnLevelLoad() { asIScriptFunction* func = GetMainModule()->GetFunctionByDecl("void onLevelLoad()"); if (func == nullptr) { return; } OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); } void LevelScriptLoader::OnLevelBegin() { asIScriptFunction* func = GetMainModule()->GetFunctionByDecl("void onLevelBegin()"); if (func == nullptr) { return; } OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); } void LevelScriptLoader::OnLevelReload() { asIScriptFunction* func = GetMainModule()->GetFunctionByDecl("void onLevelReload()"); if (func == nullptr) { return; } OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); } void LevelScriptLoader::OnLevelUpdate(float timeMult) { switch (GetContextType()) { case ScriptContextType::Legacy: { asIScriptFunction* onPlayer = GetMainModule()->GetFunctionByDecl("void onPlayer(jjPLAYER@)"); if (_onLevelUpdate == nullptr && onPlayer == nullptr) { _onLevelUpdateLastFrame = (std::int32_t)_levelHandler->_elapsedFrames; break; } // Legacy context requires fixed frame count per second OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); // It should update at 70 FPS instead of 60 FPS std::int32_t currentFrame = (std::int32_t)(_levelHandler->_elapsedFrames * (70.0f / 60.0f)); while (_onLevelUpdateLastFrame <= currentFrame) { if (_onLevelUpdate != nullptr) { ctx->Prepare(_onLevelUpdate); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); // Don't call the method again if an exception occurs //_onLevelUpdate = nullptr; } } if (onPlayer != nullptr) { for (auto* player : _levelHandler->_players) { ctx->Prepare(onPlayer); jjPLAYER* p = GetPlayerBackingStore(player); ctx->SetArgObject(0, p); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); // Don't call the method again if an exception occurs //_onLevelUpdate = nullptr; } } } _onLevelUpdateLastFrame++; } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); break; } case ScriptContextType::Standard: { _onLevelUpdateLastFrame = (int32_t)_levelHandler->_elapsedFrames; if (_onLevelUpdate == nullptr) { break; } // Standard context supports floating frame rate OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(_onLevelUpdate); ctx->SetArgFloat(0, timeMult); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); // Don't call the method again if an exception occurs _onLevelUpdate = nullptr; } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); break; } } for (auto* player : _levelHandler->_players) { auto it = _playerBackingStore.find(player->GetPlayerIndex()); if (it != _playerBackingStore.end()) { if (it->second->_timerState == 1) { // STARTED it->second->_timerLeft -= timeMult; LOGD("Player timer decremented ({})", it->second->_timerLeft); if (it->second->_timerLeft <= 0.0f) { it->second->_timerState = 0; // STOPPED asIScriptFunction* func = (asIScriptFunction*)it->second->_timerCallback; if (func == nullptr) { func = GetMainModule()->GetFunctionByName("onPlayerTimerEnd"); if (func == nullptr) { continue; } } OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); std::int32_t typeId = 0; if (func->GetParam(0, &typeId) >= 0) { if ((typeId & (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) == (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) { asITypeInfo* typeInfo = GetEngine()->GetTypeInfoById(typeId); if (typeInfo->GetName() == "jjPLAYER"_s) { jjPLAYER* p = GetPlayerBackingStore(player); ctx->SetArgObject(0, p); } } } std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); } } } } } void LevelScriptLoader::OnLevelCallback(Actors::ActorBase* initiator, uint8_t* eventParams) { std::uint32_t callbackId = eventParams[0]; if (!_enabledCallbacks[callbackId]) { return; } if (eventParams[2]) { // Vanish _enabledCallbacks.reset(callbackId); } char funcName[32]; std::size_t length = formatInto(funcName, "onFunction{}", callbackId); funcName[length] = '\0'; asIScriptFunction* func = GetMainModule()->GetFunctionByName(funcName); if (func != nullptr) { OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); std::int32_t paramIdx = 0; std::int32_t typeId = 0; if (func->GetParam(paramIdx, &typeId) >= 0) { if ((typeId & (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) == (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) { asITypeInfo* typeInfo = GetEngine()->GetTypeInfoById(typeId); if (typeInfo->GetName() == "jjPLAYER"_s) { if (auto* player = runtime_cast(initiator)) { jjPLAYER* p = GetPlayerBackingStore(player); ctx->SetArgObject(0, p); } else { // Player is required but wasn't provided return; } } paramIdx++; } } if (func->GetParam(paramIdx, &typeId) >= 0) { if (typeId == asTYPEID_BOOL || typeId == asTYPEID_INT8 || typeId == asTYPEID_UINT8) { ctx->SetArgByte(1, eventParams[1]); paramIdx++; } } std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); return; } LOGW("Callback function \"{}\" was not found in the script. Please correct the code and try again.", funcName); } bool LevelScriptLoader::OnDraw(UI::HUD* hud, Actors::Player* player, const Rectf& view, DrawType type) { asIScriptFunction* func; switch (type) { case DrawType::WeaponAmmo: func = _onDrawAmmo; break; case DrawType::Health: func = _onDrawHealth; break; case DrawType::Lives: func = _onDrawLives; break; case DrawType::PlayerTimer: func = _onDrawPlayerTimer; break; case DrawType::Score: func = _onDrawScore; break; case DrawType::GameModeHUD: func = _onDrawGameModeHUD; break; default: func = nullptr; break; } bool overrideDraw = false; if (func != nullptr) { // TODO: Move this somewhere checkedMaxSubVideoWidth = (std::int32_t)view.W; checkedMaxSubVideoHeight = (std::int32_t)view.H; realVideoW = (std::int32_t)view.W; realVideoH = (std::int32_t)view.H; subVideoW = (std::int32_t)view.W; subVideoH = (std::int32_t)view.H; OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); jjPLAYER* p = GetPlayerBackingStore(player); ctx->SetArgObject(0, p); jjCANVAS* canvasWrapper = new(asAllocMem(sizeof(jjCANVAS))) jjCANVAS(hud, view); // TODO: Cache this object ??? ctx->SetArgObject(1, canvasWrapper); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_FINISHED) { overrideDraw = (ctx->GetReturnByte() != 0); } else if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); // TODO asFreeMem(canvasWrapper); } return overrideDraw; } void LevelScriptLoader::OnPlayerDied(Actors::Player* player, Actors::ActorBase* collider) { auto it = _playerBackingStore.find(player->GetPlayerIndex()); if (it != _playerBackingStore.end()) { if (!it->second->_timerPersists) { it->second->_timerState = 0; // STOPPED } } asIScriptFunction* func = GetMainModule()->GetFunctionByName("onRoast"); if (func != nullptr) { OnBeforeScriptCall(); asIScriptContext* ctx = GetEngine()->RequestContext(); ctx->Prepare(func); std::int32_t typeId = 0; if (func->GetParam(0, &typeId) >= 0) { if ((typeId & (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) == (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) { asITypeInfo* typeInfo = GetEngine()->GetTypeInfoById(typeId); if (typeInfo->GetName() == "jjPLAYER"_s) { jjPLAYER* p = GetPlayerBackingStore(player); ctx->SetArgObject(0, p); } } } if (func->GetParam(1, &typeId) >= 0) { if ((typeId & (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) == (asTYPEID_OBJHANDLE | asTYPEID_APPOBJECT)) { asITypeInfo* typeInfo = GetEngine()->GetTypeInfoById(typeId); if (typeInfo->GetName() == "jjPLAYER"_s) { // TODO: Detect weapons properly if (auto* killer = runtime_cast(collider)) { jjPLAYER* p = GetPlayerBackingStore(killer); ctx->SetArgObject(0, p); } else { ctx->SetArgObject(0, nullptr); } } } } std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { AS_LOG_EXCEPTION(ctx); } GetEngine()->ReturnContext(ctx); OnAfterScriptCall(); } } void LevelScriptLoader::RegisterBuiltInFunctions(asIScriptEngine* engine) { RegisterMath(engine); RegisterRef(engine); RegisterString(engine); RegisterArray(engine); RegisterDictionary(engine); #if defined(WITH_IMGUI) RegisterImGuiBindings(engine); #endif // Math functions int r; r = engine->RegisterGlobalFunction("float cos(float)", asFUNCTIONPR(cosf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float sin(float)", asFUNCTIONPR(sinf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float tan(float)", asFUNCTIONPR(tanf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float acos(float)", asFUNCTIONPR(acosf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float asin(float)", asFUNCTIONPR(asinf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float atan(float)", asFUNCTIONPR(atanf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float atan2(float, float)", asFUNCTIONPR(atan2f, (float, float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float cosh(float)", asFUNCTIONPR(coshf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float sinh(float)", asFUNCTIONPR(sinhf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float tanh(float)", asFUNCTIONPR(tanhf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float log(float)", asFUNCTIONPR(logf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float log10(float)", asFUNCTIONPR(log10f, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float pow(float, float)", asFUNCTIONPR(powf, (float, float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float sqrt(float)", asFUNCTIONPR(sqrtf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float ceil(float)", asFUNCTIONPR(ceilf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float abs(float)", asFUNCTIONPR(fabsf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float floor(float)", asFUNCTIONPR(floorf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float fraction(float)", asFUNCTIONPR(asFractionf, (float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); } void LevelScriptLoader::RegisterLegacyFunctions(asIScriptEngine* engine) { // JJ2+ Declarations (provided by JJ2+ team on 2023/01/06) engine->SetDefaultNamespace(""); engine->RegisterGlobalFunction("float jjSin(uint angle)", asFUNCTION(get_sinTable), asCALL_CDECL); engine->RegisterGlobalFunction("float jjCos(uint angle)", asFUNCTION(get_cosTable), asCALL_CDECL); engine->RegisterGlobalFunction("uint jjRandom()", asFUNCTION(RandWord32), asCALL_CDECL); engine->RegisterGlobalFunction("uint64 jjUnixTimeSec()", asFUNCTION(unixTimeSec), asCALL_CDECL); engine->RegisterGlobalFunction("uint64 jjUnixTimeMs()", asFUNCTION(unixTimeMs), asCALL_CDECL); //engine->RegisterGlobalFunction("bool jjIsValidCheat(const string &in text)", asFUNCTION(stringIsCheat), asCALL_CDECL); //engine->RegisterGlobalProperty("const bool jjDebugF10", &F10Debug); engine->RegisterGlobalFunction("bool jjRegexIsValid(const string &in expression)", asFUNCTION(jjRegexIsValid), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjRegexMatch(const string &in text, const string &in expression, bool ignoreCase = false)", asFUNCTION(jjRegexMatch), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjRegexMatch(const string &in text, const string &in expression, array &out results, bool ignoreCase = false)", asFUNCTION(jjRegexMatchWithResults), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjRegexSearch(const string &in text, const string &in expression, bool ignoreCase = false)", asFUNCTION(jjRegexSearch), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjRegexSearch(const string &in text, const string &in expression, array &out results, bool ignoreCase = false)", asFUNCTION(jjRegexSearchWithResults), asCALL_CDECL); engine->RegisterGlobalFunction("string jjRegexReplace(const string &in text, const string &in expression, const string &in replacement, bool ignoreCase= false)", asFUNCTION(jjRegexReplace), asCALL_CDECL); engine->RegisterGlobalProperty("const bool jjAutoWeaponChange", &jjAutoWeaponChange); engine->RegisterGlobalProperty("const int jjScriptModuleID", &jjScriptModuleID); engine->RegisterGlobalProperty("const int jjGameTicks", &_onLevelUpdateLastFrame); engine->RegisterGlobalProperty("const uint jjActiveGameTicks", &gameTicksSpentWhileActive); engine->RegisterGlobalProperty("const int jjRenderFrame", &renderFrame); engine->RegisterGlobalFunction("int get_jjFPS()", asFUNCTION(GetFPS), asCALL_CDECL); engine->RegisterGlobalProperty("const bool jjIsTSF", &versionTSF); engine->RegisterGlobalFunction("bool get_jjIsAdmin()", asFUNCTION(isAdmin), asCALL_CDECL); engine->RegisterGlobalProperty("const bool jjIsServer", &isServer); engine->RegisterGlobalFunction("int get_jjDifficulty()", asFUNCTION(GetDifficulty), asCALL_CDECL); engine->RegisterGlobalFunction("int set_jjDifficulty(int)", asFUNCTION(SetDifficulty), asCALL_CDECL); engine->RegisterGlobalProperty("int jjDifficultyNext", &DifficultyForNextLevel); engine->RegisterGlobalProperty("const int jjDifficultyOrig", &DifficultyAtLevelStart); engine->RegisterGlobalFunction("string get_jjLevelFileName()", asFUNCTION(getLevelFileName), asCALL_CDECL); engine->RegisterGlobalFunction("string get_jjLevelName()", asFUNCTION(getCurrLevelName), asCALL_CDECL); engine->RegisterGlobalFunction("void set_jjLevelName(const string &in)", asFUNCTION(setCurrLevelName), asCALL_CDECL); engine->RegisterGlobalFunction("string get_jjMusicFileName()", asFUNCTION(get_jjMusicFileName), asCALL_CDECL); engine->RegisterGlobalFunction("string get_jjTilesetFileName()", asFUNCTION(get_jjTilesetFileName), asCALL_CDECL); engine->RegisterGlobalProperty("const uint jjTileCount", &numberOfTiles); engine->RegisterGlobalFunction("string get_jjHelpStrings(uint)", asFUNCTION(get_jjHelpStrings), asCALL_CDECL); engine->RegisterGlobalFunction("void set_jjHelpStrings(uint, const string &in)", asFUNCTION(set_jjHelpStrings), asCALL_CDECL); engine->SetDefaultNamespace("GAME"); engine->RegisterEnum("State"); engine->RegisterEnumValue("State", "STOPPED", gameSTOPPED); engine->RegisterEnumValue("State", "STARTED", gameSTARTED); engine->RegisterEnumValue("State", "PAUSED", gamePAUSED); engine->RegisterEnumValue("State", "PREGAME", gamePREGAME); engine->RegisterEnumValue("State", "OVERTIME", gameOVERTIME); engine->RegisterEnum("Mode"); engine->RegisterEnumValue("Mode", "SP", GM_SP); engine->RegisterEnumValue("Mode", "COOP", GM_COOP); engine->RegisterEnumValue("Mode", "BATTLE", GM_BATTLE); engine->RegisterEnumValue("Mode", "CTF", GM_CTF); engine->RegisterEnumValue("Mode", "TREASURE", GM_TREASURE); engine->RegisterEnumValue("Mode", "RACE", GM_RACE); engine->RegisterEnum("Custom"); engine->RegisterEnumValue("Custom", "NOCUSTOM", 0); engine->RegisterEnumValue("Custom", "RT", 1); engine->RegisterEnumValue("Custom", "LRS", 2); engine->RegisterEnumValue("Custom", "XLRS", 3); engine->RegisterEnumValue("Custom", "PEST", 4); engine->RegisterEnumValue("Custom", "TB", 5); engine->RegisterEnumValue("Custom", "JB", 6); engine->RegisterEnumValue("Custom", "DCTF", 7); engine->RegisterEnumValue("Custom", "FR", 8); engine->RegisterEnumValue("Custom", "TLRS", 9); engine->RegisterEnumValue("Custom", "DOM", 10); engine->RegisterEnumValue("Custom", "HEAD", 11); engine->RegisterEnum("Connection"); engine->RegisterEnumValue("Connection", "LOCAL", gameLOCAL); engine->RegisterEnumValue("Connection", "ONLINE", gameINTERNET); engine->RegisterEnumValue("Connection", "LAN", gameLAN_TCP); engine->SetDefaultNamespace(""); engine->RegisterGlobalFunction("GAME::State get_jjGameState()", asFUNCTION(get_gameState), asCALL_CDECL); engine->RegisterGlobalProperty("const GAME::Mode jjGameMode", &gameMode); engine->RegisterGlobalProperty("const GAME::Custom jjGameCustom", &customMode); engine->RegisterGlobalProperty("const GAME::Connection jjGameConnection", &partyMode); // TODO engine->RegisterObjectType("jjPLAYER", sizeof(jjPLAYER), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterGlobalFunction("const int get_jjPlayerCount()", asFUNCTION(get_jjPlayerCount), asCALL_CDECL); engine->RegisterGlobalFunction("const int get_jjLocalPlayerCount()", asFUNCTION(get_jjLocalPlayerCount), asCALL_CDECL); engine->RegisterGlobalFunction("jjPLAYER@ get_jjP()", asFUNCTION(get_jjP), asCALL_CDECL); // Deprecated engine->RegisterGlobalFunction("jjPLAYER@ get_p()", asFUNCTION(get_jjP), asCALL_CDECL); // Deprecated engine->RegisterGlobalFunction("jjPLAYER@ get_jjPlayers(uint8)", asFUNCTION(get_jjPlayers), asCALL_CDECL); engine->RegisterGlobalFunction("jjPLAYER@ get_jjLocalPlayers(uint8)", asFUNCTION(get_jjLocalPlayers), asCALL_CDECL); engine->SetDefaultNamespace("WEAPON"); engine->RegisterEnum("Weapon"); engine->RegisterEnumValue("Weapon", "BLASTER", 1); engine->RegisterEnumValue("Weapon", "BOUNCER", 2); engine->RegisterEnumValue("Weapon", "ICE", 3); engine->RegisterEnumValue("Weapon", "SEEKER", 4); engine->RegisterEnumValue("Weapon", "RF", 5); engine->RegisterEnumValue("Weapon", "TOASTER", 6); engine->RegisterEnumValue("Weapon", "TNT", 7); engine->RegisterEnumValue("Weapon", "GUN8", 8); engine->RegisterEnumValue("Weapon", "GUN9", 9); engine->RegisterEnumValue("Weapon", "CURRENT", 0); engine->RegisterEnum("Style"); engine->RegisterEnumValue("Style", "NORMAL", wsNORMAL); engine->RegisterEnumValue("Style", "MISSILE", wsMISSILE); engine->RegisterEnumValue("Style", "POPCORN", wsPOPCORN); engine->RegisterEnumValue("Style", "CAPPED", wsCAPPED); engine->RegisterEnumValue("Style", "TUNA", wsCAPPED); // Deprecated engine->SetDefaultNamespace("SPREAD"); engine->RegisterEnum("Spread"); engine->RegisterEnumValue("Spread", "NORMAL", wspNORMAL); engine->RegisterEnumValue("Spread", "ICE", wspNORMALORDIRECTIONANDAIM); engine->RegisterEnumValue("Spread", "ICEPU", wspDIRECTIONANDAIM); engine->RegisterEnumValue("Spread", "RF", wspDOUBLEORTRIPLE); engine->RegisterEnumValue("Spread", "RFNORMAL", wspDOUBLE); engine->RegisterEnumValue("Spread", "RFPU", wspTRIPLE); engine->RegisterEnumValue("Spread", "TOASTER", wspREFLECTSFASTFIRE); engine->RegisterEnumValue("Spread", "GUN8", wspNORMALORBBGUN); engine->RegisterEnumValue("Spread", "PEPPERSPRAY", wspBBGUN); engine->SetDefaultNamespace("GEM"); engine->RegisterEnum("Color"); engine->RegisterEnumValue("Color", "RED", 1); engine->RegisterEnumValue("Color", "GREEN", 2); engine->RegisterEnumValue("Color", "BLUE", 3); engine->RegisterEnumValue("Color", "PURPLE", 4); engine->SetDefaultNamespace("SHIELD"); engine->RegisterEnum("Shield"); engine->RegisterEnumValue("Shield", "NONE", 0); engine->RegisterEnumValue("Shield", "FIRE", 1); engine->RegisterEnumValue("Shield", "BUBBLE", 2); engine->RegisterEnumValue("Shield", "WATER", 2); engine->RegisterEnumValue("Shield", "LIGHTNING", 3); engine->RegisterEnumValue("Shield", "PLASMA", 3); engine->RegisterEnumValue("Shield", "LASER", 4); engine->SetDefaultNamespace(""); engine->RegisterObjectMethod("jjPLAYER", "int get_score() const", asMETHOD(jjPLAYER, get_score), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_score(int)", asMETHOD(jjPLAYER, set_score), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_scoreDisplayed() const", asMETHOD(jjPLAYER, get_scoreDisplayed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_scoreDisplayed(int)", asMETHOD(jjPLAYER, set_scoreDisplayed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int setScore(int score)", asMETHOD(jjPLAYER, setScore), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_xPos() const", asMETHOD(jjPLAYER, get_xPos), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float set_xPos(float)", asMETHOD(jjPLAYER, set_xPos), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_yPos() const", asMETHOD(jjPLAYER, get_yPos), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float set_yPos(float)", asMETHOD(jjPLAYER, set_yPos), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_xAcc() const", asMETHOD(jjPLAYER, get_xAcc), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float set_xAcc(float)", asMETHOD(jjPLAYER, set_xAcc), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_yAcc() const", asMETHOD(jjPLAYER, get_yAcc), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float set_yAcc(float)", asMETHOD(jjPLAYER, set_yAcc), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_xOrg() const", asMETHOD(jjPLAYER, get_xOrg), asCALL_THISCALL); //engine->RegisterObjectMethod("jjPLAYER", "float set_xOrg(float)", asMETHOD(jjPLAYER, set_xOrg), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_yOrg() const", asMETHOD(jjPLAYER, get_yOrg), asCALL_THISCALL); //engine->RegisterObjectMethod("jjPLAYER", "float set_yOrg(float)", asMETHOD(jjPLAYER, set_yOrg), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_xSpeed() const", asMETHOD(jjPLAYER, get_xSpeed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float set_xSpeed(float)", asMETHOD(jjPLAYER, set_xSpeed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_ySpeed() const", asMETHOD(jjPLAYER, get_ySpeed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float set_ySpeed(float)", asMETHOD(jjPLAYER, set_ySpeed), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "float jumpStrength", asOFFSET(jjPLAYER, jumpStrength)); engine->RegisterObjectProperty("jjPLAYER", "int8 frozen", asOFFSET(jjPLAYER, frozen)); engine->RegisterObjectMethod("jjPLAYER", "void freeze(bool frozen = true)", asMETHOD(jjPLAYER, freeze), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_currTile() const", asMETHOD(jjPLAYER, get_currTile), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool startSugarRush(int time = 1400)", asMETHOD(jjPLAYER, startSugarRush), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int8 get_health() const", asMETHOD(jjPLAYER, get_health), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int8 set_health(int8)", asMETHOD(jjPLAYER, set_health), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "const int warpID", asOFFSET(jjPLAYER, warpID)); engine->RegisterObjectMethod("jjPLAYER", "int get_fastfire() const", asMETHOD(jjPLAYER, get_fastfire), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_fastfire(int)", asMETHOD(jjPLAYER, set_fastfire), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint8 get_currWeapon() const", asMETHOD(jjPLAYER, get_currWeapon), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint8 set_currWeapon(uint8)", asMETHOD(jjPLAYER, set_currWeapon), asCALL_THISCALL); //engine->RegisterObjectMethod("jjPLAYER", "int get_lives() const", asMETHOD(jjPLAYER, get_lives), asCALL_THISCALL); //engine->RegisterObjectMethod("jjPLAYER", "int set_lives(int)", asMETHOD(jjPLAYER, set_lives), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "int lives", asOFFSET(jjPLAYER, lives)); engine->RegisterObjectMethod("jjPLAYER", "int get_invincibility() const", asMETHOD(jjPLAYER, get_invincibility), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_invincibility(int)", asMETHOD(jjPLAYER, set_invincibility), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_blink() const", asMETHOD(jjPLAYER, get_blink), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_blink(int)", asMETHOD(jjPLAYER, set_blink), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int extendInvincibility(int duration)", asMETHOD(jjPLAYER, extendInvincibility), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_food() const", asMETHOD(jjPLAYER, get_food), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_food(int)", asMETHOD(jjPLAYER, set_food), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_coins() const", asMETHOD(jjPLAYER, get_coins), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_coins(int)", asMETHOD(jjPLAYER, set_coins), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool testForCoins(int numberOfCoins)", asMETHOD(jjPLAYER, testForCoins), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_gems(GEM::Color) const", asMETHOD(jjPLAYER, get_gems), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_gems(GEM::Color, int)", asMETHOD(jjPLAYER, set_gems), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool testForGems(int numberOfGems, GEM::Color type)", asMETHOD(jjPLAYER, testForGems), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_shieldType() const", asMETHOD(jjPLAYER, get_shieldType), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_shieldType(int)", asMETHOD(jjPLAYER, set_shieldType), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_shieldTime() const", asMETHOD(jjPLAYER, get_shieldTime), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_shieldTime(int)", asMETHOD(jjPLAYER, set_shieldTime), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_ballTime() const", asMETHOD(jjPLAYER, get_rolling), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_ballTime(int)", asMETHOD(jjPLAYER, set_rolling), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "int boss", asOFFSET(jjPLAYER, bossNumber)); engine->RegisterObjectProperty("jjPLAYER", "bool bossActivated", asOFFSET(jjPLAYER, bossActive)); engine->RegisterObjectProperty("jjPLAYER", "int8 direction", asOFFSET(jjPLAYER, direction)); engine->RegisterObjectProperty("jjPLAYER", "int platform", asOFFSET(jjPLAYER, platform)); engine->RegisterObjectProperty("jjPLAYER", "const int flag", asOFFSET(jjPLAYER, flag)); engine->RegisterObjectProperty("jjPLAYER", "const int clientID", asOFFSET(jjPLAYER, clientID)); engine->RegisterObjectMethod("jjPLAYER", "int8 get_playerID() const", asMETHOD(jjPLAYER, get_playerID), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_localPlayerID() const", asMETHOD(jjPLAYER, get_localPlayerID), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "const bool teamRed", asOFFSET(jjPLAYER, team)); engine->RegisterObjectMethod("jjPLAYER", "bool get_running() const", asMETHOD(jjPLAYER, get_running), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_running(bool)", asMETHOD(jjPLAYER, set_running), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "bool alreadyDoubleJumped", asOFFSET(jjPLAYER, specialJump)); // Deprecated engine->RegisterObjectProperty("jjPLAYER", "int doubleJumpCount", asOFFSET(jjPLAYER, specialJump)); engine->RegisterObjectMethod("jjPLAYER", "int get_stoned() const", asMETHOD(jjPLAYER, get_stoned), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_stoned(int)", asMETHOD(jjPLAYER, set_stoned), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "int buttstomp", asOFFSET(jjPLAYER, buttstomp)); engine->RegisterObjectProperty("jjPLAYER", "int helicopter", asOFFSET(jjPLAYER, helicopter)); engine->RegisterObjectProperty("jjPLAYER", "int helicopterElapsed", asOFFSET(jjPLAYER, helicopterElapsed)); engine->RegisterObjectProperty("jjPLAYER", "int specialMove", asOFFSET(jjPLAYER, specialMove)); engine->RegisterObjectProperty("jjPLAYER", "int idle", asOFFSET(jjPLAYER, idle)); engine->RegisterObjectMethod("jjPLAYER", "void suckerTube(int xSpeed, int ySpeed, bool center, bool noclip = false, bool trigSample = false)", asMETHOD(jjPLAYER, suckerTube), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void poleSpin(float xSpeed, float ySpeed, uint delay = 70)", asMETHOD(jjPLAYER, poleSpin), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void spring(float xSpeed, float ySpeed, bool keepZeroSpeeds, bool sample)", asMETHOD(jjPLAYER, spring), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "const bool isLocal", asOFFSET(jjPLAYER, isLocal)); engine->RegisterObjectProperty("jjPLAYER", "const bool isActive", asOFFSET(jjPLAYER, isActive)); engine->RegisterObjectMethod("jjPLAYER", "bool get_isConnecting() const", asMETHOD(jjPLAYER, get_isConnecting), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isIdle() const", asMETHOD(jjPLAYER, get_isIdle), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isOut() const", asMETHOD(jjPLAYER, get_isOut), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isSpectating() const", asMETHOD(jjPLAYER, get_isSpectating), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isInGame() const", asMETHOD(jjPLAYER, get_isInGame), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "string get_name() const", asMETHOD(jjPLAYER, get_name), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "string get_nameUnformatted() const", asMETHOD(jjPLAYER, get_nameUnformatted), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool setName(const string &in name)", asMETHOD(jjPLAYER, setName), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int8 get_light() const", asMETHOD(jjPLAYER, get_light), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int8 set_light(int8)", asMETHOD(jjPLAYER, set_light), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint32 get_fur() const", asMETHOD(jjPLAYER, get_fur), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint32 set_fur(uint32)", asMETHOD(jjPLAYER, set_fur), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void furGet(uint8 &out a, uint8 &out b, uint8 &out c, uint8 &out d) const", asMETHOD(jjPLAYER, getFur), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void furSet(uint8 a, uint8 b, uint8 c, uint8 d)", asMETHOD(jjPLAYER, setFur), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_noFire() const", asMETHOD(jjPLAYER, get_noFire), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_noFire(bool)", asMETHOD(jjPLAYER, set_noFire), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_antiGrav() const", asMETHOD(jjPLAYER, get_antiGrav), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_antiGrav(bool)", asMETHOD(jjPLAYER, set_antiGrav), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_invisibility() const", asMETHOD(jjPLAYER, get_invisibility), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_invisibility(bool)", asMETHOD(jjPLAYER, set_invisibility), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_noclipMode() const", asMETHOD(jjPLAYER, get_noclipMode), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_noclipMode(bool)", asMETHOD(jjPLAYER, set_noclipMode), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint8 get_lighting() const", asMETHOD(jjPLAYER, get_lighting), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint8 set_lighting(uint8)", asMETHOD(jjPLAYER, set_lighting), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "uint8 resetLight()", asMETHOD(jjPLAYER, resetLight), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyLeft() const", asMETHOD(jjPLAYER, get_playerKeyLeftPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyRight() const", asMETHOD(jjPLAYER, get_playerKeyRightPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyUp() const", asMETHOD(jjPLAYER, get_playerKeyUpPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyDown() const", asMETHOD(jjPLAYER, get_playerKeyDownPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyFire() const", asMETHOD(jjPLAYER, get_playerKeyFirePressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keySelect() const", asMETHOD(jjPLAYER, get_playerKeySelectPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyJump() const", asMETHOD(jjPLAYER, get_playerKeyJumpPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_keyRun() const", asMETHOD(jjPLAYER, get_playerKeyRunPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyLeft(bool)", asMETHOD(jjPLAYER, set_playerKeyLeftPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyRight(bool)", asMETHOD(jjPLAYER, set_playerKeyRightPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyUp(bool)", asMETHOD(jjPLAYER, set_playerKeyUpPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyDown(bool)", asMETHOD(jjPLAYER, set_playerKeyDownPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyFire(bool)", asMETHOD(jjPLAYER, set_playerKeyFirePressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keySelect(bool)", asMETHOD(jjPLAYER, set_playerKeySelectPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyJump(bool)", asMETHOD(jjPLAYER, set_playerKeyJumpPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_keyRun(bool)", asMETHOD(jjPLAYER, set_playerKeyRunPressed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_powerup(uint8) const", asMETHOD(jjPLAYER, get_powerup), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_powerup(uint8, bool)", asMETHOD(jjPLAYER, set_powerup), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_ammo(uint8) const", asMETHOD(jjPLAYER, get_ammo), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_ammo(uint8, int)", asMETHOD(jjPLAYER, set_ammo), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool offsetPosition(int xPixels, int yPixels)", asMETHOD(jjPLAYER, offsetPosition), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool warpToTile(int xTile, int yTile, bool fast = false)", asMETHOD(jjPLAYER, warpToTile), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool warpToID(uint8 warpID, bool fast = false)", asMETHOD(jjPLAYER, warpToID), asCALL_THISCALL); engine->SetDefaultNamespace("CHAR"); engine->RegisterEnum("Char"); engine->RegisterEnumValue("Char", "JAZZ", mJAZZ); engine->RegisterEnumValue("Char", "SPAZ", mSPAZ); engine->RegisterEnumValue("Char", "LORI", mLORI); engine->RegisterEnumValue("Char", "BIRD", mBIRD); engine->RegisterEnumValue("Char", "BIRD2", mCHUCK); engine->RegisterEnumValue("Char", "FROG", mFROG); engine->SetDefaultNamespace(""); engine->RegisterObjectMethod("jjPLAYER", "CHAR::Char morph(bool rabbitsOnly = false, bool morphEffect = true)", asMETHOD(jjPLAYER, morph), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "CHAR::Char morphTo(CHAR::Char charNew, bool morphEffect = true)", asMETHOD(jjPLAYER, morphTo), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "CHAR::Char revertMorph(bool morphEffect = true)", asMETHOD(jjPLAYER, revertMorph), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "CHAR::Char get_charCurr() const", asMETHOD(jjPLAYER, get_charCurr), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "CHAR::Char charOrig", asOFFSET(jjPLAYER, charOrig)); engine->SetDefaultNamespace("TEAM"); engine->RegisterEnum("Color"); engine->RegisterEnumValue("Color", "NEUTRAL", -1); engine->RegisterEnumValue("Color", "BLUE", 0); engine->RegisterEnumValue("Color", "RED", 1); engine->RegisterEnumValue("Color", "GREEN", 2); engine->RegisterEnumValue("Color", "YELLOW", 3); engine->SetDefaultNamespace(""); engine->SetDefaultNamespace("CHAT"); engine->RegisterEnum("Type"); engine->RegisterEnumValue("Type", "NORMAL", 0); engine->RegisterEnumValue("Type", "TEAMCHAT", 1); engine->RegisterEnumValue("Type", "WHISPER", 2); engine->RegisterEnumValue("Type", "ME", 3); engine->SetDefaultNamespace(""); engine->RegisterObjectProperty("jjPLAYER", "const TEAM::Color team", asOFFSET(jjPLAYER, team)); engine->RegisterObjectMethod("jjPLAYER", "void kill()", asMETHOD(jjPLAYER, kill), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool hurt(int8 damage = 1, bool forceHurt = false, jjPLAYER@ attacker = null)", asMETHOD(jjPLAYER, hurt), asCALL_THISCALL); // TODO //engine->RegisterGlobalFunction("array@ jjPlayersWithClientID(int clientID)", asFUNCTION(getPlayersWithClientID), asCALL_CDECL);*/ engine->SetDefaultNamespace("TIMER"); engine->RegisterEnum("State"); engine->RegisterEnumValue("State", "STOPPED", 0); engine->RegisterEnumValue("State", "STARTED", 1); engine->RegisterEnumValue("State", "PAUSED", 2); engine->SetDefaultNamespace("STRING"); engine->RegisterEnum("Mode"); engine->RegisterEnumValue("Mode", "NORMAL", 0); engine->RegisterEnumValue("Mode", "DARK", 1); engine->RegisterEnumValue("Mode", "RIGHTALIGN", 2); engine->RegisterEnumValue("Mode", "BOUNCE", 3); engine->RegisterEnumValue("Mode", "SPIN", 4); engine->RegisterEnumValue("Mode", "PALSHIFT", 5); engine->RegisterEnum("Size"); engine->RegisterEnumValue("Size", "SMALL", 1); engine->RegisterEnumValue("Size", "MEDIUM", 0); engine->RegisterEnumValue("Size", "LARGE", 2); engine->SetDefaultNamespace(""); engine->RegisterGlobalFunction("void jjAlert(const ::string &in text, bool sendToAll = false, STRING::Size size = STRING::SMALL)", asFUNCTION(jjAlert), asCALL_CDECL); engine->RegisterGlobalFunction("void jjPrint(const ::string &in text, bool timestamp = false)", asFUNCTION(jjPrint), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDebug(const ::string &in text, bool timestamp = false)", asFUNCTION(jjDebug), asCALL_CDECL); engine->RegisterGlobalFunction("void jjChat(const ::string &in text, bool teamchat = false)", asFUNCTION(jjChat), asCALL_CDECL); engine->RegisterGlobalFunction("void jjConsole(const ::string &in text, bool sendToAll = false)", asFUNCTION(jjConsole), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSpy(const ::string &in text)", asFUNCTION(jjSpy), asCALL_CDECL); engine->RegisterObjectMethod("jjPLAYER", "TIMER::State get_timerState() const", asMETHOD(jjPLAYER, get_timerState), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_timerPersists() const", asMETHOD(jjPLAYER, get_timerPersists), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool set_timerPersists(bool)", asMETHOD(jjPLAYER, set_timerPersists), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "TIMER::State timerStart(int ticks, bool startPaused = false)", asMETHOD(jjPLAYER, timerStart), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "TIMER::State timerPause()", asMETHOD(jjPLAYER, timerPause), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "TIMER::State timerResume()", asMETHOD(jjPLAYER, timerResume), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "TIMER::State timerStop()", asMETHOD(jjPLAYER, timerStop), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_timerTime() const", asMETHOD(jjPLAYER, get_timerTime), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int set_timerTime(int)", asMETHOD(jjPLAYER, set_timerTime), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void timerFunction(const string functionName)", asMETHOD(jjPLAYER, timerFunction), asCALL_THISCALL); engine->RegisterFuncdef("void jjVOIDFUNC()"); engine->RegisterObjectMethod("jjPLAYER", "void timerFunction(jjVOIDFUNC@ function)", asMETHOD(jjPLAYER, timerFunctionPtr), asCALL_THISCALL); engine->RegisterFuncdef("void jjVOIDFUNCPLAYER(jjPLAYER@)"); engine->RegisterObjectMethod("jjPLAYER", "void timerFunction(jjVOIDFUNCPLAYER@ function)", asMETHOD(jjPLAYER, timerFunctionFuncPtr), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool activateBoss(bool activate = true)", asMETHOD(jjPLAYER, activateBoss), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool limitXScroll(uint16 left, uint16 width)", asMETHOD(jjPLAYER, limitXScroll), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void cameraFreeze(float xPixel, float yPixel, bool centered, bool instant)", asMETHOD(jjPLAYER, cameraFreezeFF), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void cameraFreeze(bool xUnfreeze, float yPixel, bool centered, bool instant)", asMETHOD(jjPLAYER, cameraFreezeBF), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void cameraFreeze(float xPixel, bool yUnfreeze, bool centered, bool instant)", asMETHOD(jjPLAYER, cameraFreezeFB), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void cameraFreeze(bool xUnfreeze, bool yUnfreeze, bool centered, bool instant)", asMETHOD(jjPLAYER, cameraFreezeBB), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void cameraUnfreeze(bool instant = true)", asMETHOD(jjPLAYER, cameraUnfreeze), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void showText(string &in text, STRING::Size size = STRING::SMALL)", asMETHOD(jjPLAYER, showText), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "void showText(uint textID, uint offset, STRING::Size size = STRING::SMALL)", asMETHOD(jjPLAYER, showTextByID), asCALL_THISCALL); engine->SetDefaultNamespace("FLIGHT"); engine->RegisterEnum("Mode"); engine->RegisterEnumValue("Mode", "NONE", 0); engine->RegisterEnumValue("Mode", "FLYCARROT", 1); engine->RegisterEnumValue("Mode", "AIRBOARD", -1); engine->SetDefaultNamespace(""); engine->RegisterObjectMethod("jjPLAYER", "FLIGHT::Mode get_fly() const", asMETHOD(jjPLAYER, get_fly), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "FLIGHT::Mode set_fly(FLIGHT::Mode)", asMETHOD(jjPLAYER, set_fly), asCALL_THISCALL); engine->SetDefaultNamespace("DIRECTION"); engine->RegisterEnum("Dir"); engine->RegisterEnumValue("Dir", "RIGHT", dirRIGHT); engine->RegisterEnumValue("Dir", "LEFT", dirLEFT); engine->RegisterEnumValue("Dir", "UP", dirUP); engine->RegisterEnumValue("Dir", "CURRENT", dirCURRENT); engine->SetDefaultNamespace(""); engine->RegisterObjectMethod("jjPLAYER", "int fireBullet(uint8 gun = 0, bool depleteAmmo = true, bool requireAmmo = true, DIRECTION::Dir direction = DIRECTION::CURRENT)", asMETHOD(jjPLAYER, fireBulletDirection), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int fireBullet(uint8 gun, bool depleteAmmo, bool requireAmmo, float angle)", asMETHOD(jjPLAYER, fireBulletAngle), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "const int subscreenX", asOFFSET(jjPLAYER, subscreenX)); engine->RegisterObjectProperty("jjPLAYER", "const int subscreenY", asOFFSET(jjPLAYER, subscreenY)); engine->RegisterObjectMethod("jjPLAYER", "float get_cameraX() const", asMETHOD(jjPLAYER, get_cameraX), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "float get_cameraY() const", asMETHOD(jjPLAYER, get_cameraY), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_deaths() const", asMETHOD(jjPLAYER, get_deaths), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isJailed() const", asMETHOD(jjPLAYER, get_isJailed), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isZombie() const", asMETHOD(jjPLAYER, get_isZombie), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_lrsLives() const", asMETHOD(jjPLAYER, get_lrsLives), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_roasts() const", asMETHOD(jjPLAYER, get_roasts), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_laps() const", asMETHOD(jjPLAYER, get_laps), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_lapTimeCurrent() const", asMETHOD(jjPLAYER, get_lapTimeCurrent), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_lapTimes(uint) const", asMETHOD(jjPLAYER, get_lapTimes), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int get_lapTimeBest() const", asMETHOD(jjPLAYER, get_lapTimeBest), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool get_isAdmin() const", asMETHOD(jjPLAYER, get_isAdmin), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool hasPrivilege(const string &in privilege, uint moduleID = ::jjScriptModuleID) const", asMETHOD(jjPLAYER, hasPrivilege), asCALL_THISCALL); engine->RegisterGlobalProperty("const bool jjLowDetail", &parLowDetail); engine->RegisterGlobalProperty("const int jjColorDepth", &colorDepth); engine->RegisterGlobalProperty("const int jjResolutionMaxWidth", &checkedMaxSubVideoWidth); engine->RegisterGlobalProperty("const int jjResolutionMaxHeight", &checkedMaxSubVideoHeight); engine->RegisterGlobalProperty("const int jjResolutionWidth", &realVideoW); engine->RegisterGlobalProperty("const int jjResolutionHeight", &realVideoH); engine->RegisterGlobalProperty("const int jjSubscreenWidth", &subVideoW); engine->RegisterGlobalProperty("const int jjSubscreenHeight", &subVideoH); engine->RegisterGlobalFunction("int get_jjBorderWidth()", asFUNCTION(getBorderWidth), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjBorderHeight()", asFUNCTION(getBorderHeight), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjVerticalSplitscreen()", asFUNCTION(getSplitscreenType), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjVerticalSplitscreen(bool)", asFUNCTION(setSplitscreenType), asCALL_CDECL); // TODO /*engine->RegisterGlobalProperty("const bool jjAllowsFireball", &checkedFireball); engine->RegisterGlobalProperty("const bool jjAllowsMouseAim", &checkedAllowMouseAim); engine->RegisterGlobalProperty("const bool jjAllowsReady", &checkedAllowReady); engine->RegisterGlobalProperty("const bool jjAllowsWalljump", &checkedAllowWalljump); engine->RegisterGlobalProperty("const bool jjAlwaysRunning", &alwaysRunning); engine->RegisterGlobalProperty("const bool jjAutoWeaponChange", &weaponChange); engine->RegisterGlobalProperty("const bool jjFriendlyFire", &checkedFriendlyFire); engine->RegisterGlobalProperty("const bool jjMouseAim", &mouseAim); engine->RegisterGlobalProperty("const bool jjNoBlink", &checkedNoBlink); engine->RegisterGlobalProperty("const bool jjNoMovement", &checkedNoMovement); engine->RegisterGlobalProperty("const bool jjQuirks", &testQuirksMode); engine->RegisterGlobalProperty("const bool jjShowMaxHealth", &showEmptyHearts); engine->RegisterGlobalProperty("const bool jjStrongPowerups", &checkedStrongPowerups);*/ engine->RegisterGlobalProperty("const int jjMaxScore", &maxScore); engine->RegisterGlobalFunction("int get_jjTeamScore(TEAM::Color)", asFUNCTION(get_teamScore), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjMaxHealth()", asFUNCTION(GetMaxHealth), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjStartHealth()", asFUNCTION(GetStartHealth), asCALL_CDECL); /*engine->RegisterGlobalProperty("const bool jjDoZombiesAlreadyExist", &doZombiesAlreadyExist); engine->RegisterGlobalFunction("jjPLAYER@ get_jjBottomFeeder()", asFUNCTION(get_bottomFeeder), asCALL_CDECL); engine->RegisterGlobalFunction("jjPLAYER@ get_jjTokenOwner()", asFUNCTION(get_tokenOwner), asCALL_CDECL);*/ engine->RegisterGlobalFunction("float get_jjLayerXOffset(uint8)", asFUNCTION(get_layerXOffset), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjLayerXOffset(uint8, float)", asFUNCTION(set_layerXOffset), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjLayerYOffset(uint8)", asFUNCTION(get_layerYOffset), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjLayerYOffset(uint8, float)", asFUNCTION(set_layerYOffset), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjLayerWidth(uint8)", asFUNCTION(get_layerWidth), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjLayerWidthReal(uint8)", asFUNCTION(get_layerRealWidth), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjLayerWidthRounded(uint8)", asFUNCTION(get_layerRoundedWidth), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjLayerHeight(uint8)", asFUNCTION(get_layerHeight), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjLayerXSpeed(uint8)", asFUNCTION(get_layerXSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjLayerXSpeed(uint8, float)", asFUNCTION(set_layerXSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjLayerYSpeed(uint8)", asFUNCTION(get_layerYSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjLayerYSpeed(uint8, float)", asFUNCTION(set_layerYSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjLayerXAutoSpeed(uint8)", asFUNCTION(get_layerXAutoSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjLayerXAutoSpeed(uint8, float)", asFUNCTION(set_layerXAutoSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjLayerYAutoSpeed(uint8)", asFUNCTION(get_layerYAutoSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjLayerYAutoSpeed(uint8, float)", asFUNCTION(set_layerYAutoSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjLayerHasTiles(uint8)", asFUNCTION(get_layerHasTiles), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjLayerHasTiles(uint8, bool)", asFUNCTION(set_layerHasTiles), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjLayerTileHeight(uint8)", asFUNCTION(get_layerTileHeight), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjLayerTileHeight(uint8, bool)", asFUNCTION(set_layerTileHeight), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjLayerTileWidth(uint8)", asFUNCTION(get_layerTileWidth), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjLayerTileWidth(uint8, bool)", asFUNCTION(set_layerTileWidth), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjLayerLimitVisibleRegion(uint8)", asFUNCTION(get_layerLimitVisibleRegion), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjLayerLimitVisibleRegion(uint8, bool)", asFUNCTION(set_layerLimitVisibleRegion), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetLayerXSpeed(uint8 layerID, float newspeed, bool newSpeedIsAnAutoSpeed)", asFUNCTION(setLayerXSpeedSeamlessly), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetLayerYSpeed(uint8 layerID, float newspeed, bool newSpeedIsAnAutoSpeed)", asFUNCTION(setLayerYSpeedSeamlessly), asCALL_CDECL); engine->RegisterObjectType("jjPALCOLOR", sizeof(jjPALCOLOR), asOBJ_VALUE | asOBJ_POD | asGetTypeTraits()); engine->RegisterObjectBehaviour("jjPALCOLOR", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(jjPALCOLOR::Create), asCALL_CDECL_OBJLAST); engine->RegisterObjectBehaviour("jjPALCOLOR", asBEHAVE_CONSTRUCT, "void f(uint8 red, uint8 green, uint8 blue)", asFUNCTION(jjPALCOLOR::CreateFromRgb), asCALL_CDECL_OBJLAST); engine->RegisterObjectProperty("jjPALCOLOR", "uint8 red", asOFFSET(jjPALCOLOR, red)); engine->RegisterObjectProperty("jjPALCOLOR", "uint8 green", asOFFSET(jjPALCOLOR, green)); engine->RegisterObjectProperty("jjPALCOLOR", "uint8 blue", asOFFSET(jjPALCOLOR, blue)); engine->RegisterObjectMethod("jjPALCOLOR", "jjPALCOLOR& opAssign(const jjPALCOLOR &in)", asMETHODPR(jjPALCOLOR, operator=, (const jjPALCOLOR&), jjPALCOLOR&), asCALL_THISCALL); engine->RegisterObjectMethod("jjPALCOLOR", "bool opEquals(const jjPALCOLOR &in) const", asMETHOD(jjPALCOLOR, operator==), asCALL_THISCALL); engine->RegisterObjectMethod("jjPALCOLOR", "uint8 getHue() const", asMETHOD(jjPALCOLOR, getHue), asCALL_THISCALL); engine->RegisterObjectMethod("jjPALCOLOR", "uint8 getSat() const", asMETHOD(jjPALCOLOR, getSat), asCALL_THISCALL); engine->RegisterObjectMethod("jjPALCOLOR", "uint8 getLight() const", asMETHOD(jjPALCOLOR, getLight), asCALL_THISCALL); engine->RegisterObjectMethod("jjPALCOLOR", "void setHSL(int hue, uint8 sat, uint8 light)", asMETHOD(jjPALCOLOR, setHSL), asCALL_THISCALL); engine->SetDefaultNamespace("COLOR"); engine->RegisterEnum("Component"); engine->RegisterEnumValue("Component", "RED", 0); engine->RegisterEnumValue("Component", "GREEN", 1); engine->RegisterEnumValue("Component", "BLUE", 2); engine->SetDefaultNamespace(""); engine->RegisterObjectMethod("jjPALCOLOR", "void swizzle(COLOR::Component red, COLOR::Component green, COLOR::Component blue)", asMETHOD(jjPALCOLOR, swizzle), asCALL_THISCALL); engine->RegisterObjectType("jjPAL", sizeof(jjPAL), asOBJ_REF); engine->RegisterObjectBehaviour("jjPAL", asBEHAVE_FACTORY, "jjPAL@ f()", asFUNCTION(jjPAL::Create), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPAL", asBEHAVE_ADDREF, "void f()", asMETHOD(jjPAL, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjPAL", asBEHAVE_RELEASE, "void f()", asMETHOD(jjPAL, Release), asCALL_THISCALL); engine->RegisterGlobalProperty("jjPAL jjPalette", &jjPalette); engine->RegisterGlobalProperty("const jjPAL jjBackupPalette", &jjBackupPalette); engine->RegisterObjectMethod("jjPAL", "jjPAL& opAssign(const jjPAL &in)", asMETHOD(jjPAL, operator=), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "bool opEquals(const jjPAL &in) const", asMETHOD(jjPAL, operator==), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "jjPALCOLOR& get_color(uint8)", asMETHOD(jjPAL, getColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "const jjPALCOLOR& get_color(uint8) const", asMETHOD(jjPAL, getConstColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "jjPALCOLOR& set_color(uint8, const jjPALCOLOR &in)", asMETHOD(jjPAL, setColorEntry), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void reset()", asMETHOD(jjPAL, reset), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void apply() const", asMETHOD(jjPAL, apply), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "bool load(string &in filename)", asMETHOD(jjPAL, load), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void fill(uint8 red, uint8 green, uint8 blue, float opacity)", asMETHOD(jjPAL, fill), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void fill(uint8 red, uint8 green, uint8 blue, uint8 start = 1, uint8 length = 254, float opacity = 1.0)", asMETHOD(jjPAL, fillTint), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void fill(jjPALCOLOR color, float opacity)", asMETHOD(jjPAL, fillFromColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void fill(jjPALCOLOR color, uint8 start = 1, uint8 length = 254, float opacity = 1.0)", asMETHOD(jjPAL, fillTintFromColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void gradient(uint8 red1, uint8 green1, uint8 blue1, uint8 red2, uint8 green2, uint8 blue2, uint8 start = 176, uint8 length = 32, float opacity = 1.0, bool inclusive = false)", asMETHOD(jjPAL, gradient), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void gradient(jjPALCOLOR color1, jjPALCOLOR color2, uint8 start = 176, uint8 length = 32, float opacity = 1.0, bool inclusive = false)", asMETHOD(jjPAL, gradientFromColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "void copyFrom(uint8 start, uint8 length, uint8 start2, const jjPAL &in source, float opacity = 1.0)", asMETHOD(jjPAL, copyFrom), asCALL_THISCALL); engine->RegisterObjectMethod("jjPAL", "uint8 findNearestColor(jjPALCOLOR color) const", asMETHOD(jjPAL, findNearestColor), asCALL_THISCALL); engine->SetDefaultNamespace("SPRITE"); engine->RegisterEnum("Mode"); engine->RegisterEnumValue("Mode", "NORMAL", spriteType_NORMAL); engine->RegisterEnumValue("Mode", "TRANSLUCENT", spriteType_TRANSLUCENT); engine->RegisterEnumValue("Mode", "TINTED", spriteType_TINTED); engine->RegisterEnumValue("Mode", "GEM", spriteType_GEM); engine->RegisterEnumValue("Mode", "INVISIBLE", spriteType_INVISIBLE); engine->RegisterEnumValue("Mode", "SINGLECOLOR", spriteType_SINGLECOLOR); engine->RegisterEnumValue("Mode", "RESIZED", spriteType_RESIZED); engine->RegisterEnumValue("Mode", "NEONGLOW", spriteType_NEONGLOW); engine->RegisterEnumValue("Mode", "FROZEN", spriteType_FROZEN); engine->RegisterEnumValue("Mode", "PLAYER", spriteType_PLAYER); engine->RegisterEnumValue("Mode", "PALSHIFT", spriteType_PALSHIFT); engine->RegisterEnumValue("Mode", "SHADOW", spriteType_SHADOW); engine->RegisterEnumValue("Mode", "SINGLEHUE", spriteType_SINGLEHUE); engine->RegisterEnumValue("Mode", "BRIGHTNESS", spriteType_BRIGHTNESS); engine->RegisterEnumValue("Mode", "TRANSLUCENTCOLOR", spriteType_TRANSLUCENTCOLOR); engine->RegisterEnumValue("Mode", "TRANSLUCENTPLAYER", spriteType_TRANSLUCENTPLAYER); engine->RegisterEnumValue("Mode", "TRANSLUCENTPALSHIFT", spriteType_TRANSLUCENTPALSHIFT); engine->RegisterEnumValue("Mode", "TRANSLUCENTSINGLEHUE", spriteType_TRANSLUCENTSINGLEHUE); engine->RegisterEnumValue("Mode", "ALPHAMAP", spriteType_ALPHAMAP); engine->RegisterEnumValue("Mode", "MENUPLAYER", spriteType_MENUPLAYER); engine->RegisterEnumValue("Mode", "BLEND_NORMAL", spriteType_BLENDNORMAL); engine->RegisterEnumValue("Mode", "BLEND_DARKEN", spriteType_BLENDDARKEN); engine->RegisterEnumValue("Mode", "BLEND_LIGHTEN", spriteType_BLENDLIGHTEN); engine->RegisterEnumValue("Mode", "BLEND_HUE", spriteType_BLENDHUE); engine->RegisterEnumValue("Mode", "BLEND_SATURATION", spriteType_BLENDSATURATION); engine->RegisterEnumValue("Mode", "BLEND_COLOR", spriteType_BLENDCOLOR); engine->RegisterEnumValue("Mode", "BLEND_LUMINANCE", spriteType_BLENDLUMINANCE); engine->RegisterEnumValue("Mode", "BLEND_MULTIPLY", spriteType_BLENDMULTIPLY); engine->RegisterEnumValue("Mode", "BLEND_SCREEN", spriteType_BLENDSCREEN); engine->RegisterEnumValue("Mode", "BLEND_DISSOLVE", spriteType_BLENDDISSOLVE); engine->RegisterEnumValue("Mode", "BLEND_OVERLAY", spriteType_BLENDOVERLAY); engine->RegisterEnumValue("Mode", "BLEND_HARDLIGHT", spriteType_BLENDHARDLIGHT); engine->RegisterEnumValue("Mode", "BLEND_SOFTLIGHT", spriteType_BLENDSOFTLIGHT); engine->RegisterEnumValue("Mode", "BLEND_DIFFERENCE", spriteType_BLENDDIFFERENCE); engine->RegisterEnumValue("Mode", "BLEND_DODGE", spriteType_BLENDDODGE); engine->RegisterEnumValue("Mode", "BLEND_BURN", spriteType_BLENDBURN); engine->RegisterEnumValue("Mode", "BLEND_EXCLUSION", spriteType_BLENDEXCLUSION); engine->RegisterEnumValue("Mode", "TRANSLUCENTTILE", spriteType_TRANSLUCENTTILE); engine->RegisterEnumValue("Mode", "CHROMAKEY", spriteType_CHROMAKEY); engine->RegisterEnumValue("Mode", "MAPPING", spriteType_MAPPING); engine->RegisterEnumValue("Mode", "TRANSLUCENTMAPPING", spriteType_TRANSLUCENTMAPPING); engine->RegisterEnum("Direction"); engine->RegisterEnumValue("Direction", "FLIPNONE", 0x00); engine->RegisterEnumValue("Direction", "FLIPH", 0xFF - 0x100); engine->RegisterEnumValue("Direction", "FLIPV", 0x40); engine->RegisterEnumValue("Direction", "FLIPHV", 0xBF - 0x100); engine->SetDefaultNamespace("TILE"); engine->RegisterEnum("Quadrant"); engine->RegisterEnumValue("Quadrant", "TOPLEFT", 0); engine->RegisterEnumValue("Quadrant", "TOPRIGHT", 1); engine->RegisterEnumValue("Quadrant", "BOTTOMLEFT", 2); engine->RegisterEnumValue("Quadrant", "BOTTOMRIGHT", 3); engine->RegisterEnumValue("Quadrant", "ALLQUADRANTS", 4); engine->RegisterEnum("Flags"); engine->RegisterEnumValue("Flags", "RAWRANGE", FLAG_HFLIPPED_TILE - 1); engine->RegisterEnumValue("Flags", "HFLIPPED", FLAG_HFLIPPED_TILE); engine->RegisterEnumValue("Flags", "VFLIPPED", FLAG_VFLIPPED_TILE); engine->RegisterEnumValue("Flags", "ANIMATED", FLAG_ANIMATED_TILE); engine->SetDefaultNamespace(""); // TODO /*engine->RegisterObjectMethod("jjPLAYER", "SPRITE::Mode get_spriteMode() const", asFUNCTION(getSpriteMode), asCALL_CDECL_OBJFIRST); engine->RegisterObjectMethod("jjPLAYER", "SPRITE::Mode set_spriteMode(SPRITE::Mode)", asFUNCTION(setSpriteMode), asCALL_CDECL_OBJFIRST); engine->RegisterObjectMethod("jjPLAYER", "uint8 get_spriteParam() const", asFUNCTION(getSpriteParam), asCALL_CDECL_OBJFIRST); engine->RegisterObjectMethod("jjPLAYER", "uint8 set_spriteParam(uint8)", asFUNCTION(setSpriteParam), asCALL_CDECL_OBJFIRST); engine->RegisterGlobalFunction("void jjSpriteModeSetMapping(uint8 index, const array &in indexMapping, const jjPAL &in rgbMapping)", asFUNCTION(setSpriteModeMapping), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSpriteModeSetMapping(uint8 index, const array &in indexMapping)", asFUNCTION(setSpriteModeMappingDynamic), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjSpriteModeIsMappingUsed(uint8 index)", asFUNCTION(isSpriteModeMappingUsed), asCALL_CDECL); engine->RegisterGlobalFunction("int jjSpriteModeFirstFreeMapping()", asFUNCTION(firstFreeSpriteModeMapping), asCALL_CDECL); engine->RegisterGlobalFunction("array@ jjSpriteModeGetIndexMapping(uint8 index)", asFUNCTION(getSpriteModeIndexMapping), asCALL_CDECL); engine->RegisterGlobalFunction("jjPAL@ jjSpriteModeGetColorMapping(uint8 index)", asFUNCTION(getSpriteModeColorMapping), asCALL_CDECL);*/ engine->RegisterObjectType("jjTEXTAPPEARANCE", sizeof(jjTEXTAPPEARANCE), asOBJ_VALUE | asOBJ_POD); engine->RegisterObjectBehaviour("jjTEXTAPPEARANCE", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(jjTEXTAPPEARANCE::constructor), asCALL_CDECL_OBJLAST); engine->RegisterObjectBehaviour("jjTEXTAPPEARANCE", asBEHAVE_CONSTRUCT, "void f(STRING::Mode mode)", asFUNCTION(jjTEXTAPPEARANCE::constructorMode), asCALL_CDECL_OBJLAST); engine->RegisterObjectMethod("jjTEXTAPPEARANCE", "jjTEXTAPPEARANCE& opAssign(STRING::Mode)", asMETHODPR(jjTEXTAPPEARANCE, operator=, (uint32_t), jjTEXTAPPEARANCE&), asCALL_THISCALL); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "int xAmp", asOFFSET(jjTEXTAPPEARANCE, xAmp)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "int yAmp", asOFFSET(jjTEXTAPPEARANCE, yAmp)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "int spacing", asOFFSET(jjTEXTAPPEARANCE, spacing)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "bool monospace", asOFFSET(jjTEXTAPPEARANCE, monospace)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "bool skipInitialHash", asOFFSET(jjTEXTAPPEARANCE, skipInitialHash)); engine->SetDefaultNamespace("STRING"); engine->RegisterEnum("SignTreatment"); engine->RegisterEnumValue("SignTreatment", "HIDESIGN", jjTEXTAPPEARANCE::ch_HIDE); engine->RegisterEnumValue("SignTreatment", "DISPLAYSIGN", jjTEXTAPPEARANCE::ch_DISPLAY); engine->RegisterEnumValue("SignTreatment", "SPECIALSIGN", jjTEXTAPPEARANCE::ch_SPECIAL); engine->RegisterEnum("Alignment"); engine->RegisterEnumValue("Alignment", "DEFAULT", jjTEXTAPPEARANCE::align_DEFAULT); engine->RegisterEnumValue("Alignment", "LEFT", jjTEXTAPPEARANCE::align_LEFT); engine->RegisterEnumValue("Alignment", "CENTER", jjTEXTAPPEARANCE::align_CENTER); engine->RegisterEnumValue("Alignment", "RIGHT", jjTEXTAPPEARANCE::align_RIGHT); engine->SetDefaultNamespace(""); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment at", asOFFSET(jjTEXTAPPEARANCE, at)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment caret", asOFFSET(jjTEXTAPPEARANCE, caret)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment hash", asOFFSET(jjTEXTAPPEARANCE, hash)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment newline", asOFFSET(jjTEXTAPPEARANCE, newline)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment pipe", asOFFSET(jjTEXTAPPEARANCE, pipe)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment section", asOFFSET(jjTEXTAPPEARANCE, section)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::SignTreatment tilde", asOFFSET(jjTEXTAPPEARANCE, tilde)); engine->RegisterObjectProperty("jjTEXTAPPEARANCE", "STRING::Alignment align", asOFFSET(jjTEXTAPPEARANCE, align)); engine->RegisterObjectType("jjCANVAS", sizeof(jjCANVAS), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterObjectMethod("jjCANVAS", "void drawPixel(int xPixel, int yPixel, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawPixel), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawRectangle(int xPixel, int yPixel, int width, int height, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawRectangle), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawSprite(int xPixel, int yPixel, int setID, uint8 animation, uint8 frame, int8 direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawSprite), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, int8 direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawCurFrameSprite), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawResizedSprite(int xPixel, int yPixel, int setID, uint8 animation, uint8 frame, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawResizedSprite), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawResizedSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawResizedCurFrameSprite), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawRotatedSprite(int xPixel, int yPixel, int setID, uint8 animation, uint8 frame, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawTransformedSprite), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawRotatedSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawTransformedCurFrameSprite), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawSwingingVineSpriteFromCurFrame(int xPixel, int yPixel, uint sprite, int length, int curvature, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawSwingingVine), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawTile(int xPixel, int yPixel, uint16 tile, TILE::Quadrant tileQuadrant = TILE::ALLQUADRANTS)", asMETHOD(jjCANVAS, ExternalDrawTile), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawString(int xPixel, int yPixel, const ::string &in text, STRING::Size size = STRING::SMALL, STRING::Mode mode = STRING::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, DrawTextBasicSize), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawString(int xPixel, int yPixel, const ::string &in text, STRING::Size size, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0)", asMETHOD(jjCANVAS, DrawTextExtSize), asCALL_THISCALL); engine->RegisterGlobalFunction("void jjDrawPixel(float xPixel, float yPixel, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawPixel), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawRectangle(float xPixel, float yPixel, int width, int height, uint8 color, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawRectangle), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawSprite(float xPixel, float yPixel, int setID, uint8 animation, uint8 frame, int8 direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawSprite), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int8 direction = 0, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawSpriteFromCurFrame), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawResizedSprite(float xPixel, float yPixel, int setID, uint8 animation, uint8 frame, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawResizedSprite), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawResizedSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, float xScale, float yScale, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawResizedSpriteFromCurFrame), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawRotatedSprite(float xPixel, float yPixel, int setID, uint8 animation, uint8 frame, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawRotatedSprite), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawRotatedSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int angle, float xScale = 1, float yScale = 1, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawRotatedSpriteFromCurFrame), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawSwingingVineSpriteFromCurFrame(float xPixel, float yPixel, uint sprite, int length, int curvature, SPRITE::Mode mode = SPRITE::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawSwingingVineSpriteFromCurFrame), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawTile(float xPixel, float yPixel, uint16 tile, TILE::Quadrant tileQuadrant = TILE::ALLQUADRANTS, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawTile), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawString(float xPixel, float yPixel, const ::string &in text, STRING::Size size = STRING::SMALL, STRING::Mode mode = STRING::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawString), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawString(float xPixel, float yPixel, const ::string &in text, STRING::Size size, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjDrawStringEx), asCALL_CDECL); engine->RegisterGlobalFunction("int jjGetStringWidth(const ::string &in text, STRING::Size size, const jjTEXTAPPEARANCE &in style)", asFUNCTION(jjGetStringWidth), asCALL_CDECL); engine->SetDefaultNamespace("TEXTURE"); engine->RegisterEnum("Texture"); engine->RegisterEnumValue("Texture", "FROMTILES", 0); engine->RegisterEnumValue("Texture", "LAYER8", 0); engine->RegisterEnumValue("Texture", "NORMAL", 1); engine->RegisterEnumValue("Texture", "PSYCH", 2); engine->RegisterEnumValue("Texture", "MEDIVO", 3); engine->RegisterEnumValue("Texture", "DIAMONDUSBETA", 4); engine->RegisterEnumValue("Texture", "WISETYNESS", 5); engine->RegisterEnumValue("Texture", "BLADE", 6); engine->RegisterEnumValue("Texture", "MEZ02", 7); engine->RegisterEnumValue("Texture", "WINDSTORMFORTRESS", 8); engine->RegisterEnumValue("Texture", "RANEFORUSV", 9); engine->RegisterEnumValue("Texture", "CORRUPTEDSANCTUARY", 10); engine->RegisterEnumValue("Texture", "XARGON", 11); engine->RegisterEnumValue("Texture", "ICTUBELECTRIC", 12); engine->RegisterEnumValue("Texture", "WTF", 13); engine->RegisterEnumValue("Texture", "MUCKAMOKNIGHT", 14); engine->RegisterEnumValue("Texture", "DESOLATION", 15); engine->RegisterEnumValue("Texture", "CUSTOM", ~0); engine->RegisterEnum("Style"); engine->RegisterEnumValue("Style", "WARPHORIZON", tbgModeWARPHORIZON); engine->RegisterEnumValue("Style", "TUNNEL", tbgModeTUNNEL); engine->RegisterEnumValue("Style", "MENU", tbgModeMENU); engine->RegisterEnumValue("Style", "TILEMENU", tbgModeTILEMENU); engine->RegisterEnumValue("Style", "WAVE", tbgModeWAVE); engine->RegisterEnumValue("Style", "CYLINDER", tbgModeCYLINDER); engine->RegisterEnumValue("Style", "REFLECTION", tbgModeREFLECTION); engine->SetDefaultNamespace(""); engine->RegisterGlobalFunction("void jjSetDarknessColor(jjPALCOLOR color = jjPALCOLOR())", asFUNCTION(jjSetDarknessColor), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetFadeColors(uint8 red, uint8 green, uint8 blue)", asFUNCTION(jjSetFadeColors), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetFadeColors(uint8 paletteColorID = 207)", asFUNCTION(jjSetFadeColorsFromPalette), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetFadeColors(jjPALCOLOR color)", asFUNCTION(jjSetFadeColorsFromPalcolor), asCALL_CDECL); engine->RegisterGlobalFunction("jjPALCOLOR jjGetFadeColors()", asFUNCTION(jjGetFadeColors), asCALL_CDECL); engine->RegisterGlobalFunction("void jjUpdateTexturedBG()", asFUNCTION(jjUpdateTexturedBG), asCALL_CDECL); // Deprecated engine->RegisterGlobalFunction("TEXTURE::Texture get_jjTexturedBGTexture()", asFUNCTION(get_jjTexturedBGTexture), asCALL_CDECL); engine->RegisterGlobalFunction("TEXTURE::Texture set_jjTexturedBGTexture(TEXTURE::Texture)", asFUNCTION(set_jjTexturedBGTexture), asCALL_CDECL); engine->RegisterGlobalFunction("TEXTURE::Style get_jjTexturedBGStyle()", asFUNCTION(get_jjTexturedBGStyle), asCALL_CDECL); engine->RegisterGlobalFunction("TEXTURE::Style set_jjTexturedBGStyle(TEXTURE::Style)", asFUNCTION(set_jjTexturedBGStyle), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjTexturedBGUsed()", asFUNCTION(get_jjTexturedBGUsed), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjTexturedBGUsed(bool)", asFUNCTION(set_jjTexturedBGUsed), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjTexturedBGStars()", asFUNCTION(get_jjTexturedBGStars), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjTexturedBGStars(bool)", asFUNCTION(set_jjTexturedBGStars), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjTexturedBGFadePositionX()", asFUNCTION(get_jjTexturedBGFadePositionX), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjTexturedBGFadePositionX(float value)", asFUNCTION(set_jjTexturedBGFadePositionX), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjTexturedBGFadePositionY()", asFUNCTION(get_jjTexturedBGFadePositionY), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjTexturedBGFadePositionY(float value)", asFUNCTION(set_jjTexturedBGFadePositionY), asCALL_CDECL); engine->SetDefaultNamespace("SNOWING"); engine->RegisterEnum("Type"); engine->RegisterEnumValue("Type", "SNOW", 0); engine->RegisterEnumValue("Type", "FLOWER", 1); engine->RegisterEnumValue("Type", "RAIN", 2); engine->RegisterEnumValue("Type", "LEAF", 3); engine->SetDefaultNamespace(""); engine->RegisterGlobalProperty("bool jjIsSnowing", &snowing); engine->RegisterGlobalProperty("bool jjIsSnowingOutdoorsOnly", &snowingOutdoors); engine->RegisterGlobalProperty("uint8 jjSnowingIntensity", &snowingIntensity); engine->RegisterGlobalProperty("SNOWING::Type jjSnowingType", &snowingType); engine->RegisterGlobalFunction("bool get_jjTriggers(uint8)", asFUNCTION(get_jjTriggers), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjTriggers(uint8, bool)", asFUNCTION(set_jjTriggers), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjSwitchTrigger(uint8 id)", asFUNCTION(jjSwitchTrigger), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjEnabledASFunctions(uint8)", asFUNCTION(isNumberedASFunctionEnabled), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjEnabledASFunctions(uint8, bool)", asFUNCTION(setNumberedASFunctionEnabled), asCALL_CDECL); engine->RegisterGlobalFunction("void jjEnableEachASFunction()", asFUNCTION(reenableAllNumberedASFunctions), asCALL_CDECL); engine->SetDefaultNamespace("WATERLIGHT"); engine->RegisterEnum("wl"); engine->RegisterEnumValue("wl", "NONE", 0); engine->RegisterEnumValue("wl", "GLOBAL", 1); engine->RegisterEnumValue("wl", "LAGUNICUS", 3); engine->SetDefaultNamespace("WATERINTERACTION"); engine->RegisterEnum("WaterInteraction"); engine->RegisterEnumValue("WaterInteraction", "POSITIONBASED", waterInteraction_POSITIONBASED); engine->RegisterEnumValue("WaterInteraction", "SWIM", waterInteraction_SWIM); engine->RegisterEnumValue("WaterInteraction", "LOWGRAVITY", waterInteraction_LOWGRAVITY); engine->SetDefaultNamespace(""); engine->RegisterGlobalProperty("WATERLIGHT::wl jjWaterLighting", &waterLightMode); engine->RegisterGlobalProperty("WATERINTERACTION::WaterInteraction jjWaterInteraction", &waterInteraction); engine->RegisterGlobalFunction("float get_jjWaterLevel()", asFUNCTION(getWaterLevel), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjWaterTarget()", asFUNCTION(getWaterLevel2), asCALL_CDECL); engine->RegisterGlobalFunction("float jjSetWaterLevel(float yPixel, bool instant)", asFUNCTION(setWaterLevel), asCALL_CDECL); engine->RegisterGlobalFunction("float get_jjWaterChangeSpeed()", asFUNCTION(get_waterChangeSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("float set_jjWaterChangeSpeed(float)", asFUNCTION(set_waterChangeSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjWaterLayer()", asFUNCTION(get_waterLayer), asCALL_CDECL); engine->RegisterGlobalFunction("int set_jjWaterLayer(int)", asFUNCTION(set_waterLayer), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetWaterGradient(uint8 red1, uint8 green1, uint8 blue1, uint8 red2, uint8 green2, uint8 blue2)", asFUNCTION(setWaterGradient), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetWaterGradient(jjPALCOLOR color1, jjPALCOLOR color2)", asFUNCTION(setWaterGradientFromColors), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetWaterGradient()", asFUNCTION(setWaterGradientToTBG), asCALL_CDECL); engine->RegisterGlobalFunction("void jjResetWaterGradient()", asFUNCTION(resetWaterGradient), asCALL_CDECL); engine->RegisterGlobalFunction("void jjTriggerRock(uint8 id)", asFUNCTION(triggerRock), asCALL_CDECL); engine->RegisterGlobalFunction("void jjNxt(const string &in filename, bool warp = false, bool fast = false)", asFUNCTION(cycleTo), asCALL_CDECL); engine->RegisterGlobalFunction("void jjNxt(bool warp = false, bool fast = false)", asFUNCTION(jjNxt), asCALL_CDECL); engine->RegisterGlobalFunction("bool get_jjEnabledTeams(uint8)", asFUNCTION(getEnabledTeam), asCALL_CDECL); engine->RegisterGlobalProperty("uint8 jjKeyChat", &ChatKey); engine->RegisterGlobalFunction("bool get_jjKey(uint8)", asFUNCTION(getKeyDown), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjMouseX()", asFUNCTION(getCursorX), asCALL_CDECL); engine->RegisterGlobalFunction("int get_jjMouseY()", asFUNCTION(getCursorY), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjMusicLoad(string &in filename, bool forceReload = false, bool temporary = false)", asFUNCTION(jjMusicLoad), asCALL_CDECL); engine->RegisterGlobalFunction("void jjMusicStop()", asFUNCTION(jjMusicStop), asCALL_CDECL); engine->RegisterGlobalFunction("void jjMusicPlay()", asFUNCTION(jjMusicPlay), asCALL_CDECL); engine->RegisterGlobalFunction("void jjMusicPause()", asFUNCTION(jjMusicPause), asCALL_CDECL); engine->RegisterGlobalFunction("void jjMusicResume()", asFUNCTION(jjMusicResume), asCALL_CDECL); engine->SetDefaultNamespace("SOUND"); engine->RegisterEnum("Sample"); engine->SetDefaultNamespace(""); engine->RegisterGlobalFunction("void jjSample(float xPixel, float yPixel, SOUND::Sample sample, int volume = 63, int frequency = 0)", asFUNCTION(playSample), asCALL_CDECL); engine->RegisterGlobalFunction("int jjSampleLooped(float xPixel, float yPixel, SOUND::Sample sample, int channel, int volume = 63, int frequency = 0)", asFUNCTION(playLoopedSample), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSamplePriority(SOUND::Sample sample)", asFUNCTION(playPrioritySample), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjSampleIsLoaded(SOUND::Sample sample)", asFUNCTION(isSampleLoaded), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjSampleLoad(SOUND::Sample sample, string& in filename)", asFUNCTION(loadSample), asCALL_CDECL); engine->RegisterGlobalProperty("const bool jjSoundEnabled", &soundEnabled); engine->RegisterGlobalProperty("const bool jjSoundFXActive", &soundFXActive); engine->RegisterGlobalProperty("const bool jjMusicActive", &musicActive); engine->RegisterGlobalProperty("const int jjSoundFXVolume", &soundFXVolume); engine->RegisterGlobalProperty("const int jjMusicVolume", &musicVolume); engine->RegisterGlobalProperty("int jjEcho", &levelEcho); engine->RegisterGlobalProperty("bool jjWarpsTransmuteCoins", &warpsTransmuteCoins); engine->RegisterGlobalProperty("bool jjDelayGeneratedCrateOrigins", &delayGeneratedCrateOrigins); engine->RegisterGlobalFunction("bool get_jjUseLayer8Speeds()", asFUNCTION(getUseLayer8Speeds), asCALL_CDECL); engine->RegisterGlobalFunction("bool set_jjUseLayer8Speeds(bool)", asFUNCTION(setUseLayer8Speeds), asCALL_CDECL); engine->RegisterGlobalProperty("bool jjSugarRushAllowed", &g_levelHasFood); engine->RegisterGlobalProperty("bool jjSugarRushesAllowed", &g_levelHasFood); engine->RegisterObjectType("jjWEAPON", sizeof(jjWEAPON), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterGlobalFunction("jjWEAPON@ get_jjWeapons(int)", asFUNCTION(get_jjWEAPON), asCALL_CDECL); engine->RegisterObjectProperty("jjWEAPON", "bool infinite", asOFFSET(jjWEAPON, infinite)); engine->RegisterObjectProperty("jjWEAPON", "bool replenishes", asOFFSET(jjWEAPON, replenishes)); engine->RegisterObjectProperty("jjWEAPON", "bool replacedByShield", asOFFSET(jjWEAPON, replacedByShield)); engine->RegisterObjectProperty("jjWEAPON", "bool replacedByBubbles", asOFFSET(jjWEAPON, replacedByBubbles)); engine->RegisterObjectProperty("jjWEAPON", "bool comesFromGunCrates", asOFFSET(jjWEAPON, comesFromGunCrates)); engine->RegisterObjectProperty("jjWEAPON", "bool gradualAim", asOFFSET(jjWEAPON, gradualAim)); engine->RegisterObjectProperty("jjWEAPON", "int multiplier", asOFFSET(jjWEAPON, multiplier)); engine->RegisterObjectProperty("jjWEAPON", "int maximum", asOFFSET(jjWEAPON, maximum)); engine->RegisterObjectProperty("jjWEAPON", "int gemsLost", asOFFSET(jjWEAPON, gemsLost)); engine->RegisterObjectProperty("jjWEAPON", "int gemsLostPowerup", asOFFSET(jjWEAPON, gemsLostPowerup)); engine->RegisterObjectProperty("jjWEAPON", "int8 style", asOFFSET(jjWEAPON, style)); engine->RegisterObjectProperty("jjWEAPON", "SPREAD::Spread spread", asOFFSET(jjWEAPON, spread)); engine->RegisterObjectProperty("jjWEAPON", "bool defaultSample", asOFFSET(jjWEAPON, defaultSample)); engine->RegisterObjectProperty("jjWEAPON", "bool allowed", asOFFSET(jjWEAPON, allowed)); engine->RegisterObjectProperty("jjWEAPON", "bool allowedPowerup", asOFFSET(jjWEAPON, allowedPowerup)); engine->RegisterObjectProperty("jjWEAPON", "bool comesFromBirds", asOFFSET(jjWEAPON, comesFromBirds)); engine->RegisterObjectProperty("jjWEAPON", "bool comesFromBirdsPowerup", asOFFSET(jjWEAPON, comesFromBirdsPowerup)); engine->SetDefaultNamespace("AIR"); engine->RegisterEnum("Jump"); engine->RegisterEnumValue("Jump", "NONE", airjumpNONE); engine->RegisterEnumValue("Jump", "HELICOPTER", airjumpHELICOPTER); engine->RegisterEnumValue("Jump", "DOUBLEJUMP", airjumpSPAZ); engine->SetDefaultNamespace("GROUND"); engine->RegisterEnum("Jump"); engine->RegisterEnumValue("Jump", "CROUCH", groundjumpNONE); engine->RegisterEnumValue("Jump", "JUMP", groundjumpREGULARJUMP); engine->RegisterEnumValue("Jump", "JAZZ", groundjumpJAZZ); engine->RegisterEnumValue("Jump", "SPAZ", groundjumpSPAZ); engine->RegisterEnumValue("Jump", "LORI", groundjumpLORI); engine->SetDefaultNamespace(""); engine->RegisterObjectType("jjCHARACTER", sizeof(jjCHARACTER), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterGlobalFunction("jjCHARACTER@ get_jjCharacters(CHAR::Char)", asFUNCTION(get_jjCHARACTER), asCALL_CDECL); engine->RegisterObjectProperty("jjCHARACTER", "AIR::Jump airJump", asOFFSET(jjCHARACTER, airJump)); engine->RegisterObjectProperty("jjCHARACTER", "GROUND::Jump groundJump", asOFFSET(jjCHARACTER, groundJump)); engine->RegisterObjectProperty("jjCHARACTER", "int doubleJumpCountMax", asOFFSET(jjCHARACTER, doubleJumpCountMax)); engine->RegisterObjectProperty("jjCHARACTER", "float doubleJumpXSpeed", asOFFSET(jjCHARACTER, doubleJumpXSpeed)); engine->RegisterObjectProperty("jjCHARACTER", "float doubleJumpYSpeed", asOFFSET(jjCHARACTER, doubleJumpYSpeed)); engine->RegisterObjectProperty("jjCHARACTER", "int helicopterDurationMax", asOFFSET(jjCHARACTER, helicopterDurationMax)); engine->RegisterObjectProperty("jjCHARACTER", "float helicopterXSpeed", asOFFSET(jjCHARACTER, helicopterXSpeed)); engine->RegisterObjectProperty("jjCHARACTER", "float helicopterYSpeed", asOFFSET(jjCHARACTER, helicopterYSpeed)); engine->RegisterObjectProperty("jjCHARACTER", "bool canHurt", asOFFSET(jjCHARACTER, canHurt)); engine->RegisterObjectProperty("jjCHARACTER", "bool canRun", asOFFSET(jjCHARACTER, canRun)); engine->RegisterObjectProperty("jjCHARACTER", "bool morphBoxCycle", asOFFSET(jjCHARACTER, morphBoxCycle)); engine->SetDefaultNamespace("CREATOR"); engine->RegisterEnum("Type"); engine->RegisterEnumValue("Type", "OBJECT", 0); engine->RegisterEnumValue("Type", "LEVEL", 1); engine->RegisterEnumValue("Type", "PLAYER", 2); engine->SetDefaultNamespace("AREA"); engine->RegisterEnum("Area"); engine->SetDefaultNamespace("OBJECT"); engine->RegisterEnum("Object"); engine->SetDefaultNamespace("ANIM"); engine->RegisterEnum("Set"); engine->SetDefaultNamespace(""); engine->RegisterGlobalFunction("int jjEventGet(uint16 xTile, uint16 yTile)", asFUNCTION(GetEvent), asCALL_CDECL); engine->RegisterGlobalFunction("int jjParameterGet(uint16 xTile, uint16 yTile, int offset, int length)", asFUNCTION(GetEventParamWrapper), asCALL_CDECL); engine->RegisterGlobalFunction("void jjEventSet(uint16 xTile, uint16 yTile, uint8 newEventID)", asFUNCTION(SetEventByte), asCALL_CDECL); engine->RegisterGlobalFunction("void jjParameterSet(uint16 xTile, uint16 yTile, int8 offset, int8 length, int newValue)", asFUNCTION(SetEventParam), asCALL_CDECL); engine->RegisterGlobalFunction("uint8 get_jjTileType(uint16)", asFUNCTION(GetTileType), asCALL_CDECL); engine->RegisterGlobalFunction("uint8 set_jjTileType(uint16,uint8)", asFUNCTION(SetTileType), asCALL_CDECL); engine->SetDefaultNamespace("LIGHT"); engine->RegisterEnum("Enforce"); engine->RegisterEnumValue("Enforce", "OPTIONAL", ambientLighting_OPTIONAL); engine->RegisterEnumValue("Enforce", "BASIC", ambientLighting_BASIC); engine->RegisterEnumValue("Enforce", "COMPLETE", ambientLighting_COMPLETE); engine->SetDefaultNamespace(""); engine->RegisterGlobalProperty("LIGHT::Enforce jjEnforceLighting", &enforceAmbientLighting); engine->SetDefaultNamespace("STATE"); engine->RegisterEnum("State"); engine->SetDefaultNamespace("BEHAVIOR"); engine->RegisterEnum("Behavior"); engine->SetDefaultNamespace("LIGHT"); engine->RegisterEnum("Type"); engine->RegisterEnumValue("Type", "NONE", 0); engine->RegisterEnumValue("Type", "NORMAL", 3); engine->RegisterEnumValue("Type", "POINT", 1); engine->RegisterEnumValue("Type", "POINT2", 2); engine->RegisterEnumValue("Type", "FLICKER", 4); engine->RegisterEnumValue("Type", "BRIGHT", 5); engine->RegisterEnumValue("Type", "LASERBEAM", 6); engine->RegisterEnumValue("Type", "LASER", 7); engine->RegisterEnumValue("Type", "RING", 8); engine->RegisterEnumValue("Type", "RING2", 9); engine->RegisterEnumValue("Type", "PLAYER", 10); engine->SetDefaultNamespace("HANDLING"); engine->RegisterEnum("Bullet"); engine->RegisterEnumValue("Bullet", "HURTBYBULLET", 0); engine->RegisterEnumValue("Bullet", "IGNOREBULLET", 1); engine->RegisterEnumValue("Bullet", "DESTROYBULLET", 2); engine->RegisterEnumValue("Bullet", "DETECTBULLET", 3); engine->RegisterEnum("Player"); engine->RegisterEnumValue("Player", "ENEMY", 0); engine->RegisterEnumValue("Player", "PLAYERBULLET", 1); engine->RegisterEnumValue("Player", "ENEMYBULLET", 2); engine->RegisterEnumValue("Player", "PARTICLE", 3); engine->RegisterEnumValue("Player", "EXPLOSION", 4); engine->RegisterEnumValue("Player", "PICKUP", 5); engine->RegisterEnumValue("Player", "DELAYEDPICKUP", 6); engine->RegisterEnumValue("Player", "HURT", 7); engine->RegisterEnumValue("Player", "SPECIAL", 8); engine->RegisterEnumValue("Player", "DYING", 9); engine->RegisterEnumValue("Player", "SPECIALDONE", 10); engine->RegisterEnumValue("Player", "SELFCOLLISION", 11); engine->SetDefaultNamespace(""); // TODO engine->RegisterObjectType("jjOBJ", sizeof(jjOBJ), asOBJ_REF /*| asOBJ_NOCOUNT*/); engine->RegisterObjectBehaviour("jjOBJ", asBEHAVE_ADDREF, "void f()", asMETHOD(jjOBJ, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjOBJ", asBEHAVE_RELEASE, "void f()", asMETHOD(jjOBJ, Release), asCALL_THISCALL); engine->RegisterGlobalFunction("jjOBJ @get_jjObjects(int)", asFUNCTION(get_jjObjects), asCALL_CDECL); engine->RegisterGlobalFunction("jjOBJ @get_jjObjectPresets(uint8)", asFUNCTION(get_jjObjectPresets), asCALL_CDECL); engine->RegisterGlobalProperty("const int jjObjectCount", &jjObjectCount); engine->RegisterGlobalProperty("const int jjObjectMax", &jjObjectMax); engine->RegisterObjectMethod("jjOBJ", "bool get_isActive() const", asMETHOD(jjOBJ, get_isActive), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "LIGHT::Type get_lightType() const", asMETHOD(jjOBJ, get_lightType), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "LIGHT::Type set_lightType(LIGHT::Type)", asMETHOD(jjOBJ, set_lightType), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool doesCollide(const jjOBJ@ object, bool always = false) const", asMETHOD(jjPLAYER, doesCollide), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "int getObjectHitForce(const jjOBJ@ target = null) const", asMETHOD(jjPLAYER, getObjectHitForce), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool objectHit(jjOBJ@ target, int force, HANDLING::Player playerHandling)", asMETHOD(jjPLAYER, objectHit), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void objectHit(jjOBJ@ target, HANDLING::Player playerHandling)", asMETHOD(jjOBJ, objectHit), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void blast(int maxDistance, bool blastObjects)", asMETHOD(jjOBJ, blast), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYER", "bool isEnemy(const jjPLAYER &in victim) const", asMETHOD(jjPLAYER, isEnemy), asCALL_THISCALL); engine->RegisterObjectProperty("jjPLAYER", "const ANIM::Set setID", asOFFSET(jjPLAYER, charCurr)); engine->RegisterObjectProperty("jjPLAYER", "const uint16 curAnim", asOFFSET(jjPLAYER, curAnim)); engine->RegisterObjectProperty("jjPLAYER", "const uint curFrame", asOFFSET(jjPLAYER, curFrame)); engine->RegisterObjectProperty("jjPLAYER", "const uint8 frameID", asOFFSET(jjPLAYER, frameID)); engine->RegisterFuncdef("void jjVOIDFUNCOBJ(jjOBJ@)"); engine->RegisterObjectType("jjBEHAVIOR", sizeof(jjBEHAVIOR), asOBJ_VALUE | asOBJ_APP_CLASS_CDA); engine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(jjBEHAVIOR::Create), asCALL_CDECL_OBJLAST); engine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_CONSTRUCT, "void f(const BEHAVIOR::Behavior &in behavior)", asFUNCTION(jjBEHAVIOR::CreateFromBehavior), asCALL_CDECL_OBJLAST); engine->RegisterObjectBehaviour("jjBEHAVIOR", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(jjBEHAVIOR::Destroy), asCALL_CDECL_OBJLAST); engine->RegisterInterface("jjBEHAVIORINTERFACE"); engine->RegisterInterfaceMethod("jjBEHAVIORINTERFACE", "void onBehave(jjOBJ@ obj)"); engine->RegisterObjectMethod("jjBEHAVIOR", "jjBEHAVIOR& opAssign(const jjBEHAVIOR &in)", asMETHODPR(jjBEHAVIOR, operator=, (const jjBEHAVIOR&), jjBEHAVIOR&), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "jjBEHAVIOR& opAssign(BEHAVIOR::Behavior)", asMETHODPR(jjBEHAVIOR, operator=, (uint32_t), jjBEHAVIOR&), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "jjBEHAVIOR& opAssign(jjVOIDFUNCOBJ@)", asMETHODPR(jjBEHAVIOR, operator=, (asIScriptFunction*), jjBEHAVIOR&), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "jjBEHAVIOR& opAssign(jjBEHAVIORINTERFACE@)", asMETHODPR(jjBEHAVIOR, operator=, (asIScriptObject*), jjBEHAVIOR&), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "bool opEquals(const jjBEHAVIOR &in) const", asMETHODPR(jjBEHAVIOR, operator==, (const jjBEHAVIOR&) const, bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "bool opEquals(BEHAVIOR::Behavior) const", asMETHODPR(jjBEHAVIOR, operator==, (uint32_t) const, bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "bool opEquals(const jjVOIDFUNCOBJ@) const", asMETHODPR(jjBEHAVIOR, operator==, (const asIScriptFunction*) const, bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "BEHAVIOR::Behavior opConv() const", asMETHOD(jjBEHAVIOR, operator uint32_t), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "jjVOIDFUNCOBJ@ opCast() const", asMETHOD(jjBEHAVIOR, operator asIScriptFunction*), asCALL_THISCALL); engine->RegisterObjectMethod("jjBEHAVIOR", "jjBEHAVIORINTERFACE@ opCast() const", asMETHOD(jjBEHAVIOR, operator asIScriptObject*), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "jjBEHAVIOR behavior", asOFFSET(jjOBJ, behavior)); engine->RegisterObjectMethod("jjOBJ", "void behave(BEHAVIOR::Behavior behavior = BEHAVIOR::DEFAULT, bool draw = true)", asMETHOD(jjOBJ, behave1), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void behave(jjBEHAVIOR behavior, bool draw = true)", asMETHOD(jjOBJ, behave2), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void behave(jjVOIDFUNCOBJ@ behavior, bool draw = true)", asMETHOD(jjOBJ, behave3), asCALL_THISCALL); engine->RegisterGlobalFunction("int jjAddObject(uint8 eventID, float xPixel, float yPixel, uint16 creatorID = 0, CREATOR::Type creatorType = CREATOR::OBJECT, BEHAVIOR::Behavior behavior = BEHAVIOR::DEFAULT)", asFUNCTION(jjOBJ::jjAddObject), asCALL_CDECL); engine->RegisterGlobalFunction("int jjAddObject(uint8 eventID, float xPixel, float xPixel, uint16 creatorID, CREATOR::Type creatorType, jjVOIDFUNCOBJ@ behavior)", asFUNCTION(jjOBJ::jjAddObjectEx), asCALL_CDECL); engine->RegisterObjectProperty("jjOBJ", "float xOrg", asOFFSET(jjOBJ, xOrg)); engine->RegisterObjectProperty("jjOBJ", "float yOrg", asOFFSET(jjOBJ, yOrg)); engine->RegisterObjectProperty("jjOBJ", "float xPos", asOFFSET(jjOBJ, xPos)); engine->RegisterObjectProperty("jjOBJ", "float yPos", asOFFSET(jjOBJ, yPos)); engine->RegisterObjectProperty("jjOBJ", "float xSpeed", asOFFSET(jjOBJ, xSpeed)); engine->RegisterObjectProperty("jjOBJ", "float ySpeed", asOFFSET(jjOBJ, ySpeed)); engine->RegisterObjectProperty("jjOBJ", "float xAcc", asOFFSET(jjOBJ, xAcc)); engine->RegisterObjectProperty("jjOBJ", "float yAcc", asOFFSET(jjOBJ, yAcc)); engine->RegisterObjectProperty("jjOBJ", "int counter", asOFFSET(jjOBJ, counter)); engine->RegisterObjectProperty("jjOBJ", "uint curFrame", asOFFSET(jjOBJ, curFrame)); engine->RegisterObjectMethod("jjOBJ", "uint determineCurFrame(bool change = true)", asMETHOD(jjOBJ, determineCurFrame), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "int age", asOFFSET(jjOBJ, age)); engine->RegisterObjectProperty("jjOBJ", "int creator", asOFFSET(jjOBJ, creator)); // Deprecated engine->RegisterObjectMethod("jjOBJ", "uint16 get_creatorID() const", asMETHOD(jjOBJ, get_creatorID), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "uint16 set_creatorID(uint16)", asMETHOD(jjOBJ, set_creatorID), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "CREATOR::Type get_creatorType() const", asMETHOD(jjOBJ, get_creatorType), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "CREATOR::Type set_creatorType(CREATOR::Type)", asMETHOD(jjOBJ, set_creatorType), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "int16 curAnim", asOFFSET(jjOBJ, curAnim)); engine->RegisterObjectMethod("jjOBJ", "int16 determineCurAnim(uint8 setID, uint8 animation, bool change = true)", asMETHOD(jjOBJ, determineCurAnim), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "int16 killAnim", asOFFSET(jjOBJ, killAnim)); engine->RegisterObjectProperty("jjOBJ", "uint8 freeze", asOFFSET(jjOBJ, freeze)); engine->RegisterObjectProperty("jjOBJ", "uint8 lightType", asOFFSET(jjOBJ, lightType)); engine->RegisterObjectProperty("jjOBJ", "int8 frameID", asOFFSET(jjOBJ, frameID)); engine->RegisterObjectProperty("jjOBJ", "int8 noHit", asOFFSET(jjOBJ, noHit)); // Deprecated engine->RegisterObjectMethod("jjOBJ", "HANDLING::Bullet get_bulletHandling() const", asMETHOD(jjOBJ, get_bulletHandling), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "HANDLING::Bullet set_bulletHandling(HANDLING::Bullet)", asMETHOD(jjOBJ, set_bulletHandling), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_causesRicochet() const", asMETHOD(jjOBJ, get_ricochet), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_causesRicochet(bool)", asMETHOD(jjOBJ, set_ricochet), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_isFreezable() const", asMETHOD(jjOBJ, get_freezable), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_isFreezable(bool)", asMETHOD(jjOBJ, set_freezable), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_isBlastable() const", asMETHOD(jjOBJ, get_blastable), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_isBlastable(bool)", asMETHOD(jjOBJ, set_blastable), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "int8 energy", asOFFSET(jjOBJ, energy)); engine->RegisterObjectProperty("jjOBJ", "int8 light", asOFFSET(jjOBJ, light)); engine->RegisterObjectProperty("jjOBJ", "uint8 objType", asOFFSET(jjOBJ, objType)); // Deprecated engine->RegisterObjectMethod("jjOBJ", "HANDLING::Player get_playerHandling() const", asMETHOD(jjOBJ, get_playerHandling), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "HANDLING::Player set_playerHandling(HANDLING::Player)", asMETHOD(jjOBJ, set_playerHandling), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_isTarget() const", asMETHOD(jjOBJ, get_isTarget), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_isTarget(bool)", asMETHOD(jjOBJ, set_isTarget), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_triggersTNT() const", asMETHOD(jjOBJ, get_triggersTNT), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_triggersTNT(bool)", asMETHOD(jjOBJ, set_triggersTNT), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_deactivates() const", asMETHOD(jjOBJ, get_deactivates), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_deactivates(bool)", asMETHOD(jjOBJ, set_deactivates), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool get_scriptedCollisions() const", asMETHOD(jjOBJ, get_scriptedCollisions), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool set_scriptedCollisions(bool)", asMETHOD(jjOBJ, set_scriptedCollisions), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "int8 state", asOFFSET(jjOBJ, state)); engine->RegisterObjectProperty("jjOBJ", "uint16 points", asOFFSET(jjOBJ, points)); engine->RegisterObjectProperty("jjOBJ", "uint8 eventID", asOFFSET(jjOBJ, eventID)); engine->RegisterObjectProperty("jjOBJ", "int8 direction", asOFFSET(jjOBJ, direction)); engine->RegisterObjectProperty("jjOBJ", "uint8 justHit", asOFFSET(jjOBJ, justHit)); engine->RegisterObjectProperty("jjOBJ", "int8 oldState", asOFFSET(jjOBJ, oldState)); engine->RegisterObjectProperty("jjOBJ", "int animSpeed", asOFFSET(jjOBJ, animSpeed)); engine->RegisterObjectProperty("jjOBJ", "int special", asOFFSET(jjOBJ, special)); engine->RegisterObjectMethod("jjOBJ", "int get_var(uint8) const", asMETHOD(jjOBJ, get_var), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "int set_var(uint8, int)", asMETHOD(jjOBJ, set_var), asCALL_THISCALL); engine->RegisterObjectProperty("jjOBJ", "uint8 doesHurt", asOFFSET(jjOBJ, doesHurt)); engine->RegisterObjectProperty("jjOBJ", "uint8 counterEnd", asOFFSET(jjOBJ, counterEnd)); engine->RegisterObjectProperty("jjOBJ", "const int16 objectID", asOFFSET(jjOBJ, objectID)); engine->RegisterGlobalFunction("void jjDeleteObject(int objectID)", asFUNCTION(jjOBJ::jjDeleteObject), asCALL_CDECL); engine->RegisterGlobalFunction("void jjKillObject(int objectID)", asFUNCTION(jjOBJ::jjKillObject), asCALL_CDECL); engine->RegisterGlobalProperty("const bool jjDeactivatingBecauseOfDeath", &jjDeactivatingBecauseOfDeath); engine->RegisterObjectMethod("jjOBJ", "int draw()", asMETHOD(jjOBJ, draw), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "int beSolid(bool shouldCheckForStompingLocalPlayers = false)", asMETHOD(jjOBJ, beSolid), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void bePlatform(float xOld, float yOld, int width = 0, int height = 0)", asMETHOD(jjOBJ, bePlatform), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void clearPlatform()", asMETHOD(jjOBJ, clearPlatform), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void putOnGround(bool precise = false)", asMETHOD(jjOBJ, putOnGround), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool ricochet()", asMETHOD(jjOBJ, ricochet), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "int unfreeze(int style)", asMETHOD(jjOBJ, unfreeze), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void delete()", asMETHOD(jjOBJ, deleteObject), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void deactivate()", asMETHOD(jjOBJ, deactivate), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void pathMovement()", asMETHOD(jjOBJ, pathMovement), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "int fireBullet(uint8 eventID) const", asMETHOD(jjOBJ, fireBullet), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void particlePixelExplosion(int style) const", asMETHOD(jjOBJ, particlePixelExplosion), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "void grantPickup(jjPLAYER@ player, int frequency) const", asMETHOD(jjOBJ, grantPickup), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "int findNearestPlayer(int maxDistance) const", asMETHOD(jjOBJ, findNearestPlayer), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "int findNearestPlayer(int maxDistance, int &out foundDistance) const", asMETHOD(jjOBJ, findNearestPlayerEx), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool doesCollide(const jjOBJ@ object, bool always = false) const", asMETHOD(jjOBJ, doesCollide), asCALL_THISCALL); engine->RegisterObjectMethod("jjOBJ", "bool doesCollide(const jjPLAYER@ player, bool always = false) const", asMETHOD(jjOBJ, doesCollidePlayer), asCALL_THISCALL); engine->RegisterGlobalFunction("void jjAddParticleTileExplosion(uint16 xTile, uint16 yTile, uint16 tile, bool collapseSceneryStyle)", asFUNCTION(jjAddParticleTileExplosion), asCALL_CDECL); engine->RegisterGlobalFunction("void jjAddParticlePixelExplosion(float xPixel, float yPixel, int curFrame, int direction, int mode)", asFUNCTION(jjAddParticlePixelExplosion), asCALL_CDECL); engine->SetDefaultNamespace("PARTICLE"); engine->RegisterEnum("Type"); engine->RegisterEnumValue("Type", "INACTIVE", particleNONE); engine->RegisterEnumValue("Type", "PIXEL", particlePIXEL); engine->RegisterEnumValue("Type", "FIRE", particleFIRE); engine->RegisterEnumValue("Type", "SMOKE", particleSMOKE); engine->RegisterEnumValue("Type", "ICETRAIL", particleICETRAIL); engine->RegisterEnumValue("Type", "SPARK", particleSPARK); engine->RegisterEnumValue("Type", "STRING", particleSCORE); engine->RegisterEnumValue("Type", "SNOW", particleSNOW); engine->RegisterEnumValue("Type", "RAIN", particleRAIN); engine->RegisterEnumValue("Type", "FLOWER", particleFLOWER); engine->RegisterEnumValue("Type", "LEAF", particleLEAF); engine->RegisterEnumValue("Type", "STAR", particleSTAR); engine->RegisterEnumValue("Type", "TILE", particleTILE); engine->SetDefaultNamespace(""); engine->RegisterObjectType("jjPARTICLE", sizeof(jjPARTICLE), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterGlobalFunction("jjPARTICLE @get_jjParticles(int)", asFUNCTION(GetParticle), asCALL_CDECL); engine->RegisterGlobalFunction("jjPARTICLE @jjAddParticle(PARTICLE::Type type)", asFUNCTION(AddParticle), asCALL_CDECL); engine->RegisterObjectProperty("jjPARTICLE", "float xPos", asOFFSET(jjPARTICLE, xPos)); engine->RegisterObjectProperty("jjPARTICLE", "float yPos", asOFFSET(jjPARTICLE, yPos)); engine->RegisterObjectProperty("jjPARTICLE", "float xSpeed", asOFFSET(jjPARTICLE, xSpeed)); engine->RegisterObjectProperty("jjPARTICLE", "float ySpeed", asOFFSET(jjPARTICLE, ySpeed)); engine->RegisterObjectProperty("jjPARTICLE", "uint8 type", asOFFSET(jjPARTICLE, particleType)); engine->RegisterObjectProperty("jjPARTICLE", "bool isActive", asOFFSET(jjPARTICLE, active)); engine->RegisterObjectType("jjPARTICLEPIXEL", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLEPIXEL", "uint8 size", 0); engine->RegisterObjectMethod("jjPARTICLEPIXEL", "uint8 get_color(int) const", asMETHOD(jjPARTICLEPIXEL, get_color), asCALL_THISCALL); engine->RegisterObjectMethod("jjPARTICLEPIXEL", "uint8 set_color(int, uint8)", asMETHOD(jjPARTICLEPIXEL, set_color), asCALL_THISCALL); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLEPIXEL pixel", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLEFIRE", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLEFIRE", "uint8 size", 0); engine->RegisterObjectProperty("jjPARTICLEFIRE", "uint8 color", 1); engine->RegisterObjectProperty("jjPARTICLEFIRE", "uint8 colorStop", 2); engine->RegisterObjectProperty("jjPARTICLEFIRE", "int8 colorDelta", 3); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLEFIRE fire", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLESMOKE", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLESMOKE", "uint8 countdown", 0); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLESMOKE smoke", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLEICETRAIL", 9, asOBJ_VALUE | asOBJ_POD); engine->RegisterObjectProperty("jjPARTICLEICETRAIL", "uint8 color", 0); engine->RegisterObjectProperty("jjPARTICLEICETRAIL", "uint8 colorStop", 1); engine->RegisterObjectProperty("jjPARTICLEICETRAIL", "int8 colorDelta", 2); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLEICETRAIL icetrail", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLESPARK", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLESPARK", "uint8 color", 0); engine->RegisterObjectProperty("jjPARTICLESPARK", "uint8 colorStop", 1); engine->RegisterObjectProperty("jjPARTICLESPARK", "int8 colorDelta", 2); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLESPARK spark", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLESTRING", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectMethod("jjPARTICLESTRING", "::string get_text() const", asMETHOD(jjPARTICLESTRING, get_text), asCALL_THISCALL); engine->RegisterObjectMethod("jjPARTICLESTRING", "void set_text(::string)", asMETHOD(jjPARTICLESTRING, set_text), asCALL_THISCALL); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLESTRING string", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLESNOW", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLESNOW", "uint8 frame", 0); engine->RegisterObjectProperty("jjPARTICLESNOW", "uint8 countup", 1); engine->RegisterObjectProperty("jjPARTICLESNOW", "uint8 countdown", 2); engine->RegisterObjectProperty("jjPARTICLESNOW", "uint16 frameBase", 3); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLESNOW snow", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLERAIN", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLERAIN", "uint8 frame", 0); engine->RegisterObjectProperty("jjPARTICLERAIN", "uint16 frameBase", 1); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLERAIN rain", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLELEAF", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLELEAF", "uint8 frame", 0); engine->RegisterObjectProperty("jjPARTICLELEAF", "uint8 countup", 1); engine->RegisterObjectProperty("jjPARTICLELEAF", "bool noclip", 2); engine->RegisterObjectProperty("jjPARTICLELEAF", "uint8 height", 3); engine->RegisterObjectProperty("jjPARTICLELEAF", "uint16 frameBase", 4); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLELEAF leaf", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLEFLOWER", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLEFLOWER", "uint8 size", 0); engine->RegisterObjectProperty("jjPARTICLEFLOWER", "uint8 color", 1); engine->RegisterObjectProperty("jjPARTICLEFLOWER", "uint8 angle", 2); engine->RegisterObjectProperty("jjPARTICLEFLOWER", "int8 angularSpeed", 3); engine->RegisterObjectProperty("jjPARTICLEFLOWER", "uint8 petals", 4); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLEFLOWER flower", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjPARTICLESTAR", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLESTAR", "uint8 size", 0); engine->RegisterObjectProperty("jjPARTICLESTAR", "uint8 color", 1); engine->RegisterObjectProperty("jjPARTICLESTAR", "uint8 angle", 2); engine->RegisterObjectProperty("jjPARTICLESTAR", "int8 angularSpeed", 3); engine->RegisterObjectProperty("jjPARTICLESTAR", "uint8 frame", 4); engine->RegisterObjectProperty("jjPARTICLESTAR", "uint8 colorChangeCounter", 5); engine->RegisterObjectProperty("jjPARTICLESTAR", "uint8 colorChangeInterval", 6); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLESTAR star", asOFFSET(jjPARTICLE, GENERIC)); // TODO /*engine->RegisterObjectType("jjPARTICLETILE", 9, asOBJ_VALUE | asOBJ_POD); // Private/deprecated engine->RegisterObjectProperty("jjPARTICLETILE", "uint8 quadrant", 0); engine->RegisterObjectMethod("jjPARTICLETILE", "uint16 get_tileID() const", asMETHOD(TparticleTILE, get_AStile), asCALL_THISCALL); engine->RegisterObjectMethod("jjPARTICLETILE", "uint16 set_tileID(uint16)", asMETHOD(TparticleTILE, set_AStile), asCALL_THISCALL); engine->RegisterObjectProperty("jjPARTICLE", "jjPARTICLETILE tile", asOFFSET(jjPARTICLE, GENERIC)); engine->RegisterObjectType("jjCONTROLPOINT", sizeof(_controlPoint), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterGlobalFunction("const jjCONTROLPOINT@ get_jjControlPoints(int)", asFUNCTION(getControlPoint), asCALL_CDECL); engine->RegisterObjectProperty("jjCONTROLPOINT", "const string name", asOFFSET(_controlPoint, name)); engine->RegisterObjectProperty("jjCONTROLPOINT", "const int xTile", asOFFSET(_controlPoint, xTile)); engine->RegisterObjectProperty("jjCONTROLPOINT", "const int yTile", asOFFSET(_controlPoint, yTile)); engine->RegisterObjectProperty("jjCONTROLPOINT", "const int direction", asOFFSET(_controlPoint, direction)); engine->RegisterObjectProperty("jjCONTROLPOINT", "const TEAM::Color controlTeam", asOFFSET(_controlPoint, controlTeam)); engine->RegisterObjectMethod("jjCONTROLPOINT", "float get_xPos() const", AS_OBJ_FLOAT_GETTER(_controlPoint, pos.x), asCALL_CDECL_OBJLAST); engine->RegisterObjectMethod("jjCONTROLPOINT", "float get_yPos() const", AS_OBJ_FLOAT_GETTER(_controlPoint, pos.y), asCALL_CDECL_OBJLAST);*/ engine->RegisterObjectType("jjSTREAM", sizeof(jjSTREAM), asOBJ_REF); engine->RegisterObjectBehaviour("jjSTREAM", asBEHAVE_FACTORY, "jjSTREAM@ f()", asFUNCTION(jjSTREAM::Create), asCALL_CDECL); engine->RegisterObjectBehaviour("jjSTREAM", asBEHAVE_FACTORY, "jjSTREAM@ f(const ::string &in filename)", asFUNCTION(jjSTREAM::CreateFromFile), asCALL_CDECL); engine->RegisterObjectBehaviour("jjSTREAM", asBEHAVE_ADDREF, "void f()", asMETHOD(jjSTREAM, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjSTREAM", asBEHAVE_RELEASE, "void f()", asMETHOD(jjSTREAM, Release), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "jjSTREAM& opAssign(const jjSTREAM &in)", asMETHODPR(jjSTREAM, operator=, (const jjSTREAM& other), jjSTREAM&), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "uint getSize() const", asMETHOD(jjSTREAM, getSize), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool isEmpty() const", asMETHOD(jjSTREAM, isEmpty), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool save(const ::string &in filename) const", asMETHOD(jjSTREAM, save), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "void clear()", asMETHOD(jjSTREAM, clear), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool discard(uint count)", asMETHOD(jjSTREAM, discard), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool write(const ::string &in value)", asMETHODPR(jjSTREAM, write, (const String&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool write(const jjSTREAM &in value)", asMETHODPR(jjSTREAM, write, (const jjSTREAM&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool get(::string &out value, uint count = 1)", asMETHODPR(jjSTREAM, get, (String&, uint32_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool get(jjSTREAM &out value, uint count = 1)", asMETHODPR(jjSTREAM, get, (jjSTREAM&, uint32_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool getLine(::string &out value, const ::string &in delim = '\\n')", asMETHOD(jjSTREAM, getLine), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(bool value)", asMETHODPR(jjSTREAM, push, (bool), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(uint8 value)", asMETHODPR(jjSTREAM, push, (uint8_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(int8 value)", asMETHODPR(jjSTREAM, push, (int8_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(uint16 value)", asMETHODPR(jjSTREAM, push, (uint16_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(int16 value)", asMETHODPR(jjSTREAM, push, (int16_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(uint32 value)", asMETHODPR(jjSTREAM, push, (uint32_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(int32 value)", asMETHODPR(jjSTREAM, push, (int32_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(uint64 value)", asMETHODPR(jjSTREAM, push, (uint64_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(int64 value)", asMETHODPR(jjSTREAM, push, (int64_t), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(float value)", asMETHODPR(jjSTREAM, push, (float), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(double value)", asMETHODPR(jjSTREAM, push, (double), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(const ::string &in value)", asMETHODPR(jjSTREAM, push, (const String&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool push(const jjSTREAM &in value)", asMETHODPR(jjSTREAM, push, (const jjSTREAM&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(bool &out value)", asMETHODPR(jjSTREAM, pop, (bool&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(uint8 &out value)", asMETHODPR(jjSTREAM, pop, (uint8_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(int8 &out value)", asMETHODPR(jjSTREAM, pop, (int64_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(uint16 &out value)", asMETHODPR(jjSTREAM, pop, (uint16_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(int16 &out value)", asMETHODPR(jjSTREAM, pop, (int16_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(uint32 &out value)", asMETHODPR(jjSTREAM, pop, (uint32_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(int32 &out value)", asMETHODPR(jjSTREAM, pop, (int32_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(uint64 &out value)", asMETHODPR(jjSTREAM, pop, (uint64_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(int64 &out value)", asMETHODPR(jjSTREAM, pop, (int64_t&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(float &out value)", asMETHODPR(jjSTREAM, pop, (float&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(double &out value)", asMETHODPR(jjSTREAM, pop, (double&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(::string &out value)", asMETHODPR(jjSTREAM, pop, (String&), bool), asCALL_THISCALL); engine->RegisterObjectMethod("jjSTREAM", "bool pop(jjSTREAM &out value)", asMETHODPR(jjSTREAM, pop, (jjSTREAM&), bool), asCALL_THISCALL); engine->RegisterGlobalFunction("bool jjSendPacket(const jjSTREAM &in packet, int toClientID = 0, uint toScriptModuleID = ::jjScriptModuleID)", asFUNCTION(jjSendPacket), asCALL_CDECL); // TODO /*engine->RegisterGlobalFunction("bool jjTakeScreenshot(const string &in filename = '')", asFUNCTION(requestScreenshot), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjZlibCompress(const jjSTREAM &in input, jjSTREAM &out output)", asFUNCTION(streamCompress), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjZlibUncompress(const jjSTREAM &in input, jjSTREAM &out output, uint size)", asFUNCTION(streamUncompress), asCALL_CDECL); engine->RegisterGlobalFunction("uint jjCRC32(const jjSTREAM &in input, uint crc = 0)", asFUNCTION(streamCRC32), asCALL_CDECL);*/ engine->RegisterObjectType("jjRNG", sizeof(jjRNG), asOBJ_REF); engine->RegisterObjectBehaviour("jjRNG", asBEHAVE_FACTORY, "jjRNG@ f(uint64 seed = 5489)", asFUNCTION(jjRNG::Create), asCALL_CDECL); engine->RegisterObjectBehaviour("jjRNG", asBEHAVE_ADDREF, "void f()", asMETHOD(jjRNG, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjRNG", asBEHAVE_RELEASE, "void f()", asMETHOD(jjRNG, Release), asCALL_THISCALL); engine->RegisterObjectMethod("jjRNG", "uint64 opCall()", asMETHOD(jjRNG, operator()), asCALL_THISCALL); engine->RegisterObjectMethod("jjRNG", "jjRNG& opAssign(const jjRNG &in)", asMETHOD(jjRNG, operator=), asCALL_THISCALL); engine->RegisterObjectMethod("jjRNG", "bool opEquals(const jjRNG &in) const", asMETHOD(jjRNG, operator==), asCALL_THISCALL); engine->RegisterObjectMethod("jjRNG", "void seed(uint64 value = 5489)", asMETHOD(jjRNG, seed), asCALL_THISCALL); engine->RegisterObjectMethod("jjRNG", "void discard(uint64 count = 1)", asMETHOD(jjRNG, discard), asCALL_THISCALL); engine->RegisterInterface("jjPUBLICINTERFACE"); engine->RegisterInterfaceMethod("jjPUBLICINTERFACE", "string getVersion() const"); // TODO //engine->RegisterGlobalFunction("jjPUBLICINTERFACE@ jjGetPublicInterface(const string &in moduleName)", asFUNCTION(getPublicInterface), asCALL_CDECL); engine->RegisterObjectType("jjANIMFRAME", sizeof(jjANIMFRAME), asOBJ_REF /*| asOBJ_NOCOUNT*/); engine->RegisterObjectBehaviour("jjANIMFRAME", asBEHAVE_ADDREF, "void f()", asMETHOD(jjANIMFRAME, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjANIMFRAME", asBEHAVE_RELEASE, "void f()", asMETHOD(jjANIMFRAME, Release), asCALL_THISCALL); engine->RegisterGlobalFunction("jjANIMFRAME @get_jjAnimFrames(uint)", asFUNCTION(jjANIMFRAME::get_jjAnimFrames), asCALL_CDECL); engine->RegisterObjectProperty("jjANIMFRAME", "int16 hotSpotX", asOFFSET(jjANIMFRAME, hotSpotX)); engine->RegisterObjectProperty("jjANIMFRAME", "int16 hotSpotY", asOFFSET(jjANIMFRAME, hotSpotY)); engine->RegisterObjectProperty("jjANIMFRAME", "int16 coldSpotX", asOFFSET(jjANIMFRAME, coldSpotX)); engine->RegisterObjectProperty("jjANIMFRAME", "int16 coldSpotY", asOFFSET(jjANIMFRAME, coldSpotY)); engine->RegisterObjectProperty("jjANIMFRAME", "int16 gunSpotX", asOFFSET(jjANIMFRAME, gunSpotX)); engine->RegisterObjectProperty("jjANIMFRAME", "int16 gunSpotY", asOFFSET(jjANIMFRAME, gunSpotY)); engine->RegisterObjectProperty("jjANIMFRAME", "const uint16 width", asOFFSET(jjANIMFRAME, width)); engine->RegisterObjectProperty("jjANIMFRAME", "const uint16 height", asOFFSET(jjANIMFRAME, height)); engine->RegisterObjectMethod("jjANIMFRAME", "jjANIMFRAME& opAssign(const jjANIMFRAME &in)", asMETHOD(jjANIMFRAME, operator=), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMFRAME", "bool get_transparent() const", asMETHOD(jjANIMFRAME, get_transparent), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMFRAME", "bool set_transparent(bool)", asMETHOD(jjANIMFRAME, set_transparent), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMFRAME", "bool doesCollide(int xPos, int yPos, int direction, const jjANIMFRAME@ frame2, int xPos2, int yPos2, int direction2, bool always = false) const", asMETHOD(jjANIMFRAME, doesCollide), asCALL_THISCALL); engine->RegisterObjectType("jjANIMATION", sizeof(jjANIMATION), asOBJ_REF /*| asOBJ_NOCOUNT*/); engine->RegisterObjectBehaviour("jjANIMATION", asBEHAVE_ADDREF, "void f()", asMETHOD(jjANIMATION, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjANIMATION", asBEHAVE_RELEASE, "void f()", asMETHOD(jjANIMATION, Release), asCALL_THISCALL); engine->RegisterGlobalFunction("jjANIMATION @get_jjAnimations(uint)", asFUNCTION(jjANIMATION::get_jjAnimations), asCALL_CDECL); engine->RegisterObjectProperty("jjANIMATION", "uint16 frameCount", asOFFSET(jjANIMATION, frameCount)); engine->RegisterObjectProperty("jjANIMATION", "int16 fps", asOFFSET(jjANIMATION, fps)); engine->RegisterObjectMethod("jjANIMATION", "uint get_firstFrame() const", asMETHOD(jjANIMATION, get_firstFrame), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMATION", "uint set_firstFrame(uint)", asMETHOD(jjANIMATION, set_firstFrame), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMATION", "uint opImplConv() const", asMETHOD(jjANIMATION, getAnimFirstFrame), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMATION", "jjANIMATION& opAssign(const jjANIMATION &in)", asMETHOD(jjANIMATION, operator=), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMATION", "bool save(const ::string &in filename, const jjPAL &in palette = jjPalette) const", asMETHOD(jjANIMATION, save), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMATION", "bool load(const ::string &in filename, int hotSpotX, int hotSpotY, int coldSpotYOffset = 0, int firstFrameToOverwrite = -1)", asMETHOD(jjANIMATION, load), asCALL_THISCALL); engine->RegisterObjectType("jjANIMSET", sizeof(jjANIMSET), asOBJ_REF /*| asOBJ_NOCOUNT*/); engine->RegisterObjectBehaviour("jjANIMSET", asBEHAVE_ADDREF, "void f()", asMETHOD(jjANIMSET, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjANIMSET", asBEHAVE_RELEASE, "void f()", asMETHOD(jjANIMSET, Release), asCALL_THISCALL); engine->RegisterGlobalFunction("jjANIMSET @get_jjAnimSets(uint)", asFUNCTION(jjANIMSET::get_jjAnimSets), asCALL_CDECL); engine->RegisterObjectProperty("jjANIMSET", "uint firstAnim", 0); engine->RegisterObjectMethod("jjANIMSET", "uint opImplConv() const", asMETHOD(jjANIMSET, convertAnimSetToUint), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMSET", "jjANIMSET @load(uint fileSetID = 2048, const string &in filename = '', int firstAnimToOverwrite = -1, int firstFrameToOverwrite = -1)", asMETHOD(jjANIMSET, load), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMSET", "jjANIMSET @allocate(const array &in frameCounts)", asMETHOD(jjANIMSET, allocate), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawString(int xPixel, int yPixel, const ::string &in text, const jjANIMATION &in animation, STRING::Mode mode = STRING::NORMAL, uint8 param = 0)", asMETHOD(jjCANVAS, drawString), asCALL_THISCALL); engine->RegisterObjectMethod("jjCANVAS", "void drawString(int xPixel, int yPixel, const ::string &in text, const jjANIMATION &in animation, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0)", asMETHOD(jjCANVAS, drawStringEx), asCALL_THISCALL); engine->RegisterGlobalFunction("void jjDrawString(float xPixel, float yPixel, const ::string &in text, const jjANIMATION &in animation, STRING::Mode mode = STRING::NORMAL, uint8 param = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjCANVAS::jjDrawString), asCALL_CDECL); engine->RegisterGlobalFunction("void jjDrawString(float xPixel, float yPixel, const ::string &in text, const jjANIMATION &in animation, const jjTEXTAPPEARANCE &in appearance, uint8 param1 = 0, SPRITE::Mode spriteMode = SPRITE::PALSHIFT, uint8 param2 = 0, int8 layerZ = 4, uint8 layerXY = 4, int8 playerID = -1)", asFUNCTION(jjCANVAS::jjDrawStringEx), asCALL_CDECL); engine->RegisterGlobalFunction("int jjGetStringWidth(const ::string &in text, const jjANIMATION &in animation, const jjTEXTAPPEARANCE &in style)", asFUNCTION(jjCANVAS::jjGetStringWidth), asCALL_CDECL); engine->RegisterObjectType("jjLAYER", sizeof(jjLAYER), asOBJ_REF); engine->RegisterObjectType("jjPIXELMAP", sizeof(jjPIXELMAP), asOBJ_REF); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(uint16 tileID = 0)", asFUNCTION(jjPIXELMAP::CreateFromTile), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(uint width, uint height)", asFUNCTION(jjPIXELMAP::CreateFromSize), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(const jjANIMFRAME@ animFrame)", asFUNCTION(jjPIXELMAP::CreateFromFrame), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(uint left, uint top, uint width, uint height, uint layer = 4)", asFUNCTION(jjPIXELMAP::CreateFromLayer), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(uint left, uint top, uint width, uint height, const jjLAYER &in layer)", asFUNCTION(jjPIXELMAP::CreateFromLayerObject), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(TEXTURE::Texture texture)", asFUNCTION(jjPIXELMAP::CreateFromTexture), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_FACTORY, "jjPIXELMAP@ f(const ::string &in filename, const jjPAL &in palette = jjPalette, uint8 threshold = 1)", asFUNCTION(jjPIXELMAP::CreateFromFilename), asCALL_CDECL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_ADDREF, "void f()", asMETHOD(jjPIXELMAP, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjPIXELMAP", asBEHAVE_RELEASE, "void f()", asMETHOD(jjPIXELMAP, Release), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "uint8& opIndex(uint, uint)", asMETHOD(jjPIXELMAP, GetPixel), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "const uint8& opIndex(uint, uint) const", asMETHOD(jjPIXELMAP, GetPixel), asCALL_THISCALL); engine->RegisterObjectProperty("jjPIXELMAP", "const uint width", asOFFSET(jjPIXELMAP, width)); engine->RegisterObjectProperty("jjPIXELMAP", "const uint height", asOFFSET(jjPIXELMAP, height)); engine->RegisterObjectMethod("jjPIXELMAP", "bool save(uint16 tileID, bool hFlip = false) const", asMETHOD(jjPIXELMAP, saveToTile), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "bool save(jjANIMFRAME@ frame) const", asMETHOD(jjPIXELMAP, saveToFrame), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "bool save(const ::string &in filename, const jjPAL &in palette = jjPalette) const", asMETHOD(jjPIXELMAP, saveToFile), asCALL_THISCALL); // TODO /*engine->RegisterObjectMethod("jjPIXELMAP", "bool makeTexture(jjLAYER@ layer = null)", asMETHOD(PixelMap, saveToTexture), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& crop(uint, uint, uint, uint)", asMETHOD(PixelMap, crop), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& addBorders(int, int, int, int, uint8 = 0)", asMETHOD(PixelMap, addBorders), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& flip(SPRITE::Direction)", asMETHOD(PixelMap, flip), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& rotate()", asMETHOD(PixelMap, rotate), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& recolor(const array &in)", asMETHOD(PixelMap, recolor), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& resize(uint, uint)", asMETHOD(PixelMap, resize), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& trim(uint &out, uint &out, uint &out, uint &out, uint8 = 0)", asMETHOD(PixelMap, trim), asCALL_THISCALL); engine->RegisterObjectMethod("jjPIXELMAP", "jjPIXELMAP& trim(uint8 = 0)", asMETHOD(PixelMap, trimBasic), asCALL_THISCALL); engine->RegisterObjectMethod("jjANIMSET", "jjANIMSET @load(const jjPIXELMAP &in, uint frameWidth, uint frameHeight, uint frameSpacingX = 0, uint frameSpacingY = 0, uint startX = 0, uint startY = 0, const array &in coldSpotYOffsets = array(), int firstAnimToOverwrite = -1, int firstFrameToOverwrite = -1)", asFUNCTION(importSpriteSheetToAnimSet), asCALL_CDECL_OBJFIRST);*/ engine->RegisterObjectType("jjMASKMAP", sizeof(jjMASKMAP), asOBJ_REF); engine->RegisterObjectBehaviour("jjMASKMAP", asBEHAVE_FACTORY, "jjMASKMAP@ f(bool filled = false)", asFUNCTION(jjMASKMAP::CreateFromBool), asCALL_CDECL); engine->RegisterObjectBehaviour("jjMASKMAP", asBEHAVE_FACTORY, "jjMASKMAP@ f(uint16 tileID)", asFUNCTION(jjMASKMAP::CreateFromTile), asCALL_CDECL); engine->RegisterObjectBehaviour("jjMASKMAP", asBEHAVE_ADDREF, "void f()", asMETHOD(jjMASKMAP, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjMASKMAP", asBEHAVE_RELEASE, "void f()", asMETHOD(jjMASKMAP, Release), asCALL_THISCALL); engine->RegisterObjectMethod("jjMASKMAP", "bool& opIndex(uint, uint)", asMETHOD(jjMASKMAP, GetPixel), asCALL_THISCALL); engine->RegisterObjectMethod("jjMASKMAP", "const bool& opIndex(uint, uint) const", asMETHOD(jjMASKMAP, GetPixel), asCALL_THISCALL); engine->RegisterObjectMethod("jjMASKMAP", "bool save(uint16 tileID, bool hFlip = false) const", asMETHOD(jjMASKMAP, save), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjLAYER", asBEHAVE_FACTORY, "jjLAYER@ f(uint layerWidth, uint layerHeight)", asFUNCTION(jjLAYER::CreateFromSize), asCALL_CDECL); engine->RegisterObjectBehaviour("jjLAYER", asBEHAVE_FACTORY, "jjLAYER@ f(const jjLAYER &in layer)", asFUNCTION(jjLAYER::CreateCopy), asCALL_CDECL); engine->RegisterObjectBehaviour("jjLAYER", asBEHAVE_ADDREF, "void f()", asMETHOD(jjLAYER, AddRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjLAYER", asBEHAVE_RELEASE, "void f()", asMETHOD(jjLAYER, Release), asCALL_THISCALL); engine->RegisterGlobalFunction("jjLAYER @get_jjLayers(int)", asFUNCTION(jjLAYER::get_jjLayers), asCALL_CDECL); engine->RegisterObjectProperty("jjLAYER", "const int width", asOFFSET(jjLAYER, width)); engine->RegisterObjectProperty("jjLAYER", "const int widthReal", asOFFSET(jjLAYER, widthReal)); engine->RegisterObjectProperty("jjLAYER", "const int widthRounded", asOFFSET(jjLAYER, widthRounded)); engine->RegisterObjectProperty("jjLAYER", "const int height", asOFFSET(jjLAYER, height)); engine->RegisterObjectProperty("jjLAYER", "float xSpeed", asOFFSET(jjLAYER, xSpeed)); engine->RegisterObjectProperty("jjLAYER", "float ySpeed", asOFFSET(jjLAYER, ySpeed)); engine->RegisterObjectProperty("jjLAYER", "float xAutoSpeed", asOFFSET(jjLAYER, xAutoSpeed)); engine->RegisterObjectProperty("jjLAYER", "float yAutoSpeed", asOFFSET(jjLAYER, yAutoSpeed)); engine->RegisterObjectProperty("jjLAYER", "float xOffset", asOFFSET(jjLAYER, xOffset)); engine->RegisterObjectProperty("jjLAYER", "float yOffset", asOFFSET(jjLAYER, yOffset)); engine->RegisterObjectProperty("jjLAYER", "float xInnerSpeed", asOFFSET(jjLAYER, xInnerSpeed)); engine->RegisterObjectProperty("jjLAYER", "float yInnerSpeed", asOFFSET(jjLAYER, yInnerSpeed)); engine->RegisterObjectProperty("jjLAYER", "float xInnerAutoSpeed", asOFFSET(jjLAYER, xInnerAutoSpeed)); engine->RegisterObjectProperty("jjLAYER", "float yInnerAutoSpeed", asOFFSET(jjLAYER, yInnerAutoSpeed)); engine->RegisterObjectMethod("jjLAYER", "SPRITE::Mode get_spriteMode() const", asMETHOD(jjLAYER, get_spriteMode), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "SPRITE::Mode set_spriteMode(SPRITE::Mode)", asMETHOD(jjLAYER, set_spriteMode), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "uint8 get_spriteParam() const", asMETHOD(jjLAYER, get_spriteParam), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "uint8 set_spriteParam(uint8)", asMETHOD(jjLAYER, set_spriteParam), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void setXSpeed(float newspeed, bool newSpeedIsAnAutoSpeed)", asMETHOD(jjLAYER, setXSpeed), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void setYSpeed(float newspeed, bool newSpeedIsAnAutoSpeed)", asMETHOD(jjLAYER, setYSpeed), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "float getXPosition(const jjPLAYER &in play) const", asMETHOD(jjLAYER, getXPosition), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "float getYPosition(const jjPLAYER &in play) const", asMETHOD(jjLAYER, getYPosition), asCALL_THISCALL); // TODO /*engine->RegisterObjectType("jjLAYERWARPHORIZON", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERWARPHORIZON", "float fadePositionX", asOFFSET(jjLAYER, WARPHORIZON.FadePosition[0])); engine->RegisterObjectProperty("jjLAYERWARPHORIZON", "float fadePositionY", asOFFSET(jjLAYER, WARPHORIZON.FadePosition[1])); engine->RegisterObjectMethod("jjLAYERWARPHORIZON", "jjPALCOLOR getFadeColor() const", asMETHOD(jjLAYER, GetFadeColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWARPHORIZON", "void setFadeColor(jjPALCOLOR)", asMETHOD(jjLAYER, SetFadeColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWARPHORIZON", "bool get_stars() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWARPHORIZON", "bool set_stars(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYERWARPHORIZON", "bool fade", asOFFSET(jjLAYER, WARPHORIZON.Fade)); engine->RegisterObjectProperty("jjLAYER", "jjLAYERWARPHORIZON warpHorizon", asOFFSET(jjLAYER, WARPHORIZON)); engine->RegisterObjectType("jjLAYERTUNNEL", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERTUNNEL", "float fadePositionX", asOFFSET(jjLAYER, TUNNEL.FadePosition[0])); engine->RegisterObjectProperty("jjLAYERTUNNEL", "float fadePositionY", asOFFSET(jjLAYER, TUNNEL.FadePosition[1])); engine->RegisterObjectMethod("jjLAYERTUNNEL", "jjPALCOLOR getFadeColor() const", asMETHOD(jjLAYER, GetFadeColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERTUNNEL", "void setFadeColor(jjPALCOLOR)", asMETHOD(jjLAYER, SetFadeColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERTUNNEL", "bool get_spiral() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERTUNNEL", "bool set_spiral(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYERTUNNEL", "bool fade", asOFFSET(jjLAYER, TUNNEL.Fade)); engine->RegisterObjectProperty("jjLAYER", "jjLAYERTUNNEL tunnel", asOFFSET(jjLAYER, TUNNEL)); engine->RegisterObjectType("jjLAYERMENU", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERMENU", "float pivotX", asOFFSET(jjLAYER, MENU.Pivot[0])); engine->RegisterObjectProperty("jjLAYERMENU", "float pivotY", asOFFSET(jjLAYER, MENU.Pivot[1])); engine->RegisterObjectMethod("jjLAYERMENU", "uint8 get_palrow16() const", asMETHOD(jjLAYER, GetFadeComponent<0>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "uint8 get_palrow32() const", asMETHOD(jjLAYER, GetFadeComponent<1>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "uint8 get_palrow256() const", asMETHOD(jjLAYER, GetFadeComponent<2>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "uint8 set_palrow16(uint8)", asMETHOD(jjLAYER, SetFadeComponent<0>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "uint8 set_palrow32(uint8)", asMETHOD(jjLAYER, SetFadeComponent<1>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "uint8 set_palrow256(uint8)", asMETHOD(jjLAYER, SetFadeComponent<2>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "bool get_lightToDark() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERMENU", "bool set_lightToDark(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYER", "jjLAYERMENU menu", asOFFSET(jjLAYER, MENU)); engine->RegisterObjectType("jjLAYERTILEMENU", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERTILEMENU", "float pivotX", asOFFSET(jjLAYER, TILEMENU.Pivot[0])); engine->RegisterObjectProperty("jjLAYERTILEMENU", "float pivotY", asOFFSET(jjLAYER, TILEMENU.Pivot[1])); engine->RegisterObjectMethod("jjLAYERTILEMENU", "bool get_fullSize() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERTILEMENU", "bool set_fullSize(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYER", "jjLAYERTILEMENU tileMenu", asOFFSET(jjLAYER, TILEMENU)); engine->RegisterObjectType("jjLAYERWAVE", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERWAVE", "float amplitudeX", asOFFSET(jjLAYER, WAVE.Amplitude[0])); engine->RegisterObjectProperty("jjLAYERWAVE", "float amplitudeY", asOFFSET(jjLAYER, WAVE.Amplitude[1])); engine->RegisterObjectMethod("jjLAYERWAVE", "uint8 get_wavelengthX() const", asMETHOD(jjLAYER, GetFadeComponent<0>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "uint8 get_wavelengthY() const", asMETHOD(jjLAYER, GetFadeComponent<1>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "int8 get_waveSpeed() const", asMETHOD(jjLAYER, GetFadeComponent<2>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "uint8 set_wavelengthX(uint8)", asMETHOD(jjLAYER, SetFadeComponent<0>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "uint8 set_wavelengthY(uint8)", asMETHOD(jjLAYER, SetFadeComponent<1>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "int8 set_waveSpeed(int8)", asMETHOD(jjLAYER, SetFadeComponent<2>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "bool get_distortionAngle() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERWAVE", "bool set_distortionAngle(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYER", "jjLAYERWAVE wave", asOFFSET(jjLAYER, WAVE)); engine->RegisterObjectType("jjLAYERCYLINDER", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERCYLINDER", "float fadePositionX", asOFFSET(jjLAYER, CYLINDER.FadePosition[0])); engine->RegisterObjectProperty("jjLAYERCYLINDER", "float fadePositionY", asOFFSET(jjLAYER, CYLINDER.FadePosition[1])); engine->RegisterObjectMethod("jjLAYERCYLINDER", "jjPALCOLOR getFadeColor() const", asMETHOD(jjLAYER, GetFadeColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERCYLINDER", "void setFadeColor(jjPALCOLOR)", asMETHOD(jjLAYER, SetFadeColor), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERCYLINDER", "bool get_halfSize() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERCYLINDER", "bool set_halfSize(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYERCYLINDER", "bool fade", asOFFSET(jjLAYER, CYLINDER.Fade)); engine->RegisterObjectProperty("jjLAYER", "jjLAYERCYLINDER cylinder", asOFFSET(jjLAYER, CYLINDER)); engine->RegisterObjectType("jjLAYERREFLECTION", 0, asOBJ_REF | asOBJ_NOHANDLE); engine->RegisterObjectProperty("jjLAYERREFLECTION", "float fadePositionX", asOFFSET(jjLAYER, REFLECTION.FadePositionX)); engine->RegisterObjectProperty("jjLAYERREFLECTION", "float top", asOFFSET(jjLAYER, REFLECTION.Top)); engine->RegisterObjectMethod("jjLAYERREFLECTION", "uint8 get_tintOpacity() const", asMETHOD(jjLAYER, GetFadeComponent<0>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "uint8 get_distance() const", asMETHOD(jjLAYER, GetFadeComponent<1>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "uint8 get_distortion() const", asMETHOD(jjLAYER, GetFadeComponent<2>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "uint8 set_tintOpacity(uint8)", asMETHOD(jjLAYER, SetFadeComponent<0>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "uint8 set_distance(uint8)", asMETHOD(jjLAYER, SetFadeComponent<1>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "uint8 set_distortion(uint8)", asMETHOD(jjLAYER, SetFadeComponent<2>), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "bool get_blur() const", asMETHOD(jjLAYER, GetStars), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYERREFLECTION", "bool set_blur(bool)", asMETHOD(jjLAYER, SetStars), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYERREFLECTION", "uint8 tintColor", asOFFSET(jjLAYER, REFLECTION.OverlayColor)); engine->RegisterObjectProperty("jjLAYER", "jjLAYERREFLECTION reflection", asOFFSET(jjLAYER, REFLECTION));*/ engine->RegisterObjectMethod("jjLAYER", "TEXTURE::Style get_textureStyle() const", asMETHOD(jjLAYER, GetTextureMode), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void set_textureStyle(TEXTURE::Style)", asMETHOD(jjLAYER, SetTextureMode), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "TEXTURE::Texture get_texture() const", asMETHOD(jjLAYER, GetTexture), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void set_texture(TEXTURE::Texture)", asMETHOD(jjLAYER, SetTexture), asCALL_THISCALL); engine->RegisterObjectProperty("jjLAYER", "int rotationAngle", asOFFSET(jjLAYER, rotationAngle)); engine->RegisterObjectProperty("jjLAYER", "int rotationRadiusMultiplier", asOFFSET(jjLAYER, rotationRadiusMultiplier)); engine->RegisterObjectProperty("jjLAYER", "bool tileHeight", asOFFSET(jjLAYER, tileHeight)); engine->RegisterObjectProperty("jjLAYER", "bool tileWidth", asOFFSET(jjLAYER, tileWidth)); engine->RegisterObjectProperty("jjLAYER", "bool limitVisibleRegion", asOFFSET(jjLAYER, limitVisibleRegion)); engine->RegisterObjectProperty("jjLAYER", "const bool hasTileMap", asOFFSET(jjLAYER, hasTileMap)); engine->RegisterObjectProperty("jjLAYER", "bool hasTiles", asOFFSET(jjLAYER, hasTiles)); engine->RegisterGlobalFunction("array@ jjLayerOrderGet()", asFUNCTION(jjLAYER::jjLayerOrderGet), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjLayerOrderSet(const array &in order)", asFUNCTION(jjLAYER::jjLayerOrderSet), asCALL_CDECL); engine->RegisterGlobalFunction("array@ jjLayersFromLevel(const string &in filename, const array &in layerIDs, int tileIDAdjustmentFactor = 0)", asFUNCTION(jjLAYER::jjLayersFromLevel), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjTilesFromTileset(const string &in filename, uint firstTileID, uint tileCount, const array@ paletteColorMapping = null)", asFUNCTION(jjLAYER::jjTilesFromTileset), asCALL_CDECL); engine->SetDefaultNamespace("LAYERSPEEDMODEL"); engine->RegisterEnum("LayerSpeedModel"); engine->RegisterEnumValue("LayerSpeedModel", "NORMAL", 0); engine->RegisterEnumValue("LayerSpeedModel", "LAYER8", 1); engine->RegisterEnumValue("LayerSpeedModel", "BOTHSPEEDS", 2); engine->RegisterEnumValue("LayerSpeedModel", "FROMSTART", 3); engine->RegisterEnumValue("LayerSpeedModel", "FITLEVEL", 4); engine->RegisterEnumValue("LayerSpeedModel", "SPEEDMULTIPLIERS", 5); engine->SetDefaultNamespace(""); engine->RegisterObjectProperty("jjLAYER", "LAYERSPEEDMODEL::LayerSpeedModel xSpeedModel", asOFFSET(jjLAYER, SpeedModeX)); engine->RegisterObjectProperty("jjLAYER", "LAYERSPEEDMODEL::LayerSpeedModel ySpeedModel", asOFFSET(jjLAYER, SpeedModeY)); // TODO /*engine->SetDefaultNamespace("SURFACE"); engine->RegisterEnum("Surface"); engine->RegisterEnumValue("Surface", "UNTEXTURED", jjLAYER::TextureSurfaceStyles::Untextured); engine->RegisterEnumValue("Surface", "LEGACY", jjLAYER::TextureSurfaceStyles::Legacy); engine->RegisterEnumValue("Surface", "FULLSCREEN", jjLAYER::TextureSurfaceStyles::FullScreen); engine->RegisterEnumValue("Surface", "INNERWINDOW", jjLAYER::TextureSurfaceStyles::InnerWindow); engine->RegisterEnumValue("Surface", "INNERLAYER", jjLAYER::TextureSurfaceStyles::InnerLayer); engine->SetDefaultNamespace(""); engine->RegisterObjectMethod("jjLAYER", "SURFACE::Surface get_textureSurface() const", asMETHOD(jjLAYER, GetTextureSurface), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void set_textureSurface(SURFACE::Surface)", asMETHOD(jjLAYER, SetTextureSurface), asCALL_THISCALL); engine->RegisterObjectType("jjTILE", sizeof(jjTILE), asOBJ_REF); engine->RegisterObjectBehaviour("jjTILE", asBEHAVE_ADDREF, "void f()", asMETHOD(jjTILE, addRef), asCALL_THISCALL); engine->RegisterObjectBehaviour("jjTILE", asBEHAVE_RELEASE, "void f()", asMETHOD(jjTILE, release), asCALL_THISCALL); engine->RegisterObjectMethod("jjTILE", "array@ getFrames() const", asMETHOD(jjTILE, getFrames), asCALL_THISCALL); engine->RegisterObjectMethod("jjTILE", "bool setFrames(const array &in frames, bool pingPong = false, uint16 wait = 0, uint16 randomWait = 0, uint16 pingPongWait = 0)", asMETHOD(jjTILE, setFrames), asCALL_THISCALL); engine->RegisterObjectMethod("jjTILE", "uint8 get_fps() const", asMETHOD(jjTILE, getFPS), asCALL_THISCALL); engine->RegisterObjectMethod("jjTILE", "void set_fps(uint8)", asMETHOD(jjTILE, setFPS), asCALL_THISCALL); engine->RegisterObjectMethod("jjTILE", "uint16 get_tileID() const", asMETHOD(jjTILE, getTileID), asCALL_THISCALL); engine->RegisterGlobalFunction("const jjTILE@ get_jjTiles(uint16)", asFUNCTION(jjTILE::getTile), asCALL_CDECL); engine->RegisterGlobalFunction("jjTILE@ get_jjAnimatedTiles(uint16)", asFUNCTION(jjTILE::getAnimatedTile), asCALL_CDECL);*/ engine->RegisterGlobalFunction("uint16 jjGetStaticTile(uint16 tileID)", asFUNCTION(jjGetStaticTile), asCALL_CDECL); engine->RegisterGlobalFunction("uint16 jjTileGet(uint8 layer, int xTile, int yTile)", asFUNCTION(jjTileGet), asCALL_CDECL); engine->RegisterGlobalFunction("uint16 jjTileSet(uint8 layer, int xTile, int yTile, uint16 newTile)", asFUNCTION(jjTileSet), asCALL_CDECL); engine->RegisterGlobalFunction("void jjGenerateSettableTileArea(uint8 layer, int xTile, int yTile, int width, int height)", asFUNCTION(jjGenerateSettableTileArea), asCALL_CDECL); engine->RegisterObjectMethod("jjLAYER", "uint16 tileGet(int xTile, int yTile) const", asMETHOD(jjLAYER, tileGet), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "uint16 tileSet(int xTile, int yTile, uint16 newTile)", asMETHOD(jjLAYER, tileSet), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void generateSettableTileArea(int xTile, int yTile, int width, int height)", asMETHOD(jjLAYER, generateSettableTileArea), asCALL_THISCALL); engine->RegisterObjectMethod("jjLAYER", "void generateSettableTileArea()", asMETHOD(jjLAYER, generateSettableTileAreaAll), asCALL_THISCALL); engine->RegisterGlobalFunction("bool jjMaskedPixel(int xPixel, int yPixel)", asFUNCTION(jjMaskedPixel), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjMaskedPixel(int xPixel, int yPixel, uint8 layer)", asFUNCTION(jjMaskedPixelLayer), asCALL_CDECL); //engine->RegisterObjectMethod("jjLAYER", "bool maskedPixel(int xPixel, int yPixel) const", asMETHOD(jjLAYER, CheckPixel), asCALL_THISCALL); engine->RegisterGlobalFunction("bool jjMaskedHLine(int xPixel, int lineLength, int yPixel)", asFUNCTION(jjMaskedHLine), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjMaskedHLine(int xPixel, int lineLength, int yPixel, uint8 layer)", asFUNCTION(jjMaskedHLineLayer), asCALL_CDECL); //engine->RegisterObjectMethod("jjLAYER", "bool maskedHLine(int xPixel, int lineLength, int yPixel) const", asMETHOD(jjLAYER, CheckHLine), asCALL_THISCALL); engine->RegisterGlobalFunction("bool jjMaskedVLine(int xPixel, int yPixel, int lineLength)", asFUNCTION(jjMaskedVLine), asCALL_CDECL); engine->RegisterGlobalFunction("bool jjMaskedVLine(int xPixel, int yPixel, int lineLength,uint8 layer)", asFUNCTION(jjMaskedVLineLayer), asCALL_CDECL); //engine->RegisterObjectMethod("jjLAYER", "bool maskedVLine(int xPixel, int yPixel, int lineLength) const", asMETHOD(jjLAYER, CheckVLineBool), asCALL_THISCALL); engine->RegisterGlobalFunction("int jjMaskedTopVLine(int xPixel, int yPixel, int lineLength)", asFUNCTION(jjMaskedTopVLine), asCALL_CDECL); engine->RegisterGlobalFunction("int jjMaskedTopVLine(int xPixel, int yPixel, int lineLength,uint8 layer)", asFUNCTION(jjMaskedTopVLineLayer), asCALL_CDECL); //engine->RegisterObjectMethod("jjLAYER", "int maskedTopVLine(int xPixel,int yPixel, int lineLength) const", asMETHOD(jjLAYER, CheckVLine), asCALL_THISCALL); //engine->RegisterGlobalProperty("uint8 jjEventAtLastMaskedPixel", tileAttr); engine->RegisterGlobalFunction("void jjSetModPosition(int order, int row, bool reset)", asFUNCTION(jjSetModPosition), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSlideModChannelVolume(int channel, float volume, int milliseconds)", asFUNCTION(jjSlideModChannelVolume), asCALL_CDECL); engine->RegisterGlobalFunction("int jjGetModOrder()", asFUNCTION(jjGetModOrder), asCALL_CDECL); engine->RegisterGlobalFunction("int jjGetModRow()", asFUNCTION(jjGetModRow), asCALL_CDECL); engine->RegisterGlobalFunction("int jjGetModTempo()", asFUNCTION(jjGetModTempo), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetModTempo(uint8 tempo)", asFUNCTION(jjSetModTempo), asCALL_CDECL); engine->RegisterGlobalFunction("int jjGetModSpeed()", asFUNCTION(jjGetModSpeed), asCALL_CDECL); engine->RegisterGlobalFunction("void jjSetModSpeed(uint8 speed)", asFUNCTION(jjSetModSpeed), asCALL_CDECL); // TODO engine->RegisterObjectType("jjPLAYERDRAW", sizeof(jjPLAYERDRAW), asOBJ_REF | asOBJ_NOCOUNT); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool name", asOFFSET(jjPLAYERDRAW, name)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool sprite", asOFFSET(jjPLAYERDRAW, sprite)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool sugarRush", asOFFSET(jjPLAYERDRAW, sugarRush)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool gunFlash", asOFFSET(jjPLAYERDRAW, gunFlash)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool invincibility", asOFFSET(jjPLAYERDRAW,invincibility)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool trail", asOFFSET(jjPLAYERDRAW, trail)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool morphingExplosions", asOFFSET(jjPLAYERDRAW, morphingExplosions)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool airboardBouncingMotion", asOFFSET(jjPLAYERDRAW, airboardBouncingMotion)); engine->RegisterObjectProperty("jjPLAYERDRAW", "bool airboardPuff", asOFFSET(jjPLAYERDRAW, airboardPuff)); engine->RegisterObjectProperty("jjPLAYERDRAW", "SPRITE::Mode spriteMode", asOFFSET(jjPLAYERDRAW, spriteMode)); engine->RegisterObjectProperty("jjPLAYERDRAW", "uint8 spriteParam", asOFFSET(jjPLAYERDRAW, spriteParam)); engine->RegisterObjectProperty("jjPLAYERDRAW", "LIGHT::Type lightType", asOFFSET(jjPLAYERDRAW, lightType)); engine->RegisterObjectProperty("jjPLAYERDRAW", "int8 light", asOFFSET(jjPLAYERDRAW, light)); engine->RegisterObjectProperty("jjPLAYERDRAW", "int layer", asOFFSET(jjPLAYERDRAW, layer)); engine->RegisterObjectProperty("jjPLAYERDRAW", "uint curFrame", asOFFSET(jjPLAYERDRAW, curFrame)); engine->RegisterObjectProperty("jjPLAYERDRAW", "int angle", asOFFSET(jjPLAYERDRAW, angle)); engine->RegisterObjectProperty("jjPLAYERDRAW", "float xOffset", asOFFSET(jjPLAYERDRAW, xOffset)); engine->RegisterObjectProperty("jjPLAYERDRAW", "float yOffset", asOFFSET(jjPLAYERDRAW, yOffset)); engine->RegisterObjectProperty("jjPLAYERDRAW", "float xScale", asOFFSET(jjPLAYERDRAW, xScale)); engine->RegisterObjectProperty("jjPLAYERDRAW", "float yScale", asOFFSET(jjPLAYERDRAW, yScale)); engine->RegisterObjectProperty("jjPLAYERDRAW", "TEAM::Color flag", asOFFSET(jjPLAYERDRAW, flag)); engine->RegisterObjectMethod("jjPLAYERDRAW", "bool get_shield(SHIELD::Shield) const", asMETHOD(jjPLAYERDRAW, get_shield), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYERDRAW", "bool set_shield(SHIELD::Shield, bool)", asMETHOD(jjPLAYERDRAW, set_shield), asCALL_THISCALL); engine->RegisterObjectMethod("jjPLAYERDRAW", "jjPLAYER@ get_player() const", asMETHOD(jjPLAYERDRAW, get_player), asCALL_THISCALL); engine->SetDefaultNamespace("STATE"); engine->RegisterEnumValue("State", "START", sSTART); engine->RegisterEnumValue("State", "SLEEP", sSLEEP); engine->RegisterEnumValue("State", "WAKE", sWAKE); engine->RegisterEnumValue("State", "KILL", sKILL); engine->RegisterEnumValue("State", "DEACTIVATE", sDEACTIVATE); engine->RegisterEnumValue("State", "WALK", sWALK); engine->RegisterEnumValue("State", "JUMP", sJUMP); engine->RegisterEnumValue("State", "FIRE", sFIRE); engine->RegisterEnumValue("State", "FLY", sFLY); engine->RegisterEnumValue("State", "BOUNCE", sBOUNCE); engine->RegisterEnumValue("State", "EXPLODE", sEXPLODE); engine->RegisterEnumValue("State", "ROCKETFLY", sROCKETFLY); engine->RegisterEnumValue("State", "STILL", sSTILL); engine->RegisterEnumValue("State", "FLOAT", sFLOAT); engine->RegisterEnumValue("State", "HIT", sHIT); engine->RegisterEnumValue("State", "SPRING", sSPRING); engine->RegisterEnumValue("State", "ACTION", sACTION); engine->RegisterEnumValue("State", "DONE", sDONE); engine->RegisterEnumValue("State", "PUSH", sPUSH); engine->RegisterEnumValue("State", "FALL", sFALL); engine->RegisterEnumValue("State", "FLOATFALL", sFLOATFALL); engine->RegisterEnumValue("State", "CIRCLE", sCIRCLE); engine->RegisterEnumValue("State", "ATTACK", sATTACK); engine->RegisterEnumValue("State", "FREEZE", sFREEZE); engine->RegisterEnumValue("State", "FADEIN", sFADEIN); engine->RegisterEnumValue("State", "FADEOUT", sFADEOUT); engine->RegisterEnumValue("State", "HIDE", sHIDE); engine->RegisterEnumValue("State", "TURN", sTURN); engine->RegisterEnumValue("State", "IDLE", sIDLE); engine->RegisterEnumValue("State", "EXTRA", sEXTRA); engine->RegisterEnumValue("State", "STOP", sSTOP); engine->RegisterEnumValue("State", "WAIT", sWAIT); engine->RegisterEnumValue("State", "LAND", sLAND); engine->RegisterEnumValue("State", "DELAYEDSTART", sDELAYEDSTART); engine->RegisterEnumValue("State", "ROTATE", sROTATE); engine->RegisterEnumValue("State", "DUCK", sDUCK); engine->SetDefaultNamespace("SOUND"); engine->RegisterEnumValue("Sample", "AMMO_BLUB1", sAMMO_BLUB1); engine->RegisterEnumValue("Sample", "AMMO_BLUB2", sAMMO_BLUB2); engine->RegisterEnumValue("Sample", "AMMO_BMP1", sAMMO_BMP1); engine->RegisterEnumValue("Sample", "AMMO_BMP2", sAMMO_BMP2); engine->RegisterEnumValue("Sample", "AMMO_BMP3", sAMMO_BMP3); engine->RegisterEnumValue("Sample", "AMMO_BMP4", sAMMO_BMP4); engine->RegisterEnumValue("Sample", "AMMO_BMP5", sAMMO_BMP5); engine->RegisterEnumValue("Sample", "AMMO_BMP6", sAMMO_BMP6); engine->RegisterEnumValue("Sample", "AMMO_BOEM1", sAMMO_BOEM1); engine->RegisterEnumValue("Sample", "AMMO_BUL1", sAMMO_BUL1); engine->RegisterEnumValue("Sample", "AMMO_BULFL1", sAMMO_BULFL1); engine->RegisterEnumValue("Sample", "AMMO_BULFL2", sAMMO_BULFL2); engine->RegisterEnumValue("Sample", "AMMO_BULFL3", sAMMO_BULFL3); engine->RegisterEnumValue("Sample", "AMMO_FIREGUN1A", sAMMO_FIREGUN1A); engine->RegisterEnumValue("Sample", "AMMO_FIREGUN2A", sAMMO_FIREGUN2A); engine->RegisterEnumValue("Sample", "AMMO_FUMP", sAMMO_FUMP); engine->RegisterEnumValue("Sample", "AMMO_GUN1", sAMMO_GUN1); engine->RegisterEnumValue("Sample", "AMMO_GUN2", sAMMO_GUN2); engine->RegisterEnumValue("Sample", "AMMO_GUN3PLOP", sAMMO_GUN3PLOP); engine->RegisterEnumValue("Sample", "AMMO_GUNFLP", sAMMO_GUNFLP); engine->RegisterEnumValue("Sample", "AMMO_GUNFLP1", sAMMO_GUNFLP1); engine->RegisterEnumValue("Sample", "AMMO_GUNFLP2", sAMMO_GUNFLP2); engine->RegisterEnumValue("Sample", "AMMO_GUNFLP3", sAMMO_GUNFLP3); engine->RegisterEnumValue("Sample", "AMMO_GUNFLP4", sAMMO_GUNFLP4); engine->RegisterEnumValue("Sample", "AMMO_GUNFLPL", sAMMO_GUNFLPL); engine->RegisterEnumValue("Sample", "AMMO_GUNJAZZ", sAMMO_GUNJAZZ); engine->RegisterEnumValue("Sample", "AMMO_GUNVELOCITY", sAMMO_GUNVELOCITY); engine->RegisterEnumValue("Sample", "AMMO_ICEGUN", sAMMO_ICEGUN); engine->RegisterEnumValue("Sample", "AMMO_ICEGUN2", sAMMO_ICEGUN2); engine->RegisterEnumValue("Sample", "AMMO_ICEGUNPU", sAMMO_ICEGUNPU); engine->RegisterEnumValue("Sample", "AMMO_ICEPU1", sAMMO_ICEPU1); engine->RegisterEnumValue("Sample", "AMMO_ICEPU2", sAMMO_ICEPU2); engine->RegisterEnumValue("Sample", "AMMO_ICEPU3", sAMMO_ICEPU3); engine->RegisterEnumValue("Sample", "AMMO_ICEPU4", sAMMO_ICEPU4); engine->RegisterEnumValue("Sample", "AMMO_LASER", sAMMO_LASER); engine->RegisterEnumValue("Sample", "AMMO_LASER2", sAMMO_LASER2); engine->RegisterEnumValue("Sample", "AMMO_LASER3", sAMMO_LASER3); engine->RegisterEnumValue("Sample", "AMMO_LAZRAYS", sAMMO_LAZRAYS); engine->RegisterEnumValue("Sample", "AMMO_MISSILE", sAMMO_MISSILE); engine->RegisterEnumValue("Sample", "AMMO_SPZBL1", sAMMO_SPZBL1); engine->RegisterEnumValue("Sample", "AMMO_SPZBL2", sAMMO_SPZBL2); engine->RegisterEnumValue("Sample", "AMMO_SPZBL3", sAMMO_SPZBL3); engine->RegisterEnumValue("Sample", "BAT_BATFLY1", sBAT_BATFLY1); engine->RegisterEnumValue("Sample", "BILSBOSS_BILLAPPEAR", sBILSBOSS_BILLAPPEAR); engine->RegisterEnumValue("Sample", "BILSBOSS_FINGERSNAP", sBILSBOSS_FINGERSNAP); engine->RegisterEnumValue("Sample", "BILSBOSS_FIRE", sBILSBOSS_FIRE); engine->RegisterEnumValue("Sample", "BILSBOSS_FIRESTART", sBILSBOSS_FIRESTART); engine->RegisterEnumValue("Sample", "BILSBOSS_SCARY3", sBILSBOSS_SCARY3); engine->RegisterEnumValue("Sample", "BILSBOSS_THUNDER", sBILSBOSS_THUNDER); engine->RegisterEnumValue("Sample", "BILSBOSS_ZIP", sBILSBOSS_ZIP); engine->RegisterEnumValue("Sample", "BONUS_BONUS1", sBONUS_BONUS1); engine->RegisterEnumValue("Sample", "BONUS_BONUSBLUB", sBONUS_BONUSBLUB); engine->RegisterEnumValue("Sample", "BUBBA_BUBBABOUNCE1", sBUBBA_BUBBABOUNCE1); engine->RegisterEnumValue("Sample", "BUBBA_BUBBABOUNCE2", sBUBBA_BUBBABOUNCE2); engine->RegisterEnumValue("Sample", "BUBBA_BUBBAEXPLO", sBUBBA_BUBBAEXPLO); engine->RegisterEnumValue("Sample", "BUBBA_FROG2", sBUBBA_FROG2); engine->RegisterEnumValue("Sample", "BUBBA_FROG3", sBUBBA_FROG3); engine->RegisterEnumValue("Sample", "BUBBA_FROG4", sBUBBA_FROG4); engine->RegisterEnumValue("Sample", "BUBBA_FROG5", sBUBBA_FROG5); engine->RegisterEnumValue("Sample", "BUBBA_SNEEZE2", sBUBBA_SNEEZE2); engine->RegisterEnumValue("Sample", "BUBBA_TORNADOATTACK2", sBUBBA_TORNADOATTACK2); engine->RegisterEnumValue("Sample", "BUMBEE_BEELOOP", sBUMBEE_BEELOOP); engine->RegisterEnumValue("Sample", "CATERPIL_RIDOE", sCATERPIL_RIDOE); engine->RegisterEnumValue("Sample", "COMMON_AIRBOARD", sCOMMON_AIRBOARD); engine->RegisterEnumValue("Sample", "COMMON_AIRBTURN", sCOMMON_AIRBTURN); engine->RegisterEnumValue("Sample", "COMMON_AIRBTURN2", sCOMMON_AIRBTURN2); engine->RegisterEnumValue("Sample", "COMMON_BASE1", sCOMMON_BASE1); engine->RegisterEnumValue("Sample", "COMMON_BELL_FIRE", sCOMMON_BELL_FIRE); engine->RegisterEnumValue("Sample", "COMMON_BELL_FIRE2", sCOMMON_BELL_FIRE2); engine->RegisterEnumValue("Sample", "COMMON_BENZIN1", sCOMMON_BENZIN1); engine->RegisterEnumValue("Sample", "COMMON_BIRDFLY", sCOMMON_BIRDFLY); engine->RegisterEnumValue("Sample", "COMMON_BIRDFLY2", sCOMMON_BIRDFLY2); engine->RegisterEnumValue("Sample", "COMMON_BLOKPLOP", sCOMMON_BLOKPLOP); engine->RegisterEnumValue("Sample", "COMMON_BLUB1", sCOMMON_BLUB1); engine->RegisterEnumValue("Sample", "COMMON_BUBBLGN1", sCOMMON_BUBBLGN1); engine->RegisterEnumValue("Sample", "COMMON_BURN", sCOMMON_BURN); engine->RegisterEnumValue("Sample", "COMMON_BURNIN", sCOMMON_BURNIN); engine->RegisterEnumValue("Sample", "COMMON_CANSPS", sCOMMON_CANSPS); engine->RegisterEnumValue("Sample", "COMMON_CLOCK", sCOMMON_CLOCK); engine->RegisterEnumValue("Sample", "COMMON_COIN", sCOMMON_COIN); engine->RegisterEnumValue("Sample", "COMMON_COLLAPS", sCOMMON_COLLAPS); engine->RegisterEnumValue("Sample", "COMMON_CUP", sCOMMON_CUP); engine->RegisterEnumValue("Sample", "COMMON_DAMPED1", sCOMMON_DAMPED1); engine->RegisterEnumValue("Sample", "COMMON_DOWN", sCOMMON_DOWN); engine->RegisterEnumValue("Sample", "COMMON_DOWNFL2", sCOMMON_DOWNFL2); engine->RegisterEnumValue("Sample", "COMMON_DRINKSPAZZ1", sCOMMON_DRINKSPAZZ1); engine->RegisterEnumValue("Sample", "COMMON_DRINKSPAZZ2", sCOMMON_DRINKSPAZZ2); engine->RegisterEnumValue("Sample", "COMMON_DRINKSPAZZ3", sCOMMON_DRINKSPAZZ3); engine->RegisterEnumValue("Sample", "COMMON_DRINKSPAZZ4", sCOMMON_DRINKSPAZZ4); engine->RegisterEnumValue("Sample", "COMMON_EAT1", sCOMMON_EAT1); engine->RegisterEnumValue("Sample", "COMMON_EAT2", sCOMMON_EAT2); engine->RegisterEnumValue("Sample", "COMMON_EAT3", sCOMMON_EAT3); engine->RegisterEnumValue("Sample", "COMMON_EAT4", sCOMMON_EAT4); engine->RegisterEnumValue("Sample", "COMMON_ELECTRIC1", sCOMMON_ELECTRIC1); engine->RegisterEnumValue("Sample", "COMMON_ELECTRIC2", sCOMMON_ELECTRIC2); engine->RegisterEnumValue("Sample", "COMMON_ELECTRICHIT", sCOMMON_ELECTRICHIT); engine->RegisterEnumValue("Sample", "COMMON_EXPL_TNT", sCOMMON_EXPL_TNT); engine->RegisterEnumValue("Sample", "COMMON_EXPSM1", sCOMMON_EXPSM1); engine->RegisterEnumValue("Sample", "COMMON_FLAMER", sCOMMON_FLAMER); engine->RegisterEnumValue("Sample", "COMMON_FLAP", sCOMMON_FLAP); engine->RegisterEnumValue("Sample", "COMMON_FOEW1", sCOMMON_FOEW1); engine->RegisterEnumValue("Sample", "COMMON_FOEW2", sCOMMON_FOEW2); engine->RegisterEnumValue("Sample", "COMMON_FOEW3", sCOMMON_FOEW3); engine->RegisterEnumValue("Sample", "COMMON_FOEW4", sCOMMON_FOEW4); engine->RegisterEnumValue("Sample", "COMMON_FOEW5", sCOMMON_FOEW5); engine->RegisterEnumValue("Sample", "COMMON_GEMSMSH1", sCOMMON_GEMSMSH1); engine->RegisterEnumValue("Sample", "COMMON_GLASS2", sCOMMON_GLASS2); engine->RegisterEnumValue("Sample", "COMMON_GUNSM1", sCOMMON_GUNSM1); engine->RegisterEnumValue("Sample", "COMMON_HARP1", sCOMMON_HARP1); engine->RegisterEnumValue("Sample", "COMMON_HEAD", sCOMMON_HEAD); engine->RegisterEnumValue("Sample", "COMMON_HELI1", sCOMMON_HELI1); engine->RegisterEnumValue("Sample", "COMMON_HIBELL", sCOMMON_HIBELL); engine->RegisterEnumValue("Sample", "COMMON_HOLYFLUT", sCOMMON_HOLYFLUT); engine->RegisterEnumValue("Sample", "COMMON_HORN1", sCOMMON_HORN1); engine->RegisterEnumValue("Sample", "COMMON_ICECRUSH", sCOMMON_ICECRUSH); engine->RegisterEnumValue("Sample", "COMMON_IMPACT1", sCOMMON_IMPACT1); engine->RegisterEnumValue("Sample", "COMMON_IMPACT2", sCOMMON_IMPACT2); engine->RegisterEnumValue("Sample", "COMMON_IMPACT3", sCOMMON_IMPACT3); engine->RegisterEnumValue("Sample", "COMMON_IMPACT4", sCOMMON_IMPACT4); engine->RegisterEnumValue("Sample", "COMMON_IMPACT5", sCOMMON_IMPACT5); engine->RegisterEnumValue("Sample", "COMMON_IMPACT6", sCOMMON_IMPACT6); engine->RegisterEnumValue("Sample", "COMMON_IMPACT7", sCOMMON_IMPACT7); engine->RegisterEnumValue("Sample", "COMMON_IMPACT8", sCOMMON_IMPACT8); engine->RegisterEnumValue("Sample", "COMMON_IMPACT9", sCOMMON_IMPACT9); engine->RegisterEnumValue("Sample", "COMMON_ITEMTRE", sCOMMON_ITEMTRE); engine->RegisterEnumValue("Sample", "COMMON_JUMP", sCOMMON_JUMP); engine->RegisterEnumValue("Sample", "COMMON_JUMP2", sCOMMON_JUMP2); engine->RegisterEnumValue("Sample", "COMMON_LAND", sCOMMON_LAND); engine->RegisterEnumValue("Sample", "COMMON_LAND1", sCOMMON_LAND1); engine->RegisterEnumValue("Sample", "COMMON_LAND2", sCOMMON_LAND2); engine->RegisterEnumValue("Sample", "COMMON_LANDCAN1", sCOMMON_LANDCAN1); engine->RegisterEnumValue("Sample", "COMMON_LANDCAN2", sCOMMON_LANDCAN2); engine->RegisterEnumValue("Sample", "COMMON_LANDPOP", sCOMMON_LANDPOP); engine->RegisterEnumValue("Sample", "COMMON_LOADJAZZ", sCOMMON_LOADJAZZ); engine->RegisterEnumValue("Sample", "COMMON_LOADSPAZ", sCOMMON_LOADSPAZ); engine->RegisterEnumValue("Sample", "COMMON_METALHIT", sCOMMON_METALHIT); engine->RegisterEnumValue("Sample", "COMMON_MONITOR", sCOMMON_MONITOR); engine->RegisterEnumValue("Sample", "COMMON_NOCOIN", sCOMMON_NOCOIN); engine->RegisterEnumValue("Sample", "COMMON_PICKUP1", sCOMMON_PICKUP1); engine->RegisterEnumValue("Sample", "COMMON_PICKUPW1", sCOMMON_PICKUPW1); engine->RegisterEnumValue("Sample", "COMMON_PISTOL1", sCOMMON_PISTOL1); engine->RegisterEnumValue("Sample", "COMMON_PLOOP1", sCOMMON_PLOOP1); engine->RegisterEnumValue("Sample", "COMMON_PLOP1", sCOMMON_PLOP1); engine->RegisterEnumValue("Sample", "COMMON_PLOP2", sCOMMON_PLOP2); engine->RegisterEnumValue("Sample", "COMMON_PLOP3", sCOMMON_PLOP3); engine->RegisterEnumValue("Sample", "COMMON_PLOP4", sCOMMON_PLOP4); engine->RegisterEnumValue("Sample", "COMMON_PLOPKORK", sCOMMON_PLOPKORK); engine->RegisterEnumValue("Sample", "COMMON_PREEXPL1", sCOMMON_PREEXPL1); engine->RegisterEnumValue("Sample", "COMMON_PREHELI", sCOMMON_PREHELI); engine->RegisterEnumValue("Sample", "COMMON_REVUP", sCOMMON_REVUP); engine->RegisterEnumValue("Sample", "COMMON_RINGGUN", sCOMMON_RINGGUN); engine->RegisterEnumValue("Sample", "COMMON_RINGGUN2", sCOMMON_RINGGUN2); engine->RegisterEnumValue("Sample", "COMMON_SHIELD1", sCOMMON_SHIELD1); engine->RegisterEnumValue("Sample", "COMMON_SHIELD4", sCOMMON_SHIELD4); engine->RegisterEnumValue("Sample", "COMMON_SHIELD_ELEC", sCOMMON_SHIELD_ELEC); engine->RegisterEnumValue("Sample", "COMMON_SHLDOF3", sCOMMON_SHLDOF3); engine->RegisterEnumValue("Sample", "COMMON_SLIP", sCOMMON_SLIP); engine->RegisterEnumValue("Sample", "COMMON_SMASH", sCOMMON_SMASH); engine->RegisterEnumValue("Sample", "COMMON_SPLAT1", sCOMMON_SPLAT1); engine->RegisterEnumValue("Sample", "COMMON_SPLAT2", sCOMMON_SPLAT2); engine->RegisterEnumValue("Sample", "COMMON_SPLAT3", sCOMMON_SPLAT3); engine->RegisterEnumValue("Sample", "COMMON_SPLAT4", sCOMMON_SPLAT4); engine->RegisterEnumValue("Sample", "COMMON_SPLUT", sCOMMON_SPLUT); engine->RegisterEnumValue("Sample", "COMMON_SPRING1", sCOMMON_SPRING1); engine->RegisterEnumValue("Sample", "COMMON_STEAM", sCOMMON_STEAM); engine->RegisterEnumValue("Sample", "COMMON_STEP", sCOMMON_STEP); engine->RegisterEnumValue("Sample", "COMMON_STRETCH", sCOMMON_STRETCH); engine->RegisterEnumValue("Sample", "COMMON_SWISH1", sCOMMON_SWISH1); engine->RegisterEnumValue("Sample", "COMMON_SWISH2", sCOMMON_SWISH2); engine->RegisterEnumValue("Sample", "COMMON_SWISH3", sCOMMON_SWISH3); engine->RegisterEnumValue("Sample", "COMMON_SWISH4", sCOMMON_SWISH4); engine->RegisterEnumValue("Sample", "COMMON_SWISH5", sCOMMON_SWISH5); engine->RegisterEnumValue("Sample", "COMMON_SWISH6", sCOMMON_SWISH6); engine->RegisterEnumValue("Sample", "COMMON_SWISH7", sCOMMON_SWISH7); engine->RegisterEnumValue("Sample", "COMMON_SWISH8", sCOMMON_SWISH8); engine->RegisterEnumValue("Sample", "COMMON_TELPORT1", sCOMMON_TELPORT1); engine->RegisterEnumValue("Sample", "COMMON_TELPORT2", sCOMMON_TELPORT2); engine->RegisterEnumValue("Sample", "COMMON_UP", sCOMMON_UP); engine->RegisterEnumValue("Sample", "COMMON_WATER", sCOMMON_WATER); engine->RegisterEnumValue("Sample", "COMMON_WOOD1", sCOMMON_WOOD1); engine->RegisterEnumValue("Sample", "DEMON_RUN", sDEMON_RUN); engine->RegisterEnumValue("Sample", "DEVILDEVAN_DRAGONFIRE", sDEVILDEVAN_DRAGONFIRE); engine->RegisterEnumValue("Sample", "DEVILDEVAN_FLAP", sDEVILDEVAN_FLAP); engine->RegisterEnumValue("Sample", "DEVILDEVAN_FROG4", sDEVILDEVAN_FROG4); engine->RegisterEnumValue("Sample", "DEVILDEVAN_JUMPUP", sDEVILDEVAN_JUMPUP); engine->RegisterEnumValue("Sample", "DEVILDEVAN_LAUGH", sDEVILDEVAN_LAUGH); engine->RegisterEnumValue("Sample", "DEVILDEVAN_PHASER2", sDEVILDEVAN_PHASER2); engine->RegisterEnumValue("Sample", "DEVILDEVAN_STRECH2", sDEVILDEVAN_STRECH2); engine->RegisterEnumValue("Sample", "DEVILDEVAN_STRECHTAIL", sDEVILDEVAN_STRECHTAIL); engine->RegisterEnumValue("Sample", "DEVILDEVAN_STRETCH1", sDEVILDEVAN_STRETCH1); engine->RegisterEnumValue("Sample", "DEVILDEVAN_STRETCH3", sDEVILDEVAN_STRETCH3); engine->RegisterEnumValue("Sample", "DEVILDEVAN_VANISH1", sDEVILDEVAN_VANISH1); engine->RegisterEnumValue("Sample", "DEVILDEVAN_WHISTLEDESCENDING2", sDEVILDEVAN_WHISTLEDESCENDING2); engine->RegisterEnumValue("Sample", "DEVILDEVAN_WINGSOUT", sDEVILDEVAN_WINGSOUT); engine->RegisterEnumValue("Sample", "DOG_AGRESSIV", sDOG_AGRESSIV); engine->RegisterEnumValue("Sample", "DOG_SNIF1", sDOG_SNIF1); engine->RegisterEnumValue("Sample", "DOG_WAF1", sDOG_WAF1); engine->RegisterEnumValue("Sample", "DOG_WAF2", sDOG_WAF2); engine->RegisterEnumValue("Sample", "DOG_WAF3", sDOG_WAF3); engine->RegisterEnumValue("Sample", "DRAGFLY_BEELOOP", sDRAGFLY_BEELOOP); engine->RegisterEnumValue("Sample", "ENDING_OHTHANK", sENDING_OHTHANK); engine->RegisterEnumValue("Sample", "ENDTUNEJAZZ_TUNE", sENDTUNEJAZZ_TUNE); engine->RegisterEnumValue("Sample", "ENDTUNELORI_CAKE", sENDTUNELORI_CAKE); engine->RegisterEnumValue("Sample", "ENDTUNESPAZ_TUNE", sENDTUNESPAZ_TUNE); engine->RegisterEnumValue("Sample", "EPICLOGO_EPIC1", sEPICLOGO_EPIC1); engine->RegisterEnumValue("Sample", "EPICLOGO_EPIC2", sEPICLOGO_EPIC2); engine->RegisterEnumValue("Sample", "EVA_KISS1", sEVA_KISS1); engine->RegisterEnumValue("Sample", "EVA_KISS2", sEVA_KISS2); engine->RegisterEnumValue("Sample", "EVA_KISS3", sEVA_KISS3); engine->RegisterEnumValue("Sample", "EVA_KISS4", sEVA_KISS4); engine->RegisterEnumValue("Sample", "FAN_FAN", sFAN_FAN); engine->RegisterEnumValue("Sample", "FATCHK_HIT1", sFATCHK_HIT1); engine->RegisterEnumValue("Sample", "FATCHK_HIT2", sFATCHK_HIT2); engine->RegisterEnumValue("Sample", "FATCHK_HIT3", sFATCHK_HIT3); engine->RegisterEnumValue("Sample", "FENCER_FENCE1", sFENCER_FENCE1); engine->RegisterEnumValue("Sample", "FROG_FROG", sFROG_FROG); engine->RegisterEnumValue("Sample", "FROG_FROG1", sFROG_FROG1); engine->RegisterEnumValue("Sample", "FROG_FROG2", sFROG_FROG2); engine->RegisterEnumValue("Sample", "FROG_FROG3", sFROG_FROG3); engine->RegisterEnumValue("Sample", "FROG_FROG4", sFROG_FROG4); engine->RegisterEnumValue("Sample", "FROG_FROG5", sFROG_FROG5); engine->RegisterEnumValue("Sample", "FROG_JAZZ2FROG", sFROG_JAZZ2FROG); engine->RegisterEnumValue("Sample", "FROG_TONG", sFROG_TONG); engine->RegisterEnumValue("Sample", "GLOVE_HIT", sGLOVE_HIT); engine->RegisterEnumValue("Sample", "HATTER_CUP", sHATTER_CUP); engine->RegisterEnumValue("Sample", "HATTER_HAT", sHATTER_HAT); engine->RegisterEnumValue("Sample", "HATTER_PTOEI", sHATTER_PTOEI); engine->RegisterEnumValue("Sample", "HATTER_SPLIN", sHATTER_SPLIN); engine->RegisterEnumValue("Sample", "HATTER_SPLOUT", sHATTER_SPLOUT); engine->RegisterEnumValue("Sample", "INTRO_BLOW", sINTRO_BLOW); engine->RegisterEnumValue("Sample", "INTRO_BOEM1", sINTRO_BOEM1); engine->RegisterEnumValue("Sample", "INTRO_BOEM2", sINTRO_BOEM2); engine->RegisterEnumValue("Sample", "INTRO_BRAKE", sINTRO_BRAKE); engine->RegisterEnumValue("Sample", "INTRO_END", sINTRO_END); engine->RegisterEnumValue("Sample", "INTRO_GRAB", sINTRO_GRAB); engine->RegisterEnumValue("Sample", "INTRO_GREN1", sINTRO_GREN1); engine->RegisterEnumValue("Sample", "INTRO_GREN2", sINTRO_GREN2); engine->RegisterEnumValue("Sample", "INTRO_GREN3", sINTRO_GREN3); engine->RegisterEnumValue("Sample", "INTRO_GUNM0", sINTRO_GUNM0); engine->RegisterEnumValue("Sample", "INTRO_GUNM1", sINTRO_GUNM1); engine->RegisterEnumValue("Sample", "INTRO_GUNM2", sINTRO_GUNM2); engine->RegisterEnumValue("Sample", "INTRO_HELI", sINTRO_HELI); engine->RegisterEnumValue("Sample", "INTRO_HITSPAZ", sINTRO_HITSPAZ); engine->RegisterEnumValue("Sample", "INTRO_HITTURT", sINTRO_HITTURT); engine->RegisterEnumValue("Sample", "INTRO_IFEEL", sINTRO_IFEEL); engine->RegisterEnumValue("Sample", "INTRO_INHALE", sINTRO_INHALE); engine->RegisterEnumValue("Sample", "INTRO_INSECT", sINTRO_INSECT); engine->RegisterEnumValue("Sample", "INTRO_KATROL", sINTRO_KATROL); engine->RegisterEnumValue("Sample", "INTRO_LAND", sINTRO_LAND); engine->RegisterEnumValue("Sample", "INTRO_MONSTER", sINTRO_MONSTER); engine->RegisterEnumValue("Sample", "INTRO_MONSTER2", sINTRO_MONSTER2); engine->RegisterEnumValue("Sample", "INTRO_ROCK", sINTRO_ROCK); engine->RegisterEnumValue("Sample", "INTRO_ROPE1", sINTRO_ROPE1); engine->RegisterEnumValue("Sample", "INTRO_ROPE2", sINTRO_ROPE2); engine->RegisterEnumValue("Sample", "INTRO_RUN", sINTRO_RUN); engine->RegisterEnumValue("Sample", "INTRO_SHOT1", sINTRO_SHOT1); engine->RegisterEnumValue("Sample", "INTRO_SHOTGRN", sINTRO_SHOTGRN); engine->RegisterEnumValue("Sample", "INTRO_SKI", sINTRO_SKI); engine->RegisterEnumValue("Sample", "INTRO_STRING", sINTRO_STRING); engine->RegisterEnumValue("Sample", "INTRO_SWISH1", sINTRO_SWISH1); engine->RegisterEnumValue("Sample", "INTRO_SWISH2", sINTRO_SWISH2); engine->RegisterEnumValue("Sample", "INTRO_SWISH3", sINTRO_SWISH3); engine->RegisterEnumValue("Sample", "INTRO_SWISH4", sINTRO_SWISH4); engine->RegisterEnumValue("Sample", "INTRO_UHTURT", sINTRO_UHTURT); engine->RegisterEnumValue("Sample", "INTRO_UP1", sINTRO_UP1); engine->RegisterEnumValue("Sample", "INTRO_UP2", sINTRO_UP2); engine->RegisterEnumValue("Sample", "INTRO_WIND_01", sINTRO_WIND_01); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_BALANCE", sJAZZSOUNDS_BALANCE); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_HEY1", sJAZZSOUNDS_HEY1); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_HEY2", sJAZZSOUNDS_HEY2); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_HEY3", sJAZZSOUNDS_HEY3); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_HEY4", sJAZZSOUNDS_HEY4); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_IDLE", sJAZZSOUNDS_IDLE); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_JAZZV1", sJAZZSOUNDS_JAZZV1); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_JAZZV2", sJAZZSOUNDS_JAZZV2); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_JAZZV3", sJAZZSOUNDS_JAZZV3); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_JAZZV4", sJAZZSOUNDS_JAZZV4); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_JUMMY", sJAZZSOUNDS_JUMMY); engine->RegisterEnumValue("Sample", "JAZZSOUNDS_PFOE", sJAZZSOUNDS_PFOE); engine->RegisterEnumValue("Sample", "LABRAT_BITE", sLABRAT_BITE); engine->RegisterEnumValue("Sample", "LABRAT_EYE2", sLABRAT_EYE2); engine->RegisterEnumValue("Sample", "LABRAT_EYE3", sLABRAT_EYE3); engine->RegisterEnumValue("Sample", "LABRAT_MOUSE1", sLABRAT_MOUSE1); engine->RegisterEnumValue("Sample", "LABRAT_MOUSE2", sLABRAT_MOUSE2); engine->RegisterEnumValue("Sample", "LABRAT_MOUSE3", sLABRAT_MOUSE3); engine->RegisterEnumValue("Sample", "LIZARD_LIZ1", sLIZARD_LIZ1); engine->RegisterEnumValue("Sample", "LIZARD_LIZ2", sLIZARD_LIZ2); engine->RegisterEnumValue("Sample", "LIZARD_LIZ4", sLIZARD_LIZ4); engine->RegisterEnumValue("Sample", "LIZARD_LIZ6", sLIZARD_LIZ6); engine->RegisterEnumValue("Sample", "LORISOUNDS_DIE1", sLORISOUNDS_DIE1); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT0", sLORISOUNDS_HURT0); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT1", sLORISOUNDS_HURT1); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT2", sLORISOUNDS_HURT2); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT3", sLORISOUNDS_HURT3); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT4", sLORISOUNDS_HURT4); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT5", sLORISOUNDS_HURT5); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT6", sLORISOUNDS_HURT6); engine->RegisterEnumValue("Sample", "LORISOUNDS_HURT7", sLORISOUNDS_HURT7); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORI1", sLORISOUNDS_LORI1); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORI2", sLORISOUNDS_LORI2); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORIBOOM", sLORISOUNDS_LORIBOOM); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORIFALL", sLORISOUNDS_LORIFALL); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORIJUMP", sLORISOUNDS_LORIJUMP); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORIJUMP2", sLORISOUNDS_LORIJUMP2); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORIJUMP3", sLORISOUNDS_LORIJUMP3); engine->RegisterEnumValue("Sample", "LORISOUNDS_LORIJUMP4", sLORISOUNDS_LORIJUMP4); engine->RegisterEnumValue("Sample", "LORISOUNDS_TOUCH", sLORISOUNDS_TOUCH); engine->RegisterEnumValue("Sample", "LORISOUNDS_WEHOO", sLORISOUNDS_WEHOO); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT0", sMENUSOUNDS_SELECT0); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT1", sMENUSOUNDS_SELECT1); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT2", sMENUSOUNDS_SELECT2); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT3", sMENUSOUNDS_SELECT3); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT4", sMENUSOUNDS_SELECT4); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT5", sMENUSOUNDS_SELECT5); engine->RegisterEnumValue("Sample", "MENUSOUNDS_SELECT6", sMENUSOUNDS_SELECT6); engine->RegisterEnumValue("Sample", "MENUSOUNDS_TYPE", sMENUSOUNDS_TYPE); engine->RegisterEnumValue("Sample", "MENUSOUNDS_TYPEENTER", sMENUSOUNDS_TYPEENTER); engine->RegisterEnumValue("Sample", "MONKEY_SPLUT", sMONKEY_SPLUT); engine->RegisterEnumValue("Sample", "MONKEY_THROW", sMONKEY_THROW); engine->RegisterEnumValue("Sample", "MOTH_FLAPMOTH", sMOTH_FLAPMOTH); engine->RegisterEnumValue("Sample", "ORANGE_BOEML", sORANGE_BOEML); engine->RegisterEnumValue("Sample", "ORANGE_BOEMR", sORANGE_BOEMR); engine->RegisterEnumValue("Sample", "ORANGE_BUBBELSL", sORANGE_BUBBELSL); engine->RegisterEnumValue("Sample", "ORANGE_BUBBELSR", sORANGE_BUBBELSR); engine->RegisterEnumValue("Sample", "ORANGE_GLAS1L", sORANGE_GLAS1L); engine->RegisterEnumValue("Sample", "ORANGE_GLAS1R", sORANGE_GLAS1R); engine->RegisterEnumValue("Sample", "ORANGE_GLAS2L", sORANGE_GLAS2L); engine->RegisterEnumValue("Sample", "ORANGE_GLAS2R", sORANGE_GLAS2R); engine->RegisterEnumValue("Sample", "ORANGE_MERGE", sORANGE_MERGE); engine->RegisterEnumValue("Sample", "ORANGE_SWEEP0L", sORANGE_SWEEP0L); engine->RegisterEnumValue("Sample", "ORANGE_SWEEP0R", sORANGE_SWEEP0R); engine->RegisterEnumValue("Sample", "ORANGE_SWEEP1L", sORANGE_SWEEP1L); engine->RegisterEnumValue("Sample", "ORANGE_SWEEP1R", sORANGE_SWEEP1R); engine->RegisterEnumValue("Sample", "ORANGE_SWEEP2L", sORANGE_SWEEP2L); engine->RegisterEnumValue("Sample", "ORANGE_SWEEP2R", sORANGE_SWEEP2R); engine->RegisterEnumValue("Sample", "P2_CRUNCH", sP2_CRUNCH); engine->RegisterEnumValue("Sample", "P2_FART", sP2_FART); engine->RegisterEnumValue("Sample", "P2_FOEW1", sP2_FOEW1); engine->RegisterEnumValue("Sample", "P2_FOEW4", sP2_FOEW4); engine->RegisterEnumValue("Sample", "P2_FOEW5", sP2_FOEW5); engine->RegisterEnumValue("Sample", "P2_FROG1", sP2_FROG1); engine->RegisterEnumValue("Sample", "P2_FROG2", sP2_FROG2); engine->RegisterEnumValue("Sample", "P2_FROG3", sP2_FROG3); engine->RegisterEnumValue("Sample", "P2_FROG4", sP2_FROG4); engine->RegisterEnumValue("Sample", "P2_FROG5", sP2_FROG5); engine->RegisterEnumValue("Sample", "P2_KISS4", sP2_KISS4); engine->RegisterEnumValue("Sample", "P2_OPEN", sP2_OPEN); engine->RegisterEnumValue("Sample", "P2_PINCH1", sP2_PINCH1); engine->RegisterEnumValue("Sample", "P2_PINCH2", sP2_PINCH2); engine->RegisterEnumValue("Sample", "P2_PLOPSEQ1", sP2_PLOPSEQ1); engine->RegisterEnumValue("Sample", "P2_PLOPSEQ2", sP2_PLOPSEQ2); engine->RegisterEnumValue("Sample", "P2_PLOPSEQ3", sP2_PLOPSEQ3); engine->RegisterEnumValue("Sample", "P2_PLOPSEQ4", sP2_PLOPSEQ4); engine->RegisterEnumValue("Sample", "P2_POEP", sP2_POEP); engine->RegisterEnumValue("Sample", "P2_PTOEI", sP2_PTOEI); engine->RegisterEnumValue("Sample", "P2_SPLOUT", sP2_SPLOUT); engine->RegisterEnumValue("Sample", "P2_SPLUT", sP2_SPLUT); engine->RegisterEnumValue("Sample", "P2_THROW", sP2_THROW); engine->RegisterEnumValue("Sample", "P2_TONG", sP2_TONG); engine->RegisterEnumValue("Sample", "PICKUPS_BOING_CHECK", sPICKUPS_BOING_CHECK); engine->RegisterEnumValue("Sample", "PICKUPS_HELI2", sPICKUPS_HELI2); engine->RegisterEnumValue("Sample", "PICKUPS_STRETCH1A", sPICKUPS_STRETCH1A); engine->RegisterEnumValue("Sample", "PINBALL_BELL", sPINBALL_BELL); engine->RegisterEnumValue("Sample", "PINBALL_FLIP1", sPINBALL_FLIP1); engine->RegisterEnumValue("Sample", "PINBALL_FLIP2", sPINBALL_FLIP2); engine->RegisterEnumValue("Sample", "PINBALL_FLIP3", sPINBALL_FLIP3); engine->RegisterEnumValue("Sample", "PINBALL_FLIP4", sPINBALL_FLIP4); engine->RegisterEnumValue("Sample", "QUEEN_LADYUP", sQUEEN_LADYUP); engine->RegisterEnumValue("Sample", "QUEEN_SCREAM", sQUEEN_SCREAM); engine->RegisterEnumValue("Sample", "RAPIER_GOSTDIE", sRAPIER_GOSTDIE); engine->RegisterEnumValue("Sample", "RAPIER_GOSTLOOP", sRAPIER_GOSTLOOP); engine->RegisterEnumValue("Sample", "RAPIER_GOSTOOOH", sRAPIER_GOSTOOOH); engine->RegisterEnumValue("Sample", "RAPIER_GOSTRIP", sRAPIER_GOSTRIP); engine->RegisterEnumValue("Sample", "RAPIER_HITCHAR", sRAPIER_HITCHAR); engine->RegisterEnumValue("Sample", "ROBOT_BIG1", sROBOT_BIG1); engine->RegisterEnumValue("Sample", "ROBOT_BIG2", sROBOT_BIG2); engine->RegisterEnumValue("Sample", "ROBOT_CAN1", sROBOT_CAN1); engine->RegisterEnumValue("Sample", "ROBOT_CAN2", sROBOT_CAN2); engine->RegisterEnumValue("Sample", "ROBOT_HYDRO", sROBOT_HYDRO); engine->RegisterEnumValue("Sample", "ROBOT_HYDRO2", sROBOT_HYDRO2); engine->RegisterEnumValue("Sample", "ROBOT_HYDROFIL", sROBOT_HYDROFIL); engine->RegisterEnumValue("Sample", "ROBOT_HYDROPUF", sROBOT_HYDROPUF); engine->RegisterEnumValue("Sample", "ROBOT_IDLE1", sROBOT_IDLE1); engine->RegisterEnumValue("Sample", "ROBOT_IDLE2", sROBOT_IDLE2); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN1", sROBOT_JMPCAN1); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN10", sROBOT_JMPCAN10); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN2", sROBOT_JMPCAN2); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN3", sROBOT_JMPCAN3); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN4", sROBOT_JMPCAN4); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN5", sROBOT_JMPCAN5); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN6", sROBOT_JMPCAN6); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN7", sROBOT_JMPCAN7); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN8", sROBOT_JMPCAN8); engine->RegisterEnumValue("Sample", "ROBOT_JMPCAN9", sROBOT_JMPCAN9); engine->RegisterEnumValue("Sample", "ROBOT_METAL1", sROBOT_METAL1); engine->RegisterEnumValue("Sample", "ROBOT_METAL2", sROBOT_METAL2); engine->RegisterEnumValue("Sample", "ROBOT_METAL3", sROBOT_METAL3); engine->RegisterEnumValue("Sample", "ROBOT_METAL4", sROBOT_METAL4); engine->RegisterEnumValue("Sample", "ROBOT_METAL5", sROBOT_METAL5); engine->RegisterEnumValue("Sample", "ROBOT_OPEN", sROBOT_OPEN); engine->RegisterEnumValue("Sample", "ROBOT_OUT", sROBOT_OUT); engine->RegisterEnumValue("Sample", "ROBOT_POEP", sROBOT_POEP); engine->RegisterEnumValue("Sample", "ROBOT_POLE", sROBOT_POLE); engine->RegisterEnumValue("Sample", "ROBOT_SHOOT", sROBOT_SHOOT); engine->RegisterEnumValue("Sample", "ROBOT_STEP1", sROBOT_STEP1); engine->RegisterEnumValue("Sample", "ROBOT_STEP2", sROBOT_STEP2); engine->RegisterEnumValue("Sample", "ROBOT_STEP3", sROBOT_STEP3); engine->RegisterEnumValue("Sample", "ROCK_ROCK1", sROCK_ROCK1); engine->RegisterEnumValue("Sample", "RUSH_RUSH", sRUSH_RUSH); engine->RegisterEnumValue("Sample", "SCIENCE_PLOPKAOS", sSCIENCE_PLOPKAOS); engine->RegisterEnumValue("Sample", "SKELETON_BONE1", sSKELETON_BONE1); engine->RegisterEnumValue("Sample", "SKELETON_BONE2", sSKELETON_BONE2); engine->RegisterEnumValue("Sample", "SKELETON_BONE3", sSKELETON_BONE3); engine->RegisterEnumValue("Sample", "SKELETON_BONE5", sSKELETON_BONE5); engine->RegisterEnumValue("Sample", "SKELETON_BONE6", sSKELETON_BONE6); engine->RegisterEnumValue("Sample", "SKELETON_BONE7", sSKELETON_BONE7); engine->RegisterEnumValue("Sample", "SMALTREE_FALL", sSMALTREE_FALL); engine->RegisterEnumValue("Sample", "SMALTREE_GROUND", sSMALTREE_GROUND); engine->RegisterEnumValue("Sample", "SMALTREE_HEAD", sSMALTREE_HEAD); engine->RegisterEnumValue("Sample", "SONCSHIP_METAL1", sSONCSHIP_METAL1); engine->RegisterEnumValue("Sample", "SONCSHIP_MISSILE2", sSONCSHIP_MISSILE2); engine->RegisterEnumValue("Sample", "SONCSHIP_SCRAPE", sSONCSHIP_SCRAPE); engine->RegisterEnumValue("Sample", "SONCSHIP_SHIPLOOP", sSONCSHIP_SHIPLOOP); engine->RegisterEnumValue("Sample", "SONCSHIP_TARGETLOCK", sSONCSHIP_TARGETLOCK); engine->RegisterEnumValue("Sample", "SONICSHIP_METAL1", sSONCSHIP_METAL1); // Private/deprecated engine->RegisterEnumValue("Sample", "SONICSHIP_MISSILE2", sSONCSHIP_MISSILE2); // Private/deprecated engine->RegisterEnumValue("Sample", "SONICSHIP_SCRAPE", sSONCSHIP_SCRAPE); // Private/deprecated engine->RegisterEnumValue("Sample", "SONICSHIP_SHIPLOOP", sSONCSHIP_SHIPLOOP); // Private/deprecated engine->RegisterEnumValue("Sample", "SONICSHIP_TARGETLOCK", sSONCSHIP_TARGETLOCK); // Private/deprecated engine->RegisterEnumValue("Sample", "SPAZSOUNDS_AUTSCH1", sSPAZSOUNDS_AUTSCH1); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_AUTSCH2", sSPAZSOUNDS_AUTSCH2); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_BIRDSIT", sSPAZSOUNDS_BIRDSIT); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_BURP", sSPAZSOUNDS_BURP); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_CHIRP", sSPAZSOUNDS_CHIRP); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_EATBIRD", sSPAZSOUNDS_EATBIRD); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_HAHAHA", sSPAZSOUNDS_HAHAHA); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_HAHAHA2", sSPAZSOUNDS_HAHAHA2); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_HAPPY", sSPAZSOUNDS_HAPPY); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_HIHI", sSPAZSOUNDS_HIHI); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_HOHOHO1", sSPAZSOUNDS_HOHOHO1); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_HOOO", sSPAZSOUNDS_HOOO); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_KARATE7", sSPAZSOUNDS_KARATE7); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_KARATE8", sSPAZSOUNDS_KARATE8); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_OHOH", sSPAZSOUNDS_OHOH); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_OOOH", sSPAZSOUNDS_OOOH); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_WOOHOO", sSPAZSOUNDS_WOOHOO); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_YAHOO", sSPAZSOUNDS_YAHOO); engine->RegisterEnumValue("Sample", "SPAZSOUNDS_YAHOO2", sSPAZSOUNDS_YAHOO2); engine->RegisterEnumValue("Sample", "SPRING_BOING_DOWN", sSPRING_BOING_DOWN); engine->RegisterEnumValue("Sample", "SPRING_SPRING1", sSPRING_SPRING1); engine->RegisterEnumValue("Sample", "STEAM_STEAM", sSTEAM_STEAM); engine->RegisterEnumValue("Sample", "STONED_STONED", sSTONED_STONED); engine->RegisterEnumValue("Sample", "SUCKER_FART", sSUCKER_FART); engine->RegisterEnumValue("Sample", "SUCKER_PINCH1", sSUCKER_PINCH1); engine->RegisterEnumValue("Sample", "SUCKER_PINCH2", sSUCKER_PINCH2); engine->RegisterEnumValue("Sample", "SUCKER_PINCH3", sSUCKER_PINCH3); engine->RegisterEnumValue("Sample", "SUCKER_PLOPSEQ1", sSUCKER_PLOPSEQ1); engine->RegisterEnumValue("Sample", "SUCKER_PLOPSEQ2", sSUCKER_PLOPSEQ2); engine->RegisterEnumValue("Sample", "SUCKER_PLOPSEQ3", sSUCKER_PLOPSEQ3); engine->RegisterEnumValue("Sample", "SUCKER_PLOPSEQ4", sSUCKER_PLOPSEQ4); engine->RegisterEnumValue("Sample", "SUCKER_UP", sSUCKER_UP); engine->RegisterEnumValue("Sample", "TUFBOSS_CATCH", sTUFBOSS_CATCH); engine->RegisterEnumValue("Sample", "TUFBOSS_RELEASE", sTUFBOSS_RELEASE); engine->RegisterEnumValue("Sample", "TUFBOSS_SWING", sTUFBOSS_SWING); engine->RegisterEnumValue("Sample", "TURTLE_BITE3", sTURTLE_BITE3); engine->RegisterEnumValue("Sample", "TURTLE_HIDE", sTURTLE_HIDE); engine->RegisterEnumValue("Sample", "TURTLE_HITSHELL", sTURTLE_HITSHELL); engine->RegisterEnumValue("Sample", "TURTLE_IDLE1", sTURTLE_IDLE1); engine->RegisterEnumValue("Sample", "TURTLE_IDLE2", sTURTLE_IDLE2); engine->RegisterEnumValue("Sample", "TURTLE_NECK", sTURTLE_NECK); engine->RegisterEnumValue("Sample", "TURTLE_SPK1TURT", sTURTLE_SPK1TURT); engine->RegisterEnumValue("Sample", "TURTLE_SPK2TURT", sTURTLE_SPK2TURT); engine->RegisterEnumValue("Sample", "TURTLE_SPK3TURT", sTURTLE_SPK3TURT); engine->RegisterEnumValue("Sample", "TURTLE_SPK4TURT", sTURTLE_SPK4TURT); engine->RegisterEnumValue("Sample", "TURTLE_TURN", sTURTLE_TURN); engine->RegisterEnumValue("Sample", "UTERUS_CRABCLOSE", sUTERUS_CRABCLOSE); engine->RegisterEnumValue("Sample", "UTERUS_CRABOPEN2", sUTERUS_CRABOPEN2); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS1", sUTERUS_SCISSORS1); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS2", sUTERUS_SCISSORS2); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS3", sUTERUS_SCISSORS3); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS4", sUTERUS_SCISSORS4); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS5", sUTERUS_SCISSORS5); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS6", sUTERUS_SCISSORS6); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS7", sUTERUS_SCISSORS7); engine->RegisterEnumValue("Sample", "UTERUS_SCISSORS8", sUTERUS_SCISSORS8); engine->RegisterEnumValue("Sample", "UTERUS_SCREAM1", sUTERUS_SCREAM1); engine->RegisterEnumValue("Sample", "UTERUS_STEP1", sUTERUS_STEP1); engine->RegisterEnumValue("Sample", "UTERUS_STEP2", sUTERUS_STEP2); engine->RegisterEnumValue("Sample", "WIND_WIND2A", sWIND_WIND2A); engine->RegisterEnumValue("Sample", "WITCH_LAUGH", sWITCH_LAUGH); engine->RegisterEnumValue("Sample", "WITCH_MAGIC", sWITCH_MAGIC); engine->RegisterEnumValue("Sample", "XBILSY_BILLAPPEAR", sXBILSY_BILLAPPEAR); engine->RegisterEnumValue("Sample", "XBILSY_FINGERSNAP", sXBILSY_FINGERSNAP); engine->RegisterEnumValue("Sample", "XBILSY_FIRE", sXBILSY_FIRE); engine->RegisterEnumValue("Sample", "XBILSY_FIRESTART", sXBILSY_FIRESTART); engine->RegisterEnumValue("Sample", "XBILSY_SCARY3", sXBILSY_SCARY3); engine->RegisterEnumValue("Sample", "XBILSY_THUNDER", sXBILSY_THUNDER); engine->RegisterEnumValue("Sample", "XBILSY_ZIP", sXBILSY_ZIP); engine->RegisterEnumValue("Sample", "XLIZARD_LIZ1", sXLIZARD_LIZ1); engine->RegisterEnumValue("Sample", "XLIZARD_LIZ2", sXLIZARD_LIZ2); engine->RegisterEnumValue("Sample", "XLIZARD_LIZ4", sXLIZARD_LIZ4); engine->RegisterEnumValue("Sample", "XLIZARD_LIZ6", sXLIZARD_LIZ6); engine->RegisterEnumValue("Sample", "XTURTLE_BITE3", sXTURTLE_BITE3); engine->RegisterEnumValue("Sample", "XTURTLE_HIDE", sXTURTLE_HIDE); engine->RegisterEnumValue("Sample", "XTURTLE_HITSHELL", sXTURTLE_HITSHELL); engine->RegisterEnumValue("Sample", "XTURTLE_IDLE1", sXTURTLE_IDLE1); engine->RegisterEnumValue("Sample", "XTURTLE_IDLE2", sXTURTLE_IDLE2); engine->RegisterEnumValue("Sample", "XTURTLE_NECK", sXTURTLE_NECK); engine->RegisterEnumValue("Sample", "XTURTLE_SPK1TURT", sXTURTLE_SPK1TURT); engine->RegisterEnumValue("Sample", "XTURTLE_SPK2TURT", sXTURTLE_SPK2TURT); engine->RegisterEnumValue("Sample", "XTURTLE_SPK3TURT", sXTURTLE_SPK3TURT); engine->RegisterEnumValue("Sample", "XTURTLE_SPK4TURT", sXTURTLE_SPK4TURT); engine->RegisterEnumValue("Sample", "XTURTLE_TURN", sXTURTLE_TURN); engine->RegisterEnumValue("Sample", "ZDOG_AGRESSIV", sZDOG_AGRESSIV); engine->RegisterEnumValue("Sample", "ZDOG_SNIF1", sZDOG_SNIF1); engine->RegisterEnumValue("Sample", "ZDOG_WAF1", sZDOG_WAF1); engine->RegisterEnumValue("Sample", "ZDOG_WAF2", sZDOG_WAF2); engine->RegisterEnumValue("Sample", "ZDOG_WAF3", sZDOG_WAF3); engine->SetDefaultNamespace("AREA"); engine->RegisterEnumValue("Area", "ONEWAY", areaONEWAY); engine->RegisterEnumValue("Area", "HURT", areaHURT); engine->RegisterEnumValue("Area", "VINE", areaVINE); engine->RegisterEnumValue("Area", "HOOK", areaHOOK); engine->RegisterEnumValue("Area", "SLIDE", areaSLIDE); engine->RegisterEnumValue("Area", "HPOLE", areaHPOLE); engine->RegisterEnumValue("Area", "VPOLE", areaVPOLE); engine->RegisterEnumValue("Area", "FLYOFF", areaFLYOFF); engine->RegisterEnumValue("Area", "RICOCHET", areaRICOCHET); engine->RegisterEnumValue("Area", "BELTRIGHT", areaBELTRIGHT); engine->RegisterEnumValue("Area", "BELTLEFT", areaBELTLEFT); engine->RegisterEnumValue("Area", "ACCBELTRIGHT", areaBELTACCRIGHT); engine->RegisterEnumValue("Area", "ACCBELTLEFT", areaBELTACCLEFT); engine->RegisterEnumValue("Area", "STOPENEMY", areaSTOPENEMY); engine->RegisterEnumValue("Area", "WINDLEFT", areaWINDLEFT); engine->RegisterEnumValue("Area", "WINDRIGHT", areaWINDRIGHT); engine->RegisterEnumValue("Area", "EOL", areaEOL); engine->RegisterEnumValue("Area", "WARPEOL", areaWARPEOL); engine->RegisterEnumValue("Area", "REVERTMORPH", areaENDMORPH); engine->RegisterEnumValue("Area", "FLOATUP", areaFLOATUP); engine->RegisterEnumValue("Area", "TRIGGERROCK", areaROCKTRIGGER); engine->RegisterEnumValue("Area", "DIMLIGHT", areaDIMLIGHT); engine->RegisterEnumValue("Area", "SETLIGHT", areaSETLIGHT); engine->RegisterEnumValue("Area", "LIMITXSCROLL", areaLIMITXSCROLL); engine->RegisterEnumValue("Area", "RESETLIGHT", areaRESETLIGHT); engine->RegisterEnumValue("Area", "WARPSECRET", areaWARPSECRET); engine->RegisterEnumValue("Area", "ECHO", areaECHO); engine->RegisterEnumValue("Area", "ACTIVATEBOSS", areaBOSSTRIGGER); engine->RegisterEnumValue("Area", "JAZZLEVELSTART", areaJAZZLEVELSTART); engine->RegisterEnumValue("Area", "JAZZSTART", areaJAZZLEVELSTART); // Private/deprecated engine->RegisterEnumValue("Area", "SPAZLEVELSTART", areaSPAZLEVELSTART); engine->RegisterEnumValue("Area", "SPAZSTART", areaSPAZLEVELSTART); // Private/deprecated engine->RegisterEnumValue("Area", "MPLEVELSTART", areaMPLEVELSTART); engine->RegisterEnumValue("Area", "MPSTART", areaMPLEVELSTART); // Private/deprecated engine->RegisterEnumValue("Area", "LORILEVELSTART", areaLORILEVELSTART); engine->RegisterEnumValue("Area", "LORISTART", areaLORILEVELSTART); // Private/deprecated engine->RegisterEnumValue("Area", "WARP", areaWARP); engine->RegisterEnumValue("Area", "WARPTARGET", areaWARPTARGET); engine->RegisterEnumValue("Area", "PATH", areaAREAID); engine->RegisterEnumValue("Area", "AREAID", areaAREAID); // Private/deprecated engine->RegisterEnumValue("Area", "NOFIREZONE", areaNOFIREZONE); engine->RegisterEnumValue("Area", "TRIGGERZONE", areaTRIGGERZONE); engine->RegisterEnumValue("Area", "SUCKERTUBE", aSUCKERTUBE); engine->RegisterEnumValue("Area", "TEXT", aTEXT); engine->RegisterEnumValue("Area", "WATERLEVEL", aWATERLEVEL); engine->RegisterEnumValue("Area", "MORPHFROG", aMORPHFROG); engine->RegisterEnumValue("Area", "WATERBLOCK", aWATERBLOCK); engine->SetDefaultNamespace("OBJECT"); engine->RegisterEnumValue("Object", "BLASTERBULLET", aPLAYERBULLET1); engine->RegisterEnumValue("Object", "BOUNCERBULLET", aPLAYERBULLET2); engine->RegisterEnumValue("Object", "ICEBULLET", aPLAYERBULLET3); engine->RegisterEnumValue("Object", "SEEKERBULLET", aPLAYERBULLET4); engine->RegisterEnumValue("Object", "RFBULLET", aPLAYERBULLET5); engine->RegisterEnumValue("Object", "TOASTERBULLET", aPLAYERBULLET6); engine->RegisterEnumValue("Object", "FIREBALLBULLET", aPLAYERBULLET8); engine->RegisterEnumValue("Object", "ELECTROBULLET", aPLAYERBULLET9); engine->RegisterEnumValue("Object", "BLASTERBULLETPU", aPLAYERBULLETP1); engine->RegisterEnumValue("Object", "BOUNCERBULLETPU", aPLAYERBULLETP2); engine->RegisterEnumValue("Object", "ICEBULLETPU", aPLAYERBULLETP3); engine->RegisterEnumValue("Object", "SEEKERBULLETPU", aPLAYERBULLETP4); engine->RegisterEnumValue("Object", "RFBULLETPU", aPLAYERBULLETP5); engine->RegisterEnumValue("Object", "TOASTERBULLETPU", aPLAYERBULLETP6); engine->RegisterEnumValue("Object", "FIREBALLBULLETPU", aPLAYERBULLETP8); engine->RegisterEnumValue("Object", "ELECTROBULLETPU", aPLAYERBULLETP9); engine->RegisterEnumValue("Object", "FIRESHIELDBULLET", aPLAYERBULLETC1); engine->RegisterEnumValue("Object", "WATERSHIELDBULLET", aPLAYERBULLETC2); engine->RegisterEnumValue("Object", "BUBBLESHIELDBULLET", aPLAYERBULLETC2); // Private/deprecated engine->RegisterEnumValue("Object", "LIGHTNINGSHIELDBULLET", aPLAYERBULLETC3); engine->RegisterEnumValue("Object", "PLASMASHIELDBULLET", aPLAYERBULLETC3); // Private/deprecated engine->RegisterEnumValue("Object", "BULLET", aBULLET); engine->RegisterEnumValue("Object", "SMOKERING", aCATSMOKE); engine->RegisterEnumValue("Object", "SHARD", aSHARD); engine->RegisterEnumValue("Object", "EXPLOSION", aEXPLOSION); engine->RegisterEnumValue("Object", "BOUNCEONCE", aBOUNCEONCE); engine->RegisterEnumValue("Object", "FLICKERGEM", aREDGEMTEMP); engine->RegisterEnumValue("Object", "LASER", aPLAYERLASER); engine->RegisterEnumValue("Object", "UTERUSSPIKEBALL", aUTERUSEL); engine->RegisterEnumValue("Object", "BIRD", aBIRD); engine->RegisterEnumValue("Object", "BUBBLE", aBUBBLE); engine->RegisterEnumValue("Object", "ICEAMMO3", aGUN3AMMO3); engine->RegisterEnumValue("Object", "BOUNCERAMMO3", aGUN2AMMO3); engine->RegisterEnumValue("Object", "SEEKERAMMO3", aGUN4AMMO3); engine->RegisterEnumValue("Object", "RFAMMO3", aGUN5AMMO3); engine->RegisterEnumValue("Object", "TOASTERAMMO3", aGUN6AMMO3); engine->RegisterEnumValue("Object", "TNTAMMO3", aGUN7AMMO3); engine->RegisterEnumValue("Object", "GUN8AMMO3", aGUN8AMMO3); engine->RegisterEnumValue("Object", "GUN9AMMO3", aGUN9AMMO3); engine->RegisterEnumValue("Object", "TURTLESHELL", aTURTLESHELL); engine->RegisterEnumValue("Object", "SWINGINGVINE", aSWINGVINE); engine->RegisterEnumValue("Object", "BOMB", aBOMB); engine->RegisterEnumValue("Object", "SILVERCOIN", aSILVERCOIN); engine->RegisterEnumValue("Object", "GOLDCOIN", aGOLDCOIN); engine->RegisterEnumValue("Object", "GUNCRATE", aGUNCRATE); engine->RegisterEnumValue("Object", "CARROTCRATE", aCARROTCRATE); engine->RegisterEnumValue("Object", "ONEUPCRATE", a1UPCRATE); engine->RegisterEnumValue("Object", "GEMBARREL", aGEMBARREL); engine->RegisterEnumValue("Object", "CARROTBARREL", aCARROTBARREL); engine->RegisterEnumValue("Object", "ONEUPBARREL", a1UPBARREL); engine->RegisterEnumValue("Object", "BOMBCRATE", aBOMBCRATE); engine->RegisterEnumValue("Object", "ICEAMMO15", aGUN3AMMO15); engine->RegisterEnumValue("Object", "BOUNCERAMMO15", aGUN2AMMO15); engine->RegisterEnumValue("Object", "SEEKERAMMO15", aGUN4AMMO15); engine->RegisterEnumValue("Object", "RFAMMO15", aGUN5AMMO15); engine->RegisterEnumValue("Object", "TOASTERAMMO15", aGUN6AMMO15); engine->RegisterEnumValue("Object", "TNT", aTNT); engine->RegisterEnumValue("Object", "AIRBOARDGENERATOR", aAIRBOARDGENERATOR); engine->RegisterEnumValue("Object", "FROZENSPRING", aFROZENGREENSPRING); engine->RegisterEnumValue("Object", "FASTFIRE", aGUNFASTFIRE); engine->RegisterEnumValue("Object", "SPRINGCRATE", aSPRINGCRATE); engine->RegisterEnumValue("Object", "REDGEM", aREDGEM); engine->RegisterEnumValue("Object", "GREENGEM", aGREENGEM); engine->RegisterEnumValue("Object", "BLUEGEM", aBLUEGEM); engine->RegisterEnumValue("Object", "PURPLEGEM", aPURPLEGEM); engine->RegisterEnumValue("Object", "SUPERGEM", aSUPERREDGEM); engine->RegisterEnumValue("Object", "BIRDCAGE", aBIRDCAGE); engine->RegisterEnumValue("Object", "GUNBARREL", aGUNBARREL); engine->RegisterEnumValue("Object", "GEMCRATE", aGEMCRATE); engine->RegisterEnumValue("Object", "MORPH", aMORPHMONITOR); engine->RegisterEnumValue("Object", "CARROT", aENERGYUP); engine->RegisterEnumValue("Object", "FULLENERGY", aFULLENERGY); engine->RegisterEnumValue("Object", "FIRESHIELD", aFIRESHIELD); engine->RegisterEnumValue("Object", "WATERSHIELD", aWATERSHIELD); engine->RegisterEnumValue("Object", "BUBBLESHIELD", aWATERSHIELD); // Private/deprecated engine->RegisterEnumValue("Object", "LIGHTNINGSHIELD", aLIGHTSHIELD); engine->RegisterEnumValue("Object", "PLASMASHIELD", aLIGHTSHIELD); // Private/deprecated engine->RegisterEnumValue("Object", "FASTFEET", aFASTFEET); engine->RegisterEnumValue("Object", "ONEUP", aEXTRALIFE); engine->RegisterEnumValue("Object", "EXTRALIFE", aEXTRALIFE); // Private/deprecated engine->RegisterEnumValue("Object", "EXTRALIVE", aEXTRALIFE); // Private/deprecated engine->RegisterEnumValue("Object", "EOLPOST", aENDOFLEVELPOST); engine->RegisterEnumValue("Object", "SAVEPOST", aSAVEPOST); engine->RegisterEnumValue("Object", "CHECKPOINT", aSAVEPOST); // Private/deprecated engine->RegisterEnumValue("Object", "BONUSPOST", aBONUSLEVELPOST); engine->RegisterEnumValue("Object", "REDSPRING", aREDSPRING); engine->RegisterEnumValue("Object", "GREENSPRING", aGREENSPRING); engine->RegisterEnumValue("Object", "BLUESPRING", aBLUESPRING); engine->RegisterEnumValue("Object", "INVINCIBILITY", aINVINCIBILITY); engine->RegisterEnumValue("Object", "EXTRATIME", aEXTRATIME); engine->RegisterEnumValue("Object", "FREEZER", aFREEZER); engine->RegisterEnumValue("Object", "FREEZEENEMIES", aFREEZER); // Private/deprecated engine->RegisterEnumValue("Object", "HORREDSPRING", aHREDSPRING); engine->RegisterEnumValue("Object", "HORGREENSPRING", aHGREENSPRING); engine->RegisterEnumValue("Object", "HORBLUESPRING", aHBLUESPRING); engine->RegisterEnumValue("Object", "BIRDMORPH", aBIRDMORPHMONITOR); engine->RegisterEnumValue("Object", "TRIGGERCRATE", aTRIGGERCRATE); engine->RegisterEnumValue("Object", "FLYCARROT", aFLYCARROT); engine->RegisterEnumValue("Object", "RECTREDGEM", aRECTREDGEM); engine->RegisterEnumValue("Object", "RECTGREENGEM", aRECTGREENGEM); engine->RegisterEnumValue("Object", "RECTBLUEGEM", aRECTBLUEGEM); engine->RegisterEnumValue("Object", "TUFTURT", aTUFTURT); engine->RegisterEnumValue("Object", "TUFBOSS", aTUFBOSS); engine->RegisterEnumValue("Object", "LABRAT", aLABRAT); engine->RegisterEnumValue("Object", "DRAGON", aDRAGON); engine->RegisterEnumValue("Object", "LIZARD", aLIZARD); engine->RegisterEnumValue("Object", "BEE", aBUMBEE); engine->RegisterEnumValue("Object", "BUMBEE", aBUMBEE); // Private/deprecated engine->RegisterEnumValue("Object", "RAPIER", aRAPIER); engine->RegisterEnumValue("Object", "SPARK", aSPARK); engine->RegisterEnumValue("Object", "BAT", aBAT); engine->RegisterEnumValue("Object", "SUCKER", aSUCKER); engine->RegisterEnumValue("Object", "CATERPILLAR", aCATERPILLAR); engine->RegisterEnumValue("Object", "CHESHIRE1", aCHESHIRE1); engine->RegisterEnumValue("Object", "CHESHIRE2", aCHESHIRE2); engine->RegisterEnumValue("Object", "HATTER", aHATTER); engine->RegisterEnumValue("Object", "BILSY", aBILSYBOSS); engine->RegisterEnumValue("Object", "SKELETON", aSKELETON); engine->RegisterEnumValue("Object", "DOGGYDOGG", aDOGGYDOGG); engine->RegisterEnumValue("Object", "NORMTURTLE", aNORMTURTLE); engine->RegisterEnumValue("Object", "HELMUT", aHELMUT); engine->RegisterEnumValue("Object", "DEMON", aDEMON); engine->RegisterEnumValue("Object", "DRAGONFLY", aDRAGONFLY); engine->RegisterEnumValue("Object", "MONKEY", aMONKEY); engine->RegisterEnumValue("Object", "FATCHICK", aFATCHK); engine->RegisterEnumValue("Object", "FENCER", aFENCER); engine->RegisterEnumValue("Object", "FISH", aFISH); engine->RegisterEnumValue("Object", "MOTH", aMOTH); engine->RegisterEnumValue("Object", "STEAM", aSTEAM); engine->RegisterEnumValue("Object", "ROTATINGROCK", aROCK); engine->RegisterEnumValue("Object", "BLASTERPOWERUP", aGUN1POWER); engine->RegisterEnumValue("Object", "BOUNCERPOWERUP", aGUN2POWER); engine->RegisterEnumValue("Object", "ICEPOWERUP", aGUN3POWER); engine->RegisterEnumValue("Object", "SEEKERPOWERUP", aGUN4POWER); engine->RegisterEnumValue("Object", "RFPOWERUP", aGUN5POWER); engine->RegisterEnumValue("Object", "TOASTERPOWERUP", aGUN6POWER); engine->RegisterEnumValue("Object", "LEFTPADDLE", aPINLEFTPADDLE); engine->RegisterEnumValue("Object", "RIGHTPADDLE", aPINRIGHTPADDLE); engine->RegisterEnumValue("Object", "FIVEHUNDREDBUMP", aPIN500BUMP); engine->RegisterEnumValue("Object", "CARROTBUMP", aPINCARROTBUMP); engine->RegisterEnumValue("Object", "APPLE", aAPPLE); engine->RegisterEnumValue("Object", "BANANA", aBANANA); engine->RegisterEnumValue("Object", "CHERRY", aCHERRY); engine->RegisterEnumValue("Object", "ORANGE", aORANGE); engine->RegisterEnumValue("Object", "PEAR", aPEAR); engine->RegisterEnumValue("Object", "PRETZEL", aPRETZEL); engine->RegisterEnumValue("Object", "STRAWBERRY", aSTRAWBERRY); engine->RegisterEnumValue("Object", "STEADYLIGHT", aSTEADYLIGHT); engine->RegisterEnumValue("Object", "PULZELIGHT", aPULZELIGHT); engine->RegisterEnumValue("Object", "PULSELIGHT", aPULZELIGHT); // Private/deprecated engine->RegisterEnumValue("Object", "FLICKERLIGHT", aFLICKERLIGHT); engine->RegisterEnumValue("Object", "QUEEN", aQUEENBOSS); engine->RegisterEnumValue("Object", "FLOATSUCKER", aFLOATSUCKER); engine->RegisterEnumValue("Object", "BRIDGE", aBRIDGE); engine->RegisterEnumValue("Object", "LEMON", aLEMON); engine->RegisterEnumValue("Object", "LIME", aLIME); engine->RegisterEnumValue("Object", "THING", aTHING); engine->RegisterEnumValue("Object", "WATERMELON", aWMELON); engine->RegisterEnumValue("Object", "PEACH", aPEACH); engine->RegisterEnumValue("Object", "GRAPES", aGRAPES); engine->RegisterEnumValue("Object", "LETTUCE", aLETTUCE); engine->RegisterEnumValue("Object", "EGGPLANT", aEGGPLANT); engine->RegisterEnumValue("Object", "CUCUMB", aCUCUMB); engine->RegisterEnumValue("Object", "CUCUMBER", aCUCUMB); // Private/deprecated engine->RegisterEnumValue("Object", "COKE", aCOKE); engine->RegisterEnumValue("Object", "SOFTDRINK", aCOKE); // Private/deprecated engine->RegisterEnumValue("Object", "PEPSI", aPEPSI); engine->RegisterEnumValue("Object", "SODAPOP", aCOKE); // Private/deprecated engine->RegisterEnumValue("Object", "MILK", aMILK); engine->RegisterEnumValue("Object", "PIE", aPIE); engine->RegisterEnumValue("Object", "CAKE", aCAKE); engine->RegisterEnumValue("Object", "DONUT", aDONUT); engine->RegisterEnumValue("Object", "CUPCAKE", aCUPCAKE); engine->RegisterEnumValue("Object", "CHIPS", aCHIPS); engine->RegisterEnumValue("Object", "CANDY", aCANDY1); engine->RegisterEnumValue("Object", "CHOCBAR", aCHOCBAR); engine->RegisterEnumValue("Object", "aCHOCOLATEBAR", aCHOCBAR); // Private/deprecated engine->RegisterEnumValue("Object", "ICECREAM", aICECREAM); engine->RegisterEnumValue("Object", "BURGER", aBURGER); engine->RegisterEnumValue("Object", "PIZZA", aPIZZA); engine->RegisterEnumValue("Object", "FRIES", aFRIES); engine->RegisterEnumValue("Object", "CHICKENLEG", aCHICKLEG); engine->RegisterEnumValue("Object", "SANDWICH", aSANDWICH); engine->RegisterEnumValue("Object", "TACO", aTACOBELL); engine->RegisterEnumValue("Object", "WEENIE", aWEENIE); engine->RegisterEnumValue("Object", "HAM", aHAM); engine->RegisterEnumValue("Object", "CHEESE", aCHEESE); engine->RegisterEnumValue("Object", "FLOATLIZARD", aFLOATLIZARD); engine->RegisterEnumValue("Object", "STANDMONKEY", aSTANDMONKEY); engine->RegisterEnumValue("Object", "DESTRUCTSCENERY", aDESTRUCTSCENERY); engine->RegisterEnumValue("Object", "DESTRUCTSCENERYBOMB", aDESTRUCTSCENERYBOMB); engine->RegisterEnumValue("Object", "TNTDESTRUCTSCENERY", aDESTRUCTSCENERYBOMB); // Private/deprecated engine->RegisterEnumValue("Object", "COLLAPSESCENERY", aCOLLAPSESCENERY); engine->RegisterEnumValue("Object", "STOMPSCENERY", aSTOMPSCENERY); engine->RegisterEnumValue("Object", "GEMSTOMP", aGEMSTOMP); engine->RegisterEnumValue("Object", "RAVEN", aRAVEN); engine->RegisterEnumValue("Object", "TUBETURTLE", aTUBETURTLE); engine->RegisterEnumValue("Object", "GEMRING", aGEMRING); engine->RegisterEnumValue("Object", "SMALLTREE", aROTSMALLTREE); engine->RegisterEnumValue("Object", "AMBIENTSOUND", aAMBIENTSOUND); engine->RegisterEnumValue("Object", "UTERUS", aUTERUS); engine->RegisterEnumValue("Object", "CRAB", aCRAB); engine->RegisterEnumValue("Object", "WITCH", aWITCH); engine->RegisterEnumValue("Object", "ROCKETTURTLE", aROCKTURT); engine->RegisterEnumValue("Object", "BUBBA", aBUBBA); engine->RegisterEnumValue("Object", "DEVILDEVAN", aDEVILDEVAN); engine->RegisterEnumValue("Object", "DEVANROBOT", aDEVANROBOT); engine->RegisterEnumValue("Object", "ROBOT", aROBOT); engine->RegisterEnumValue("Object", "CARROTUSPOLE", aCARROTUSPOLE); engine->RegisterEnumValue("Object", "PSYCHPOLE", aPSYCHPOLE); engine->RegisterEnumValue("Object", "DIAMONDUSPOLE", aDIAMONDUSPOLE); engine->RegisterEnumValue("Object", "FRUITPLATFORM", aFRUITPLATFORM); engine->RegisterEnumValue("Object", "BOLLPLATFORM", aBOLLPLATFORM); engine->RegisterEnumValue("Object", "GRASSPLATFORM", aGRASSPLATFORM); engine->RegisterEnumValue("Object", "PINKPLATFORM", aPINKPLATFORM); engine->RegisterEnumValue("Object", "SONICPLATFORM", aSONICPLATFORM); engine->RegisterEnumValue("Object", "SPIKEPLATFORM", aSPIKEPLATFORM); engine->RegisterEnumValue("Object", "SPIKEBOLL", aSPIKEBOLL); engine->RegisterEnumValue("Object", "GENERATOR", aGENERATOR); engine->RegisterEnumValue("Object", "EVA", aEVA); engine->RegisterEnumValue("Object", "BUBBLER", aBUBBLER); engine->RegisterEnumValue("Object", "TNTPOWERUP", aTNTPOWER); engine->RegisterEnumValue("Object", "GUN8POWERUP", aGUN8POWER); engine->RegisterEnumValue("Object", "GUN9POWERUP", aGUN9POWER); engine->RegisterEnumValue("Object", "SPIKEBOLL3D", aSPIKEBOLL3D); engine->RegisterEnumValue("Object", "SPRINGCORD", aSPRINGCORD); engine->RegisterEnumValue("Object", "BEES", aBEES); engine->RegisterEnumValue("Object", "COPTER", aCOPTER); engine->RegisterEnumValue("Object", "LASERSHIELD", aLASERSHIELD); engine->RegisterEnumValue("Object", "STOPWATCH", aSTOPWATCH); engine->RegisterEnumValue("Object", "JUNGLEPOLE", aJUNGLEPOLE); engine->RegisterEnumValue("Object", "WARP", areaWARP); engine->RegisterEnumValue("Object", "BIGROCK", aBIGROCK); engine->RegisterEnumValue("Object", "BIGBOX", aBIGBOX); engine->RegisterEnumValue("Object", "TRIGGERSCENERY", aTRIGGERSCENERY); engine->RegisterEnumValue("Object", "BOLLY", aSONICBOSS); engine->RegisterEnumValue("Object", "BUTTERFLY", aBUTTERFLY); engine->RegisterEnumValue("Object", "BEEBOY", aBEEBOY); engine->RegisterEnumValue("Object", "SNOW", aSNOW); engine->RegisterEnumValue("Object", "TWEEDLEBOSS", aTWEEDLEBOSS); engine->RegisterEnumValue("Object", "AIRBOARD", aAIRBOARD); engine->RegisterEnumValue("Object", "CTFBASE", aFLAG); engine->RegisterEnumValue("Object", "XMASNORMTURTLE", aXNORMTURTLE); engine->RegisterEnumValue("Object", "XMASLIZARD", aXLIZARD); engine->RegisterEnumValue("Object", "XMASFLOATLIZARD", aXFLOATLIZARD); engine->RegisterEnumValue("Object", "XMASBILSY", aXBILSYBOSS); engine->RegisterEnumValue("Object", "CAT", aZCAT); engine->RegisterEnumValue("Object", "PACMANGHOST", aZGHOST); engine->SetDefaultNamespace("BEHAVIOR"); engine->RegisterEnumValue("Behavior", "DEFAULT", -1); engine->RegisterEnumValue("Behavior", "INACTIVE", aUNKNOWN); engine->RegisterEnumValue("Behavior", "BLASTERBULLET", aPLAYERBULLET1); engine->RegisterEnumValue("Behavior", "BOUNCERBULLET", aPLAYERBULLET2); engine->RegisterEnumValue("Behavior", "ICEBULLET", aPLAYERBULLET3); engine->RegisterEnumValue("Behavior", "SEEKERBULLET", aPLAYERBULLET4); engine->RegisterEnumValue("Behavior", "RFBULLET", aPLAYERBULLET5); engine->RegisterEnumValue("Behavior", "TOASTERBULLET", aPLAYERBULLET6); engine->RegisterEnumValue("Behavior", "FIREBALLBULLET", aPLAYERBULLET8); engine->RegisterEnumValue("Behavior", "ELECTROBULLET", aPLAYERBULLET9); engine->RegisterEnumValue("Behavior", "BLASTERBULLETPU", aPLAYERBULLETP1); engine->RegisterEnumValue("Behavior", "BOUNCERBULLETPU", aPLAYERBULLETP2); engine->RegisterEnumValue("Behavior", "ICEBULLETPU", aPLAYERBULLETP3); engine->RegisterEnumValue("Behavior", "SEEKERBULLETPU", aPLAYERBULLETP4); engine->RegisterEnumValue("Behavior", "RFBULLETPU", aPLAYERBULLETP5); engine->RegisterEnumValue("Behavior", "TOASTERBULLETPU", aPLAYERBULLETP6); engine->RegisterEnumValue("Behavior", "FIREBALLBULLETPU", aPLAYERBULLETP8); engine->RegisterEnumValue("Behavior", "ELECTROBULLETPU", aPLAYERBULLETP9); engine->RegisterEnumValue("Behavior", "FIRESHIELDBULLET", aPLAYERBULLETC1); engine->RegisterEnumValue("Behavior", "WATERSHIELDBULLET", aPLAYERBULLETC2); engine->RegisterEnumValue("Behavior", "BUBBLESHIELDBULLET", aPLAYERBULLETC2); // Private/deprecated engine->RegisterEnumValue("Behavior", "LIGHTNINGSHIELDBULLET", aPLAYERBULLETC3); engine->RegisterEnumValue("Behavior", "PLASMASHIELDBULLET", aPLAYERBULLETC3); // Private/deprecated engine->RegisterEnumValue("Behavior", "BULLET", aBULLET); engine->RegisterEnumValue("Behavior", "SMOKERING", aCATSMOKE); engine->RegisterEnumValue("Behavior", "SHARD", aSHARD); engine->RegisterEnumValue("Behavior", "EXPLOSION", aEXPLOSION); engine->RegisterEnumValue("Behavior", "BOUNCEONCE", aBOUNCEONCE); engine->RegisterEnumValue("Behavior", "FLICKERGEM", aREDGEMTEMP); engine->RegisterEnumValue("Behavior", "LASER", aPLAYERLASER); engine->RegisterEnumValue("Behavior", "UTERUSSPIKEBALL", aUTERUSEL); engine->RegisterEnumValue("Behavior", "BIRD", aBIRD); engine->RegisterEnumValue("Behavior", "BUBBLE", aBUBBLE); engine->RegisterEnumValue("Behavior", "ICEAMMO3", aGUN3AMMO3); engine->RegisterEnumValue("Behavior", "BOUNCERAMMO3", aGUN2AMMO3); engine->RegisterEnumValue("Behavior", "SEEKERAMMO3", aGUN4AMMO3); engine->RegisterEnumValue("Behavior", "RFAMMO3", aGUN5AMMO3); engine->RegisterEnumValue("Behavior", "TOASTERAMMO3", aGUN6AMMO3); engine->RegisterEnumValue("Behavior", "TNTAMMO3", aGUN7AMMO3); engine->RegisterEnumValue("Behavior", "GUN8AMMO3", aGUN8AMMO3); engine->RegisterEnumValue("Behavior", "GUN9AMMO3", aGUN9AMMO3); engine->RegisterEnumValue("Behavior", "TURTLESHELL", aTURTLESHELL); engine->RegisterEnumValue("Behavior", "SWINGINGVINE", aSWINGVINE); engine->RegisterEnumValue("Behavior", "BOMB", aBOMB); engine->RegisterEnumValue("Behavior", "SILVERCOIN", aSILVERCOIN); engine->RegisterEnumValue("Behavior", "GOLDCOIN", aGOLDCOIN); engine->RegisterEnumValue("Behavior", "GUNCRATE", aGUNCRATE); engine->RegisterEnumValue("Behavior", "CARROTCRATE", aCARROTCRATE); engine->RegisterEnumValue("Behavior", "ONEUPCRATE", a1UPCRATE); engine->RegisterEnumValue("Behavior", "GEMBARREL", aGEMBARREL); engine->RegisterEnumValue("Behavior", "CARROTBARREL", aCARROTBARREL); engine->RegisterEnumValue("Behavior", "ONEUPBARREL", a1UPBARREL); engine->RegisterEnumValue("Behavior", "BOMBCRATE", aBOMBCRATE); engine->RegisterEnumValue("Behavior", "ICEAMMO15", aGUN3AMMO15); engine->RegisterEnumValue("Behavior", "BOUNCERAMMO15", aGUN2AMMO15); engine->RegisterEnumValue("Behavior", "SEEKERAMMO15", aGUN4AMMO15); engine->RegisterEnumValue("Behavior", "RFAMMO15", aGUN5AMMO15); engine->RegisterEnumValue("Behavior", "TOASTERAMMO15", aGUN6AMMO15); engine->RegisterEnumValue("Behavior", "TNT", aTNT); engine->RegisterEnumValue("Behavior", "AIRBOARDGENERATOR", aAIRBOARDGENERATOR); engine->RegisterEnumValue("Behavior", "FROZENSPRING", aFROZENGREENSPRING); engine->RegisterEnumValue("Behavior", "FASTFIRE", aGUNFASTFIRE); engine->RegisterEnumValue("Behavior", "SPRINGCRATE", aSPRINGCRATE); engine->RegisterEnumValue("Behavior", "REDGEM", aREDGEM); engine->RegisterEnumValue("Behavior", "GREENGEM", aGREENGEM); engine->RegisterEnumValue("Behavior", "BLUEGEM", aBLUEGEM); engine->RegisterEnumValue("Behavior", "PURPLEGEM", aPURPLEGEM); engine->RegisterEnumValue("Behavior", "SUPERGEM", aSUPERREDGEM); engine->RegisterEnumValue("Behavior", "BIRDCAGE", aBIRDCAGE); engine->RegisterEnumValue("Behavior", "GUNBARREL", aGUNBARREL); engine->RegisterEnumValue("Behavior", "GEMCRATE", aGEMCRATE); engine->RegisterEnumValue("Behavior", "MORPH", aMORPHMONITOR); engine->RegisterEnumValue("Behavior", "CARROT", aENERGYUP); engine->RegisterEnumValue("Behavior", "FULLENERGY", aFULLENERGY); engine->RegisterEnumValue("Behavior", "FIRESHIELD", aFIRESHIELD); engine->RegisterEnumValue("Behavior", "WATERSHIELD", aWATERSHIELD); engine->RegisterEnumValue("Behavior", "BUBBLESHIELD", aWATERSHIELD); // Private/deprecated engine->RegisterEnumValue("Behavior", "LIGHTNINGSHIELD", aLIGHTSHIELD); engine->RegisterEnumValue("Behavior", "PLASMASHIELD", aLIGHTSHIELD); // Private/deprecated engine->RegisterEnumValue("Behavior", "FASTFEET", aFASTFEET); engine->RegisterEnumValue("Behavior", "ONEUP", aEXTRALIFE); engine->RegisterEnumValue("Behavior", "EXTRALIFE", aEXTRALIFE); // Private/deprecated engine->RegisterEnumValue("Behavior", "EXTRALIVE", aEXTRALIFE); // Private/deprecated engine->RegisterEnumValue("Behavior", "EOLPOST", aENDOFLEVELPOST); engine->RegisterEnumValue("Behavior", "SAVEPOST", aSAVEPOST); engine->RegisterEnumValue("Behavior", "CHECKPOINT", aSAVEPOST); // Private/deprecated engine->RegisterEnumValue("Behavior", "BONUSPOST", aBONUSLEVELPOST); engine->RegisterEnumValue("Behavior", "REDSPRING", aREDSPRING); engine->RegisterEnumValue("Behavior", "GREENSPRING", aGREENSPRING); engine->RegisterEnumValue("Behavior", "BLUESPRING", aBLUESPRING); engine->RegisterEnumValue("Behavior", "INVINCIBILITY", aINVINCIBILITY); engine->RegisterEnumValue("Behavior", "EXTRATIME", aEXTRATIME); engine->RegisterEnumValue("Behavior", "FREEZER", aFREEZER); engine->RegisterEnumValue("Behavior", "FREEZEENEMIES", aFREEZER); // Private/deprecated engine->RegisterEnumValue("Behavior", "HORREDSPRING", aHREDSPRING); engine->RegisterEnumValue("Behavior", "HORGREENSPRING", aHGREENSPRING); engine->RegisterEnumValue("Behavior", "HORBLUESPRING", aHBLUESPRING); engine->RegisterEnumValue("Behavior", "BIRDMORPH", aBIRDMORPHMONITOR); engine->RegisterEnumValue("Behavior", "TRIGGERCRATE", aTRIGGERCRATE); engine->RegisterEnumValue("Behavior", "FLYCARROT", aFLYCARROT); engine->RegisterEnumValue("Behavior", "RECTREDGEM", aRECTREDGEM); engine->RegisterEnumValue("Behavior", "RECTGREENGEM", aRECTGREENGEM); engine->RegisterEnumValue("Behavior", "RECTBLUEGEM", aRECTBLUEGEM); engine->RegisterEnumValue("Behavior", "TUFTURT", aTUFTURT); engine->RegisterEnumValue("Behavior", "TUFBOSS", aTUFBOSS); engine->RegisterEnumValue("Behavior", "LABRAT", aLABRAT); engine->RegisterEnumValue("Behavior", "DRAGON", aDRAGON); engine->RegisterEnumValue("Behavior", "LIZARD", aLIZARD); engine->RegisterEnumValue("Behavior", "BEE", aBUMBEE); engine->RegisterEnumValue("Behavior", "BUMBEE", aBUMBEE); // Private/deprecated engine->RegisterEnumValue("Behavior", "RAPIER", aRAPIER); engine->RegisterEnumValue("Behavior", "SPARK", aSPARK); engine->RegisterEnumValue("Behavior", "BAT", aBAT); engine->RegisterEnumValue("Behavior", "SUCKER", aSUCKER); engine->RegisterEnumValue("Behavior", "CATERPILLAR", aCATERPILLAR); engine->RegisterEnumValue("Behavior", "CHESHIRE1", aCHESHIRE1); engine->RegisterEnumValue("Behavior", "CHESHIRE2", aCHESHIRE2); engine->RegisterEnumValue("Behavior", "HATTER", aHATTER); engine->RegisterEnumValue("Behavior", "BILSY", aBILSYBOSS); engine->RegisterEnumValue("Behavior", "SKELETON", aSKELETON); engine->RegisterEnumValue("Behavior", "DOGGYDOGG", aDOGGYDOGG); engine->RegisterEnumValue("Behavior", "NORMTURTLE", aNORMTURTLE); engine->RegisterEnumValue("Behavior", "HELMUT", aHELMUT); engine->RegisterEnumValue("Behavior", "DEMON", aDEMON); engine->RegisterEnumValue("Behavior", "DRAGONFLY", aDRAGONFLY); engine->RegisterEnumValue("Behavior", "MONKEY", aMONKEY); engine->RegisterEnumValue("Behavior", "FATCHICK", aFATCHK); engine->RegisterEnumValue("Behavior", "FENCER", aFENCER); engine->RegisterEnumValue("Behavior", "FISH", aFISH); engine->RegisterEnumValue("Behavior", "MOTH", aMOTH); engine->RegisterEnumValue("Behavior", "STEAM", aSTEAM); engine->RegisterEnumValue("Behavior", "ROTATINGROCK", aROCK); engine->RegisterEnumValue("Behavior", "BLASTERPOWERUP", aGUN1POWER); engine->RegisterEnumValue("Behavior", "BOUNCERPOWERUP", aGUN2POWER); engine->RegisterEnumValue("Behavior", "ICEPOWERUP", aGUN3POWER); engine->RegisterEnumValue("Behavior", "SEEKERPOWERUP", aGUN4POWER); engine->RegisterEnumValue("Behavior", "RFPOWERUP", aGUN5POWER); engine->RegisterEnumValue("Behavior", "TOASTERPOWERUP", aGUN6POWER); engine->RegisterEnumValue("Behavior", "LEFTPADDLE", aPINLEFTPADDLE); engine->RegisterEnumValue("Behavior", "RIGHTPADDLE", aPINRIGHTPADDLE); engine->RegisterEnumValue("Behavior", "FIVEHUNDREDBUMP", aPIN500BUMP); engine->RegisterEnumValue("Behavior", "CARROTBUMP", aPINCARROTBUMP); engine->RegisterEnumValue("Behavior", "APPLE", aAPPLE); engine->RegisterEnumValue("Behavior", "BANANA", aBANANA); engine->RegisterEnumValue("Behavior", "CHERRY", aCHERRY); engine->RegisterEnumValue("Behavior", "ORANGE", aORANGE); engine->RegisterEnumValue("Behavior", "PEAR", aPEAR); engine->RegisterEnumValue("Behavior", "PRETZEL", aPRETZEL); engine->RegisterEnumValue("Behavior", "STRAWBERRY", aSTRAWBERRY); engine->RegisterEnumValue("Behavior", "STEADYLIGHT", aSTEADYLIGHT); engine->RegisterEnumValue("Behavior", "PULZELIGHT", aPULZELIGHT); engine->RegisterEnumValue("Behavior", "PULSELIGHT", aPULZELIGHT); // Private/deprecated engine->RegisterEnumValue("Behavior", "FLICKERLIGHT", aFLICKERLIGHT); engine->RegisterEnumValue("Behavior", "QUEEN", aQUEENBOSS); engine->RegisterEnumValue("Behavior", "FLOATSUCKER", aFLOATSUCKER); engine->RegisterEnumValue("Behavior", "BRIDGE", aBRIDGE); engine->RegisterEnumValue("Behavior", "LEMON", aLEMON); engine->RegisterEnumValue("Behavior", "LIME", aLIME); engine->RegisterEnumValue("Behavior", "THING", aTHING); engine->RegisterEnumValue("Behavior", "WATERMELON", aWMELON); engine->RegisterEnumValue("Behavior", "PEACH", aPEACH); engine->RegisterEnumValue("Behavior", "GRAPES", aGRAPES); engine->RegisterEnumValue("Behavior", "LETTUCE", aLETTUCE); engine->RegisterEnumValue("Behavior", "EGGPLANT", aEGGPLANT); engine->RegisterEnumValue("Behavior", "CUCUMB", aCUCUMB); engine->RegisterEnumValue("Behavior", "CUCUMBER", aCUCUMB); // Private/deprecated engine->RegisterEnumValue("Behavior", "COKE", aCOKE); engine->RegisterEnumValue("Behavior", "SOFTDRINK", aCOKE); // Private/deprecated engine->RegisterEnumValue("Behavior", "PEPSI", aPEPSI); engine->RegisterEnumValue("Behavior", "SODAPOP", aCOKE); // Private/deprecated engine->RegisterEnumValue("Behavior", "MILK", aMILK); engine->RegisterEnumValue("Behavior", "PIE", aPIE); engine->RegisterEnumValue("Behavior", "CAKE", aCAKE); engine->RegisterEnumValue("Behavior", "DONUT", aDONUT); engine->RegisterEnumValue("Behavior", "CUPCAKE", aCUPCAKE); engine->RegisterEnumValue("Behavior", "CHIPS", aCHIPS); engine->RegisterEnumValue("Behavior", "CANDY", aCANDY1); engine->RegisterEnumValue("Behavior", "CHOCBAR", aCHOCBAR); engine->RegisterEnumValue("Behavior", "aCHOCOLATEBAR", aCHOCBAR); // Private/deprecated engine->RegisterEnumValue("Behavior", "ICECREAM", aICECREAM); engine->RegisterEnumValue("Behavior", "BURGER", aBURGER); engine->RegisterEnumValue("Behavior", "PIZZA", aPIZZA); engine->RegisterEnumValue("Behavior", "FRIES", aFRIES); engine->RegisterEnumValue("Behavior", "CHICKENLEG", aCHICKLEG); engine->RegisterEnumValue("Behavior", "SANDWICH", aSANDWICH); engine->RegisterEnumValue("Behavior", "TACO", aTACOBELL); engine->RegisterEnumValue("Behavior", "WEENIE", aWEENIE); engine->RegisterEnumValue("Behavior", "HAM", aHAM); engine->RegisterEnumValue("Behavior", "CHEESE", aCHEESE); engine->RegisterEnumValue("Behavior", "FLOATLIZARD", aFLOATLIZARD); engine->RegisterEnumValue("Behavior", "STANDMONKEY", aSTANDMONKEY); engine->RegisterEnumValue("Behavior", "DESTRUCTSCENERY", aDESTRUCTSCENERY); engine->RegisterEnumValue("Behavior", "DESTRUCTSCENERYBOMB", aDESTRUCTSCENERYBOMB); engine->RegisterEnumValue("Behavior", "TNTDESTRUCTSCENERY", aDESTRUCTSCENERYBOMB); // Private/deprecated engine->RegisterEnumValue("Behavior", "COLLAPSESCENERY", aCOLLAPSESCENERY); engine->RegisterEnumValue("Behavior", "STOMPSCENERY", aSTOMPSCENERY); engine->RegisterEnumValue("Behavior", "GEMSTOMP", aGEMSTOMP); engine->RegisterEnumValue("Behavior", "RAVEN", aRAVEN); engine->RegisterEnumValue("Behavior", "TUBETURTLE", aTUBETURTLE); engine->RegisterEnumValue("Behavior", "GEMRING", aGEMRING); engine->RegisterEnumValue("Behavior", "SMALLTREE", aROTSMALLTREE); engine->RegisterEnumValue("Behavior", "AMBIENTSOUND", aAMBIENTSOUND); engine->RegisterEnumValue("Behavior", "UTERUS", aUTERUS); engine->RegisterEnumValue("Behavior", "CRAB", aCRAB); engine->RegisterEnumValue("Behavior", "WITCH", aWITCH); engine->RegisterEnumValue("Behavior", "ROCKETTURTLE", aROCKTURT); engine->RegisterEnumValue("Behavior", "BUBBA", aBUBBA); engine->RegisterEnumValue("Behavior", "DEVILDEVAN", aDEVILDEVAN); engine->RegisterEnumValue("Behavior", "DEVANROBOT", aDEVANROBOT); engine->RegisterEnumValue("Behavior", "ROBOT", aROBOT); engine->RegisterEnumValue("Behavior", "CARROTUSPOLE", aCARROTUSPOLE); engine->RegisterEnumValue("Behavior", "PSYCHPOLE", aPSYCHPOLE); engine->RegisterEnumValue("Behavior", "DIAMONDUSPOLE", aDIAMONDUSPOLE); engine->RegisterEnumValue("Behavior", "FRUITPLATFORM", aFRUITPLATFORM); engine->RegisterEnumValue("Behavior", "BOLLPLATFORM", aBOLLPLATFORM); engine->RegisterEnumValue("Behavior", "GRASSPLATFORM", aGRASSPLATFORM); engine->RegisterEnumValue("Behavior", "PINKPLATFORM", aPINKPLATFORM); engine->RegisterEnumValue("Behavior", "SONICPLATFORM", aSONICPLATFORM); engine->RegisterEnumValue("Behavior", "SPIKEPLATFORM", aSPIKEPLATFORM); engine->RegisterEnumValue("Behavior", "SPIKEBOLL", aSPIKEBOLL); engine->RegisterEnumValue("Behavior", "GENERATOR", aGENERATOR); engine->RegisterEnumValue("Behavior", "EVA", aEVA); engine->RegisterEnumValue("Behavior", "BUBBLER", aBUBBLER); engine->RegisterEnumValue("Behavior", "TNTPOWERUP", aTNTPOWER); engine->RegisterEnumValue("Behavior", "GUN8POWERUP", aGUN8POWER); engine->RegisterEnumValue("Behavior", "GUN9POWERUP", aGUN9POWER); engine->RegisterEnumValue("Behavior", "SPIKEBOLL3D", aSPIKEBOLL3D); engine->RegisterEnumValue("Behavior", "SPRINGCORD", aSPRINGCORD); engine->RegisterEnumValue("Behavior", "BEES", aBEES); engine->RegisterEnumValue("Behavior", "COPTER", aCOPTER); engine->RegisterEnumValue("Behavior", "LASERSHIELD", aLASERSHIELD); engine->RegisterEnumValue("Behavior", "STOPWATCH", aSTOPWATCH); engine->RegisterEnumValue("Behavior", "JUNGLEPOLE", aJUNGLEPOLE); engine->RegisterEnumValue("Behavior", "WARP", areaWARP); engine->RegisterEnumValue("Behavior", "BIGROCK", aBIGROCK); engine->RegisterEnumValue("Behavior", "BIGBOX", aBIGBOX); engine->RegisterEnumValue("Behavior", "TRIGGERSCENERY", aTRIGGERSCENERY); engine->RegisterEnumValue("Behavior", "BOLLY", aSONICBOSS); engine->RegisterEnumValue("Behavior", "BUTTERFLY", aBUTTERFLY); engine->RegisterEnumValue("Behavior", "BEEBOY", aBEEBOY); engine->RegisterEnumValue("Behavior", "SNOW", aSNOW); engine->RegisterEnumValue("Behavior", "TWEEDLEBOSS", aTWEEDLEBOSS); engine->RegisterEnumValue("Behavior", "AIRBOARD", aAIRBOARD); engine->RegisterEnumValue("Behavior", "CTFBASE", aFLAG); engine->RegisterEnumValue("Behavior", "XMASNORMTURTLE", aXNORMTURTLE); engine->RegisterEnumValue("Behavior", "XMASLIZARD", aXLIZARD); engine->RegisterEnumValue("Behavior", "XMASFLOATLIZARD", aXFLOATLIZARD); engine->RegisterEnumValue("Behavior", "XMASBILSY", aXBILSYBOSS); engine->RegisterEnumValue("Behavior", "CAT", aZCAT); engine->RegisterEnumValue("Behavior", "PACMANGHOST", aZGHOST); engine->RegisterEnumValue("Behavior", "WALKINGENEMY", aCOUNT + 1); engine->RegisterEnumValue("Behavior", "ROCKETTURTLEPLUS", aCOUNT + 2); engine->RegisterEnumValue("Behavior", "BOLLYTOP", aCOUNT + 3); engine->RegisterEnumValue("Behavior", "BOLLYBOTTOM", aCOUNT + 4); engine->RegisterEnumValue("Behavior", "PLATFORM", aCOUNT + 5); engine->RegisterEnumValue("Behavior", "SPRING", aCOUNT + 6); engine->RegisterEnumValue("Behavior", "AMMO15", aCOUNT + 7); engine->RegisterEnumValue("Behavior", "MONITOR", aCOUNT + 8); engine->RegisterEnumValue("Behavior", "CRATE", aCOUNT + 9); engine->RegisterEnumValue("Behavior", "PICKUP", aCOUNT + 10); engine->RegisterEnumValue("Behavior", "DIAMONDSAREFOREVER", aCOUNT + 11); engine->RegisterEnumValue("Behavior", "FLAG", aCOUNT + 12); engine->RegisterEnumValue("Behavior", "MONKEYBULLET", aCOUNT + 13); engine->RegisterEnumValue("Behavior", "BILSYBULLET", aCOUNT + 14); engine->RegisterEnumValue("Behavior", "BOLLYBULLET", aCOUNT + 15); engine->RegisterEnumValue("Behavior", "BOLLYSPIKEBALL", aCOUNT + 16); engine->RegisterEnumValue("Behavior", "WITCHBULLET", aCOUNT + 17); engine->RegisterEnumValue("Behavior", "TUFBOSSBULLET", aCOUNT + 18); engine->RegisterEnumValue("Behavior", "ROBOTSHARD", aCOUNT + 19); engine->RegisterEnumValue("Behavior", "BONE", aCOUNT + 20); engine->RegisterEnumValue("Behavior", "EXPLOSION2", aCOUNT + 21); engine->RegisterEnumValue("Behavior", "BURNING", aCOUNT + 22); engine->RegisterEnumValue("Behavior", "AIRBOARDFALL", aCOUNT + 23); engine->RegisterEnumValue("Behavior", "BIRDFEATHER", aCOUNT + 24); engine->RegisterEnumValue("Behavior", "UFO", aCOUNT + 25); engine->RegisterEnumValue("Behavior", "CORPSE", aCOUNT + 26); engine->RegisterEnumValue("Behavior", "BIGOBJECT", aCOUNT + 27); engine->SetDefaultNamespace("ANIM"); engine->RegisterEnumValue("Set", "AMMO", mAMMO); engine->RegisterEnumValue("Set", "BAT", mBAT); engine->RegisterEnumValue("Set", "BEEBOY", mBEEBOY); engine->RegisterEnumValue("Set", "BEES", mBEES); engine->RegisterEnumValue("Set", "BIGBOX", mBIGBOX); engine->RegisterEnumValue("Set", "BIGROCK", mBIGROCK); engine->RegisterEnumValue("Set", "BIGTREE", mBIGTREE); engine->RegisterEnumValue("Set", "BILSBOSS", mBILSBOSS); engine->RegisterEnumValue("Set", "BIRD", mBIRD); engine->RegisterEnumValue("Set", "BIRD3D", mBIRD3D); engine->RegisterEnumValue("Set", "BOLLPLAT", mBOLLPLAT); engine->RegisterEnumValue("Set", "BONUS", mBONUS); engine->RegisterEnumValue("Set", "BOSS", mBOSS); engine->RegisterEnumValue("Set", "BRIDGE", mBRIDGE); engine->RegisterEnumValue("Set", "BUBBA", mBUBBA); engine->RegisterEnumValue("Set", "BUMBEE", mBUMBEE); engine->RegisterEnumValue("Set", "BUTTERFLY", mBUTTERFLY); engine->RegisterEnumValue("Set", "CARROTPOLE", mCARROTPOLE); engine->RegisterEnumValue("Set", "CAT", mCAT); engine->RegisterEnumValue("Set", "CAT2", mCAT2); engine->RegisterEnumValue("Set", "CATERPIL", mCATERPIL); engine->RegisterEnumValue("Set", "CHUCK", mCHUCK); engine->RegisterEnumValue("Set", "COMMON", mCOMMON); engine->RegisterEnumValue("Set", "CONTINUE", mCONTINUE); engine->RegisterEnumValue("Set", "DEMON", mDEMON); engine->RegisterEnumValue("Set", "DESTSCEN", mDESTSCEN); engine->RegisterEnumValue("Set", "DEVAN", mDEVAN); engine->RegisterEnumValue("Set", "DEVILDEVAN", mDEVILDEVAN); engine->RegisterEnumValue("Set", "DIAMPOLE", mDIAMPOLE); engine->RegisterEnumValue("Set", "DOG", mDOG); engine->RegisterEnumValue("Set", "DOOR", mDOOR); engine->RegisterEnumValue("Set", "DRAGFLY", mDRAGFLY); engine->RegisterEnumValue("Set", "DRAGON", mDRAGON); engine->RegisterEnumValue("Set", "EVA", mEVA); engine->RegisterEnumValue("Set", "FACES", mFACES); engine->RegisterEnumValue("Set", "FATCHK", mFATCHK); engine->RegisterEnumValue("Set", "FENCER", mFENCER); engine->RegisterEnumValue("Set", "FISH", mFISH); engine->RegisterEnumValue("Set", "FLAG", mFLAG); engine->RegisterEnumValue("Set", "FLARE", mFLARE); engine->RegisterEnumValue("Set", "FONT", mFONT); engine->RegisterEnumValue("Set", "FROG", mFROG); engine->RegisterEnumValue("Set", "FRUITPLAT", mFRUITPLAT); engine->RegisterEnumValue("Set", "GEMRING", mGEMRING); engine->RegisterEnumValue("Set", "GLOVE", mGLOVE); engine->RegisterEnumValue("Set", "GRASSPLAT", mGRASSPLAT); engine->RegisterEnumValue("Set", "HATTER", mHATTER); engine->RegisterEnumValue("Set", "HELMUT", mHELMUT); engine->RegisterEnumValue("Set", "JAZZ", mJAZZ); engine->RegisterEnumValue("Set", "JAZZ3D", mJAZZ3D); engine->RegisterEnumValue("Set", "JUNGLEPOLE", mJUNGLEPOLE); engine->RegisterEnumValue("Set", "LABRAT", mLABRAT); engine->RegisterEnumValue("Set", "LIZARD", mLIZARD); engine->RegisterEnumValue("Set", "LORI", mLORI); engine->RegisterEnumValue("Set", "LORI2", mLORI2); engine->RegisterEnumValue("Set", "MENU", mMENU); engine->RegisterEnumValue("Set", "MENUFONT", mMENUFONT); engine->RegisterEnumValue("Set", "MONKEY", mMONKEY); engine->RegisterEnumValue("Set", "MOTH", mMOTH); engine->RegisterEnumValue("Set", "PICKUPS", mPICKUPS); engine->RegisterEnumValue("Set", "PINBALL", mPINBALL); engine->RegisterEnumValue("Set", "PINKPLAT", mPINKPLAT); engine->RegisterEnumValue("Set", "PSYCHPOLE", mPSYCHPOLE); engine->RegisterEnumValue("Set", "QUEEN", mQUEEN); engine->RegisterEnumValue("Set", "RAPIER", mRAPIER); engine->RegisterEnumValue("Set", "RAVEN", mRAVEN); engine->RegisterEnumValue("Set", "ROBOT", mROBOT); engine->RegisterEnumValue("Set", "ROCK", mROCK); engine->RegisterEnumValue("Set", "ROCKTURT", mROCKTURT); engine->RegisterEnumValue("Set", "SKELETON", mSKELETON); engine->RegisterEnumValue("Set", "SMALTREE", mSMALTREE); engine->RegisterEnumValue("Set", "SNOW", mSNOW); engine->RegisterEnumValue("Set", "SONCSHIP", mSONCSHIP); engine->RegisterEnumValue("Set", "SONICSHIP", mSONCSHIP); // Private/deprecated engine->RegisterEnumValue("Set", "SONICPLAT", mSONICPLAT); engine->RegisterEnumValue("Set", "SPARK", mSPARK); engine->RegisterEnumValue("Set", "SPAZ", mSPAZ); engine->RegisterEnumValue("Set", "SPAZ2", mSPAZ2); engine->RegisterEnumValue("Set", "SPAZ3D", mSPAZ3D); engine->RegisterEnumValue("Set", "SPIKEBOLL", mSPIKEBOLL); engine->RegisterEnumValue("Set", "SPIKEBOLL3D", mSPIKEBOLL3D); engine->RegisterEnumValue("Set", "SPIKEPLAT", mSPIKEPLAT); engine->RegisterEnumValue("Set", "SPRING", mSPRING); engine->RegisterEnumValue("Set", "STEAM", mSTEAM); engine->RegisterEnumValue("Set", "SUCKER", mSUCKER); engine->RegisterEnumValue("Set", "TUBETURT", mTUBETURT); engine->RegisterEnumValue("Set", "TUFBOSS", mTUFBOSS); engine->RegisterEnumValue("Set", "TUFTUR", mTUFTURT); engine->RegisterEnumValue("Set", "TURTLE", mTURTLE); engine->RegisterEnumValue("Set", "TWEEDLE", mTWEEDLE); engine->RegisterEnumValue("Set", "UTERUS", mUTERUS); engine->RegisterEnumValue("Set", "VINE", mVINE); engine->RegisterEnumValue("Set", "WARP10", mWARP10); engine->RegisterEnumValue("Set", "WARP100", mWARP100); engine->RegisterEnumValue("Set", "WARP20", mWARP20); engine->RegisterEnumValue("Set", "WARP50", mWARP50); engine->RegisterEnumValue("Set", "WITCH", mWITCH); engine->RegisterEnumValue("Set", "XBILSY", mXBILSY); engine->RegisterEnumValue("Set", "XLIZARD", mXLIZARD); engine->RegisterEnumValue("Set", "XTURTLE", mXTURTLE); engine->RegisterEnumValue("Set", "ZDOG", mZDOG); engine->RegisterEnumValue("Set", "ZSPARK", mZSPARK); engine->RegisterEnumValue("Set", "PLUS_AMMO", mZZAMMO); engine->RegisterEnumValue("Set", "PLUS_BETA", mZZBETA); engine->RegisterEnumValue("Set", "PLUS_COMMON", mZZCOMMON); engine->RegisterEnumValue("Set", "PLUS_CONTINUE", mZZCONTINUE); engine->RegisterEnumValue("Set", "PLUS_FONT", mZZFONT); engine->RegisterEnumValue("Set", "PLUS_MENUFONT", mZZMENUFONT); engine->RegisterEnumValue("Set", "PLUS_REPLACEMENTS", mZZREPLACEMENTS); engine->RegisterEnumValue("Set", "PLUS_RETICLES", mZZRETICLES); engine->RegisterEnumValue("Set", "PLUS_SCENERY", mZZSCENERY); engine->RegisterEnumValue("Set", "PLUS_WARP", mZZWARP); engine->RegisterGlobalFunction("Set get_CUSTOM(uint8)", asFUNCTION(getCustomSetID), asCALL_CDECL); engine->SetDefaultNamespace("RABBIT"); engine->RegisterEnum("Anim"); engine->RegisterEnumValue("Anim", "AIRBOARD", mJAZZ_AIRBOARD); engine->RegisterEnumValue("Anim", "AIRBOARDTURN", mJAZZ_AIRBOARDTURN); engine->RegisterEnumValue("Anim", "BUTTSTOMPLAND", mJAZZ_BUTTSTOMPLAND); engine->RegisterEnumValue("Anim", "CORPSE", mJAZZ_CORPSE); engine->RegisterEnumValue("Anim", "DIE", mJAZZ_DIE); engine->RegisterEnumValue("Anim", "DIVE", mJAZZ_DIVE); engine->RegisterEnumValue("Anim", "DIVEFIREQUIT", mJAZZ_DIVEFIREQUIT); engine->RegisterEnumValue("Anim", "DIVEFIRERIGHT", mJAZZ_DIVEFIRERIGHT); engine->RegisterEnumValue("Anim", "DIVEUP", mJAZZ_DIVEUP); engine->RegisterEnumValue("Anim", "EARBRACHIATE", mJAZZ_EARBRACHIATE); engine->RegisterEnumValue("Anim", "ENDOFLEVEL", mJAZZ_ENDOFLEVEL); engine->RegisterEnumValue("Anim", "FALL", mJAZZ_FALL); engine->RegisterEnumValue("Anim", "FALLBUTTSTOMP", mJAZZ_FALLBUTTSTOMP); engine->RegisterEnumValue("Anim", "FALLLAND", mJAZZ_FALLLAND); engine->RegisterEnumValue("Anim", "FIRE", mJAZZ_FIRE); engine->RegisterEnumValue("Anim", "FIREUP", mJAZZ_FIREUP); engine->RegisterEnumValue("Anim", "FIREUPQUIT", mJAZZ_FIREUPQUIT); engine->RegisterEnumValue("Anim", "FROG", mJAZZ_FROG); engine->RegisterEnumValue("Anim", "HANGFIREQUIT", mJAZZ_HANGFIREQUIT); engine->RegisterEnumValue("Anim", "HANGFIREREST", mJAZZ_HANGFIREREST); engine->RegisterEnumValue("Anim", "HANGFIREUP", mJAZZ_HANGFIREUP); engine->RegisterEnumValue("Anim", "HANGIDLE1", mJAZZ_HANGIDLE1); engine->RegisterEnumValue("Anim", "HANGIDLE2", mJAZZ_HANGIDLE2); engine->RegisterEnumValue("Anim", "HANGINGFIREQUIT", mJAZZ_HANGINGFIREQUIT); engine->RegisterEnumValue("Anim", "HANGINGFIRERIGHT", mJAZZ_HANGINGFIRERIGHT); engine->RegisterEnumValue("Anim", "HELICOPTER", mJAZZ_HELICOPTER); engine->RegisterEnumValue("Anim", "HELICOPTERFIREQUIT", mJAZZ_HELICOPTERFIREQUIT); engine->RegisterEnumValue("Anim", "HELICOPTERFIRERIGHT", mJAZZ_HELICOPTERFIRERIGHT); engine->RegisterEnumValue("Anim", "HPOLE", mJAZZ_HPOLE); engine->RegisterEnumValue("Anim", "HURT", mJAZZ_HURT); engine->RegisterEnumValue("Anim", "IDLE1", mJAZZ_IDLE1); engine->RegisterEnumValue("Anim", "IDLE2", mJAZZ_IDLE2); engine->RegisterEnumValue("Anim", "IDLE3", mJAZZ_IDLE3); engine->RegisterEnumValue("Anim", "IDLE4", mJAZZ_IDLE4); engine->RegisterEnumValue("Anim", "IDLE5", mJAZZ_IDLE5); engine->RegisterEnumValue("Anim", "JUMPFIREQUIT", mJAZZ_JUMPFIREQUIT); engine->RegisterEnumValue("Anim", "JUMPFIRERIGHT", mJAZZ_JUMPFIRERIGHT); engine->RegisterEnumValue("Anim", "JUMPING1", mJAZZ_JUMPING1); engine->RegisterEnumValue("Anim", "JUMPING2", mJAZZ_JUMPING2); engine->RegisterEnumValue("Anim", "JUMPING3", mJAZZ_JUMPING3); engine->RegisterEnumValue("Anim", "LEDGEWIGGLE", mJAZZ_LEDGEWIGGLE); engine->RegisterEnumValue("Anim", "LIFT", mJAZZ_LIFT); engine->RegisterEnumValue("Anim", "LIFTJUMP", mJAZZ_LIFTJUMP); engine->RegisterEnumValue("Anim", "LIFTLAND", mJAZZ_LIFTLAND); engine->RegisterEnumValue("Anim", "LOOKUP", mJAZZ_LOOKUP); engine->RegisterEnumValue("Anim", "LOOPY", mJAZZ_LOOPY); engine->RegisterEnumValue("Anim", "PUSH", mJAZZ_PUSH); engine->RegisterEnumValue("Anim", "QUIT", mJAZZ_QUIT); engine->RegisterEnumValue("Anim", "REV1", mJAZZ_REV1); engine->RegisterEnumValue("Anim", "REV2", mJAZZ_REV2); engine->RegisterEnumValue("Anim", "REV3", mJAZZ_REV3); engine->RegisterEnumValue("Anim", "RIGHTFALL", mJAZZ_RIGHTFALL); engine->RegisterEnumValue("Anim", "RIGHTJUMP", mJAZZ_RIGHTJUMP); engine->RegisterEnumValue("Anim", "ROLLING", mJAZZ_ROLLING); engine->RegisterEnumValue("Anim", "RUN1", mJAZZ_RUN1); engine->RegisterEnumValue("Anim", "RUN2", mJAZZ_RUN2); engine->RegisterEnumValue("Anim", "RUN3", mJAZZ_RUN3); engine->RegisterEnumValue("Anim", "SKID1", mJAZZ_SKID1); engine->RegisterEnumValue("Anim", "SKID2", mJAZZ_SKID2); engine->RegisterEnumValue("Anim", "SKID3", mJAZZ_SKID3); engine->RegisterEnumValue("Anim", "SPRING", mJAZZ_SPRING); engine->RegisterEnumValue("Anim", "STAND", mJAZZ_STAND); engine->RegisterEnumValue("Anim", "STATIONARYJUMP", mJAZZ_STATIONARYJUMP); engine->RegisterEnumValue("Anim", "STATIONARYJUMPEND", mJAZZ_STATIONARYJUMPEND); engine->RegisterEnumValue("Anim", "STATIONARYJUMPSTART", mJAZZ_STATIONARYJUMPSTART); engine->RegisterEnumValue("Anim", "STONED", mJAZZ_STONED); engine->RegisterEnumValue("Anim", "SWIMDOWN", mJAZZ_SWIMDOWN); engine->RegisterEnumValue("Anim", "SWIMRIGHT", mJAZZ_SWIMRIGHT); engine->RegisterEnumValue("Anim", "SWIMTURN1", mJAZZ_SWIMTURN1); engine->RegisterEnumValue("Anim", "SWIMTURN2", mJAZZ_SWIMTURN2); engine->RegisterEnumValue("Anim", "SWIMUP", mJAZZ_SWIMUP); engine->RegisterEnumValue("Anim", "SWINGINGVINE", mJAZZ_SWINGINGVINE); engine->RegisterEnumValue("Anim", "TELEPORT", mJAZZ_TELEPORT); engine->RegisterEnumValue("Anim", "TELEPORTFALL", mJAZZ_TELEPORTFALL); engine->RegisterEnumValue("Anim", "TELEPORTFALLING", mJAZZ_TELEPORTFALLING); engine->RegisterEnumValue("Anim", "TELEPORTFALLTELEPORT", mJAZZ_TELEPORTFALLTELEPORT); engine->RegisterEnumValue("Anim", "TELEPORTSTAND", mJAZZ_TELEPORTSTAND); engine->RegisterEnumValue("Anim", "VPOLE", mJAZZ_VPOLE); // Create fake MLLE namespace, because "MLLE-Include-xxx.asc" includes are blocked engine->SetDefaultNamespace("MLLE"); engine->RegisterGlobalFunction("bool Setup()", asFUNCTION(mlleSetup), asCALL_CDECL); engine->RegisterGlobalFunction("void ReapplyPalette()", asFUNCTION(mlleReapplyPalette), asCALL_CDECL); engine->RegisterGlobalFunction("void SpawnOffgrids()", asFUNCTION(mlleSpawnOffgrids), asCALL_CDECL); engine->RegisterGlobalFunction("void SpawnOffgridsLocal()", asFUNCTION(mlleSpawnOffgridsLocal), asCALL_CDECL); engine->RegisterGlobalProperty("const jjPAL Palette", &jjBackupPalette); } void LevelScriptLoader::RegisterStandardFunctions(asIScriptEngine* engine, asIScriptModule* module) { int r; r = engine->RegisterGlobalFunction("int Random()", asFUNCTIONPR(asRandom, (), int), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("int Random(int)", asFUNCTIONPR(asRandom, (int), int), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float Random(float, float)", asFUNCTIONPR(asRandom, (float, float), float), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void Print(const string &in)", asFUNCTION(asScript), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("uint8 get_Difficulty()", asFUNCTION(asGetDifficulty), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("bool get_IsReforged()", asFUNCTION(asIsReforged), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("int get_LevelWidth()", asFUNCTION(asGetLevelWidth), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("int get_LevelHeight()", asFUNCTION(asGetLevelHeight), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float get_ElapsedFrames()", asFUNCTION(asGetElapsedFrames), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float get_AmbientLight()", asFUNCTION(asGetAmbientLight), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void set_AmbientLight(float)", asFUNCTION(asSetAmbientLight), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("float get_WaterLevel()", asFUNCTION(asGetWaterLevel), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void set_WaterLevel(float)", asFUNCTION(asSetWaterLevel), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void PreloadMetadata(const string &in)", asFUNCTION(asPreloadMetadata), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void RegisterSpawnable(int, const string &in)", asFUNCTION(asRegisterSpawnable), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void Spawn(int, int, int)", asFUNCTION(asSpawnEvent), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void Spawn(int, int, int, const array &in)", asFUNCTION(asSpawnEventParams), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void Spawn(const string &in, int, int)", asFUNCTION(asSpawnType), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void Spawn(const string &in, int, int, const array &in)", asFUNCTION(asSpawnTypeParams), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void ChangeLevel(int, const string &in = string())", asFUNCTION(asChangeLevel), asCALL_CDECL); RETURN_ASSERT(r >= 0); //r = engine->RegisterGlobalFunction("void MusicPlay(const string &in)", asFUNCTION(asMusicPlay), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void ShowLevelText(const string &in)", asFUNCTION(asShowLevelText), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("void SetWeather(uint8, uint8)", asFUNCTION(asSetWeather), asCALL_CDECL); RETURN_ASSERT(r >= 0); // Game-specific classes ScriptActorWrapper::RegisterFactory(engine, module); ScriptPlayerWrapper::RegisterFactory(engine); } jjPLAYER* LevelScriptLoader::GetPlayerBackingStore(Actors::Player* player) { auto it = _playerBackingStore.find(player->GetPlayerIndex()); if (it != _playerBackingStore.end()) { it->second->SyncPropertiesToBackingStore(); return it->second.get(); } auto [result, success] = _playerBackingStore.emplace(player->GetPlayerIndex(), std::make_unique(this, player)); DEATH_DEBUG_ASSERT(success); result->second->SyncPropertiesToBackingStore(); return result->second.get(); } jjPLAYER* LevelScriptLoader::GetPlayerBackingStore(std::int32_t playerIndex) { std::uint8_t playerIndex8 = (std::uint8_t)playerIndex; auto it = _playerBackingStore.find(playerIndex8); if (it != _playerBackingStore.end()) { it->second->SyncPropertiesToBackingStore(); return it->second.get(); } auto& players = _levelHandler->_players; auto** pp = std::find_if(players.begin(), players.end(), [playerIndex8](Actors::Player* p) { return (p->GetPlayerIndex() == playerIndex8); }); if (pp == players.end()) { return nullptr; } auto [result, success] = _playerBackingStore.emplace(playerIndex8, std::make_unique(this, *pp)); DEATH_DEBUG_ASSERT(success); result->second->SyncPropertiesToBackingStore(); return result->second.get(); } Actors::ActorBase* LevelScriptLoader::CreateActorInstance(StringView typeName) { auto nullTerminatedTypeName = String::nullTerminatedView(typeName); // Create an instance of the ActorBase script class that inherits from the ScriptActorWrapper C++ class asITypeInfo* typeInfo = GetMainModule()->GetTypeInfoByName(nullTerminatedTypeName.data()); if (typeInfo == nullptr) { return nullptr; } asIScriptObject* obj = static_cast(GetEngine()->CreateScriptObject(typeInfo)); // Get the pointer to the C++ side of the ActorBase class ScriptActorWrapper* obj2 = *static_cast(obj->GetAddressOfProperty(0)); // Increase the reference count to the C++ object, as this is what will be used to control the life time of the object from the application side obj2->AddRef(); // Release the reference to the script side obj->Release(); return obj2; } ArrayView LevelScriptLoader::GetPlayers() const { return _levelHandler->_players; } uint8_t LevelScriptLoader::asGetDifficulty() { auto _this = ScriptLoader::FromActiveContext(); return (uint8_t)_this->_levelHandler->_difficulty; } bool LevelScriptLoader::asIsReforged() { auto _this = ScriptLoader::FromActiveContext(); return (uint8_t)_this->_levelHandler->_isReforged; } int LevelScriptLoader::asGetLevelWidth() { auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_tileMap->GetLevelBounds().X; } int LevelScriptLoader::asGetLevelHeight() { auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_tileMap->GetLevelBounds().Y; } float LevelScriptLoader::asGetElapsedFrames() { auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_elapsedFrames; } float LevelScriptLoader::asGetAmbientLight() { auto _this = ScriptLoader::FromActiveContext(); // TODO: Viewports return _this->_levelHandler->_defaultAmbientLight.W; } void LevelScriptLoader::asSetAmbientLight(float value) { auto _this = ScriptLoader::FromActiveContext(); // TODO: Viewports _this->_levelHandler->_defaultAmbientLight.W = value; } float LevelScriptLoader::asGetWaterLevel() { auto _this = ScriptLoader::FromActiveContext(); return _this->_levelHandler->_waterLevel; } void LevelScriptLoader::asSetWaterLevel(float value) { auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->_waterLevel = value; } void LevelScriptLoader::asPreloadMetadata(const String& path) { ContentResolver::Get().PreloadMetadataAsync(path); } void LevelScriptLoader::asRegisterSpawnable(std::int32_t eventType, const String& typeName) { auto _this = ScriptLoader::FromActiveContext(); asITypeInfo* typeInfo = _this->GetMainModule()->GetTypeInfoByName(typeName.data()); if (typeInfo == nullptr) { return; } bool added = _this->_eventTypeToTypeInfo.emplace(eventType, typeInfo).second; if (added) { _this->_levelHandler->EventSpawner()->RegisterSpawnable((EventType)eventType, asRegisterSpawnableCallback); } } std::shared_ptr LevelScriptLoader::asRegisterSpawnableCallback(const Actors::ActorActivationDetails& details) { if (auto* levelHandler = runtime_cast(details.LevelHandler)) { auto _this = levelHandler->_scripts.get(); // Spawn() function with custom event cannot be used in OnLevelLoad(), because _scripts is not assigned yet if (_this != nullptr) { auto it = _this->_eventTypeToTypeInfo.find((int)details.Type); if (it != _this->_eventTypeToTypeInfo.end()) { asIScriptObject* obj = static_cast(_this->GetEngine()->CreateScriptObject(it->second)); ScriptActorWrapper* obj2 = *static_cast(obj->GetAddressOfProperty(0)); obj2->AddRef(); obj->Release(); obj2->OnActivated(details); return std::shared_ptr(obj2); } } } return nullptr; } void LevelScriptLoader::asSpawnEvent(std::int32_t eventType, std::int32_t x, std::int32_t y) { auto _this = ScriptLoader::FromActiveContext(); uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] { }; auto actor = _this->_levelHandler->EventSpawner()->SpawnEvent((EventType)eventType, spawnParams, Actors::ActorState::None, Vector3i(x, y, ILevelHandler::MainPlaneZ)); if (actor != nullptr) { _this->_levelHandler->AddActor(actor); } } void LevelScriptLoader::asSpawnEventParams(std::int32_t eventType, std::int32_t x, std::int32_t y, const CScriptArray& eventParams) { auto _this = ScriptLoader::FromActiveContext(); uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] { }; int size = eventParams.GetSize(); std::memcpy(spawnParams, eventParams.At(0), size); auto actor = _this->_levelHandler->EventSpawner()->SpawnEvent((EventType)eventType, spawnParams, Actors::ActorState::None, Vector3i(x, y, ILevelHandler::MainPlaneZ)); if (actor != nullptr) { _this->_levelHandler->AddActor(actor); } } void LevelScriptLoader::asSpawnType(const String& typeName, std::int32_t x, std::int32_t y) { auto _this = ScriptLoader::FromActiveContext(); auto actor = _this->CreateActorInstance(typeName); if (actor == nullptr) { return; } uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] { }; actor->OnActivated(Actors::ActorActivationDetails( _this->_levelHandler, Vector3i(x, y, ILevelHandler::MainPlaneZ), spawnParams )); _this->_levelHandler->AddActor(std::shared_ptr(actor)); } void LevelScriptLoader::asSpawnTypeParams(const String& typeName, std::int32_t x, std::int32_t y, const CScriptArray& eventParams) { auto _this = ScriptLoader::FromActiveContext(); auto actor = _this->CreateActorInstance(typeName); if (actor == nullptr) { return; } uint8_t spawnParams[Events::EventSpawner::SpawnParamsSize] { }; int size = eventParams.GetSize(); std::memcpy(spawnParams, eventParams.At(0), size); actor->OnActivated(Actors::ActorActivationDetails( _this->_levelHandler, Vector3i(x, y, ILevelHandler::MainPlaneZ), spawnParams )); _this->_levelHandler->AddActor(std::shared_ptr(actor)); } void LevelScriptLoader::asChangeLevel(std::int32_t exitType, const String& path) { auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->BeginLevelChange(nullptr, (ExitType)exitType, path); } void LevelScriptLoader::asShowLevelText(const String& text) { auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->ShowLevelText(text); } void LevelScriptLoader::asSetWeather(std::uint8_t weatherType, std::uint8_t intensity) { auto _this = ScriptLoader::FromActiveContext(); _this->_levelHandler->SetWeather((WeatherType)weatherType, intensity); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/LevelScriptLoader.h000066400000000000000000000302561512772601700272370ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include "ScriptLoader.h" #include "JJ2PlusDefinitions.h" #include "../ILevelHandler.h" #include "../../nCine/Base/BitArray.h" namespace Jazz2::UI { class HUD; } namespace Jazz2::Scripting { namespace Legacy { class jjPLAYER; } /** @brief Specifies a part to be drawn in @ref LevelScriptLoader::OnDraw() */ enum class DrawType { WeaponAmmo, Health, Lives, PlayerTimer, Score, GameModeHUD }; /** @brief Binds an **AngelScript** script file to @ref LevelHandler and executes the script @experimental */ class LevelScriptLoader : public ScriptLoader { friend class Legacy::jjPLAYER; public: LevelScriptLoader(LevelHandler* levelHandler, StringView scriptPath); /** @brief Returns list of players */ ArrayView GetPlayers() const; /** @brief Returns script backing store for specified player */ jjPLAYER* GetPlayerBackingStore(Actors::Player* player); /** @overload */ jjPLAYER* GetPlayerBackingStore(std::int32_t playerIndex); /** @brief Called when a level is loaded */ void OnLevelLoad(); /** @brief Called when a level begins */ void OnLevelBegin(); /** @brief Called when a level reloads */ void OnLevelReload(); /** @brief Called when a level updates (on the beginning of each frame) */ void OnLevelUpdate(float timeMult); /** @brief Called when a level callback is triggered */ void OnLevelCallback(Actors::ActorBase* initiator, std::uint8_t* eventParams); /** @brief Called when a part of viewport or HUD is drawn */ bool OnDraw(UI::HUD* hud, Actors::Player* player, const Rectf& view, DrawType type); /** @brief Called when a player dies */ void OnPlayerDied(Actors::Player* player, Actors::ActorBase* collider); protected: String OnProcessInclude(StringView includePath, StringView scriptPath) override; void OnProcessPragma(StringView content, ScriptContextType& contextType) override; /** @brief Called before a script function is called */ void OnBeforeScriptCall(); /** @brief Called after a script function is called */ void OnAfterScriptCall(); private: LevelHandler* _levelHandler; asIScriptFunction* _onLevelUpdate; std::int32_t _onLevelUpdateLastFrame; asIScriptFunction* _onDrawAmmo; asIScriptFunction* _onDrawHealth; asIScriptFunction* _onDrawLives; asIScriptFunction* _onDrawPlayerTimer; asIScriptFunction* _onDrawScore; asIScriptFunction* _onDrawGameModeHUD; HashMap _eventTypeToTypeInfo; BitArray _enabledCallbacks; HashMap> _playerBackingStore; // Global scripting variables static constexpr std::int32_t FLAG_HFLIPPED_TILE = 0x1000; static constexpr std::int32_t FLAG_VFLIPPED_TILE = 0x2000; static constexpr std::int32_t FLAG_ANIMATED_TILE = 0x4000; jjPAL jjPalette; jjPAL jjBackupPalette; std::int32_t jjObjectCount = 0; std::int32_t jjObjectMax = 0; std::int32_t gameMode = 0; std::int32_t customMode = 0; std::int32_t partyMode = 0; bool jjAutoWeaponChange = true; std::uint32_t jjScriptModuleID = 0; std::uint32_t gameTicksSpentWhileActive = 0; std::int32_t renderFrame = 0; bool versionTSF = true; bool isServer = false; bool jjDeactivatingBecauseOfDeath = false; std::int32_t DifficultyForNextLevel = 0; std::int32_t DifficultyAtLevelStart = 0; std::uint32_t numberOfTiles = 0; bool parLowDetail = false; std::int32_t colorDepth = 0; std::int32_t checkedMaxSubVideoWidth = 0; std::int32_t checkedMaxSubVideoHeight = 0; std::int32_t realVideoW = 0; std::int32_t realVideoH = 0; std::int32_t subVideoW = 0; std::int32_t subVideoH = 0; bool snowing = false; bool snowingOutdoors = false; std::uint8_t snowingIntensity = 0; std::int32_t snowingType = 0; std::int32_t maxScore = 0; std::int32_t waterLightMode = 0; std::int32_t waterInteraction = 0; std::uint8_t ChatKey = 0; bool soundEnabled = false; bool soundFXActive = false; bool musicActive = false; std::int32_t soundFXVolume = false; std::int32_t musicVolume = false; std::int32_t levelEcho = 0; bool warpsTransmuteCoins = false; bool delayGeneratedCrateOrigins = false; bool g_levelHasFood = false; std::int32_t enforceAmbientLighting = 0; LevelScriptLoader(const LevelScriptLoader&) = delete; LevelScriptLoader& operator=(const LevelScriptLoader&) = delete; Actors::ActorBase* CreateActorInstance(StringView typeName); static void RegisterBuiltInFunctions(asIScriptEngine* engine); void RegisterLegacyFunctions(asIScriptEngine* engine); void RegisterStandardFunctions(asIScriptEngine* engine, asIScriptModule* module); static std::uint8_t asGetDifficulty(); static bool asIsReforged(); static std::int32_t asGetLevelWidth(); static std::int32_t asGetLevelHeight(); static float asGetElapsedFrames(); static float asGetAmbientLight(); static void asSetAmbientLight(float value); static float asGetWaterLevel(); static void asSetWaterLevel(float value); static void asPreloadMetadata(const String& path); static void asRegisterSpawnable(std::int32_t eventType, const String& typeName); static std::shared_ptr asRegisterSpawnableCallback(const Actors::ActorActivationDetails& details); static void asSpawnEvent(std::int32_t eventType, std::int32_t x, std::int32_t y); static void asSpawnEventParams(std::int32_t eventType, std::int32_t x, std::int32_t y, const CScriptArray& eventParams); static void asSpawnType(const String& typeName, std::int32_t x, std::int32_t y); static void asSpawnTypeParams(const String& typeName, std::int32_t x, std::int32_t y, const CScriptArray& eventParams); static void asChangeLevel(std::int32_t exitType, const String& path); static void asShowLevelText(const String& text); static void asSetWeather(std::uint8_t weatherType, std::uint8_t intensity); static std::int32_t GetDifficulty(); static std::int32_t SetDifficulty(std::int32_t value); static String get_jjMusicFileName(); static String get_jjHelpStrings(std::uint32_t index); static void set_jjHelpStrings(std::uint32_t index, const String& text); static void jjAlert(const String& text, bool sendToAll, std::uint32_t size); static void jjPrint(const String& text, bool timestamp); static void jjDebug(const String& text, bool timestamp); static void jjChat(const String& text, bool teamchat); static void jjConsole(const String& text, bool sendToAll); static void jjSpy(const String& text); static float get_layerXOffset(std::uint8_t id); static float set_layerXOffset(std::uint8_t id, float value); static float get_layerYOffset(std::uint8_t id); static float set_layerYOffset(std::uint8_t id, float value); static std::int32_t get_layerWidth(std::uint8_t id); static std::int32_t get_layerRealWidth(std::uint8_t id); static std::int32_t get_layerRoundedWidth(std::uint8_t id); static std::int32_t get_layerHeight(std::uint8_t id); static float get_layerXSpeed(std::uint8_t id); static float set_layerXSpeed(std::uint8_t id, float value); static float get_layerYSpeed(std::uint8_t id); static float set_layerYSpeed(std::uint8_t id, float value); static float get_layerXAutoSpeed(std::uint8_t id); static float set_layerXAutoSpeed(std::uint8_t id, float value); static float get_layerYAutoSpeed(std::uint8_t id); static float set_layerYAutoSpeed(std::uint8_t id, float value); static bool get_layerHasTiles(std::uint8_t id); static bool set_layerHasTiles(std::uint8_t id, bool value); static bool get_layerTileHeight(std::uint8_t id); static bool set_layerTileHeight(std::uint8_t id, bool value); static bool get_layerTileWidth(std::uint8_t id); static bool set_layerTileWidth(std::uint8_t id, bool value); static bool get_layerLimitVisibleRegion(std::uint8_t id); static bool set_layerLimitVisibleRegion(std::uint8_t id, bool value); static void setLayerXSpeedSeamlessly(std::uint8_t id, float newspeed, bool newSpeedIsAnAutoSpeed); static void setLayerYSpeedSeamlessly(std::uint8_t id, float newspeed, bool newSpeedIsAnAutoSpeed); static void jjDrawPixel(float xPixel, float yPixel, std::uint8_t color, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawRectangle(float xPixel, float yPixel, std::int32_t width, std::int32_t height, std::uint8_t color, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawSprite(float xPixel, float yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, std::int8_t direction, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, std::int8_t direction, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawResizedSprite(float xPixel, float yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawResizedSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawRotatedSprite(float xPixel, float yPixel, std::int32_t setID, std::uint8_t animation, std::uint8_t frame, std::int32_t angle, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawRotatedSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, std::int32_t angle, float xScale, float yScale, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawSwingingVineSpriteFromCurFrame(float xPixel, float yPixel, std::uint32_t sprite, std::int32_t length, std::int32_t curvature, spriteType mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawTile(float xPixel, float yPixel, std::uint16_t tile, std::uint32_t tileQuadrant, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawString(float xPixel, float yPixel, const String& text, std::uint32_t size, std::uint32_t mode, std::uint8_t param, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static void jjDrawStringEx(float xPixel, float yPixel, const String& text, std::uint32_t size, const jjTEXTAPPEARANCE& appearance, std::uint8_t param1, spriteType spriteMode, std::uint8_t param2, std::int8_t layerZ, std::uint8_t layerXY, std::int8_t playerID); static std::int32_t jjGetStringWidth(const String& text, std::uint32_t size, const jjTEXTAPPEARANCE& style); static bool get_jjTriggers(std::uint8_t id); static bool set_jjTriggers(std::uint8_t id, bool value); static bool jjSwitchTrigger(std::uint8_t id); static bool isNumberedASFunctionEnabled(std::uint8_t id); static bool setNumberedASFunctionEnabled(std::uint8_t id, bool value); static void reenableAllNumberedASFunctions(); static float getWaterLevel(); static float getWaterLevel2(); static float setWaterLevel(float value, bool instant); static float get_waterChangeSpeed(); static float set_waterChangeSpeed(float value); static std::int32_t get_waterLayer(); static std::int32_t set_waterLayer(std::int32_t value); static void setWaterGradient(std::uint8_t red1, std::uint8_t green1, std::uint8_t blue1, std::uint8_t red2, std::uint8_t green2, std::uint8_t blue2); static void setWaterGradientFromColors(jjPALCOLOR color1, jjPALCOLOR color2); static void setWaterGradientToTBG(); static void resetWaterGradient(); static void triggerRock(std::uint8_t id); static void cycleTo(const String& filename, bool warp, bool fast); static void jjNxt(bool warp, bool fast); static bool jjMusicLoad(const String& filename, bool forceReload, bool temporary); static void jjMusicStop(); static void jjMusicPlay(); static void jjMusicPause(); static void jjMusicResume(); static bool jjSendPacket(const jjSTREAM& packet, std::int32_t toClientID, std::uint32_t toScriptModuleID); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterArray.cpp000066400000000000000000001667751512772601700270120ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "RegisterArray.h" #include "../../Main.h" #include "../../nCine/Base/Algorithms.h" #include #include #include #include #define AS_USE_ACCESSORS 1 using namespace Death::Containers; using namespace Death::Containers::Literals; namespace Jazz2::Scripting { struct SArrayBuffer { std::uint32_t maxElements; std::uint32_t numElements; std::uint8_t data[1]; }; struct SArrayCache { asIScriptFunction* cmpFunc; asIScriptFunction* eqFunc; std::int32_t cmpFuncReturnCode; // To allow better error message in case of multiple matches std::int32_t eqFuncReturnCode; }; // We just define a number here that we assume nobody else is using for // object type user data. The add-ons have reserved the numbers 1000 // through 1999 for this purpose, so we should be fine. constexpr asPWORD ARRAY_CACHE = 1000; static void CleanupTypeInfoArrayCache(asITypeInfo* type) { SArrayCache* cache = static_cast(type->GetUserData(ARRAY_CACHE)); if (cache != nullptr) { cache->~SArrayCache(); asFreeMem(cache); } } CScriptArray* CScriptArray::Create(asITypeInfo* ti, std::uint32_t length) { // Allocate the memory void* mem = asAllocMem(sizeof(CScriptArray)); if (mem == nullptr) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } return nullptr; } // Initialize the object CScriptArray* a = new(mem) CScriptArray(length, ti); return a; } CScriptArray* CScriptArray::Create(asITypeInfo* ti, void* initList) { // Allocate the memory void* mem = asAllocMem(sizeof(CScriptArray)); if (mem == nullptr) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } return nullptr; } // Initialize the object CScriptArray* a = new(mem) CScriptArray(ti, initList); return a; } CScriptArray* CScriptArray::Create(asITypeInfo* ti, std::uint32_t length, void* defVal) { // Allocate the memory void* mem = asAllocMem(sizeof(CScriptArray)); if (mem == nullptr) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } return nullptr; } // Initialize the object CScriptArray* a = new(mem) CScriptArray(length, defVal, ti); return a; } CScriptArray* CScriptArray::Create(asITypeInfo* ti) { return CScriptArray::Create(ti, std::uint32_t(0)); } // This optional callback is called when the template type is first used by the compiler. // It allows the application to validate if the template can be instantiated for the requested // subtype at compile time, instead of at runtime. The output argument dontGarbageCollect // allow the callback to tell the engine if the template instance type shouldn't be garbage collected, // i.e. no asOBJ_GC flag. static bool ScriptArrayTemplateCallback(asITypeInfo* ti, bool& dontGarbageCollect) { // Make sure the subtype can be instantiated with a default factory/constructor, // otherwise we won't be able to instantiate the elements. std::int32_t typeId = ti->GetSubTypeId(); if (typeId == asTYPEID_VOID) return false; if ((typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE)) { asITypeInfo* subtype = ti->GetEngine()->GetTypeInfoById(typeId); std::uint32_t flags = subtype->GetFlags(); if ((flags & asOBJ_VALUE) && !(flags & asOBJ_POD)) { // Verify that there is a default constructor bool found = false; for (std::uint32_t n = 0; n < subtype->GetBehaviourCount(); n++) { asEBehaviours beh; asIScriptFunction* func = subtype->GetBehaviourByIndex(n, &beh); if (beh != asBEHAVE_CONSTRUCT) continue; if (func->GetParamCount() == 0) { // Found the default constructor found = true; break; } } if (!found) { // There is no default constructor // TODO: Should format the message to give the name of the subtype for better understanding ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, "The subtype has no default constructor"); return false; } } else if ((flags & asOBJ_REF)) { bool found = false; // If value assignment for ref type has been disabled then the array // can be created if the type has a default factory function if (!ti->GetEngine()->GetEngineProperty(asEP_DISALLOW_VALUE_ASSIGN_FOR_REF_TYPE)) { // Verify that there is a default factory for (std::uint32_t n = 0; n < subtype->GetFactoryCount(); n++) { asIScriptFunction* func = subtype->GetFactoryByIndex(n); if (func->GetParamCount() == 0) { // Found the default factory found = true; break; } } } if (!found) { // No default factory char buffer[512]; snprintf(buffer, sizeof(buffer), "The subtype '%s' has no default factory", subtype ? subtype->GetEngine()->GetTypeDeclaration(subtype->GetTypeId()) : "UNKNOWN"); ti->GetEngine()->WriteMessage("array", 0, 0, asMSGTYPE_ERROR, buffer); return false; } } // If the object type is not garbage collected then the array also doesn't need to be if (!(flags & asOBJ_GC)) { dontGarbageCollect = true; } } else if (!(typeId & asTYPEID_OBJHANDLE)) { // Arrays with primitives cannot form circular references, // thus there is no need to garbage collect them dontGarbageCollect = true; } else { DEATH_ASSERT(typeId & asTYPEID_OBJHANDLE); // It is not necessary to set the array as garbage collected for all handle types. // If it is possible to determine that the handle cannot refer to an object type // that can potentially form a circular reference with the array then it is not // necessary to make the array garbage collected. asITypeInfo* subtype = ti->GetEngine()->GetTypeInfoById(typeId); std::uint32_t flags = subtype->GetFlags(); if (!(flags & asOBJ_GC)) { if ((flags & asOBJ_SCRIPT_OBJECT)) { // Even if a script class is by itself not garbage collected, it is possible // that classes that derive from it may be, so it is not possible to know // that no circular reference can occur. if ((flags & asOBJ_NOINHERIT)) { // A script class declared as final cannot be inherited from, thus // we can be certain that the object cannot be garbage collected. dontGarbageCollect = true; } } else { // For application registered classes we assume the application knows // what it is doing and don't mark the array as garbage collected unless // the type is also garbage collected. dontGarbageCollect = true; } } } // The type is ok return true; } void RegisterArray(asIScriptEngine* engine) { std::int32_t r; // Register the object type user data clean up engine->SetTypeInfoUserDataCleanupCallback(CleanupTypeInfoArrayCache, ARRAY_CACHE); // Register the array type as a template r = engine->RegisterObjectType("array", 0, asOBJ_REF | asOBJ_GC | asOBJ_TEMPLATE); RETURN_ASSERT(r >= 0); // Register a callback for validating the subtype before it is used r = engine->RegisterObjectBehaviour("array", asBEHAVE_TEMPLATE_CALLBACK, "bool f(int&in, bool&out)", asFUNCTION(ScriptArrayTemplateCallback), asCALL_CDECL); RETURN_ASSERT(r >= 0); // Templates receive the object type as the first parameter. To the script writer this is hidden r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*), CScriptArray*), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length) explicit", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, std::uint32_t), CScriptArray*), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_FACTORY, "array@ f(int&in, uint length, const T &in value)", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, std::uint32_t, void*), CScriptArray*), asCALL_CDECL); RETURN_ASSERT(r >= 0); // Register the factory that will be used for initialization lists r = engine->RegisterObjectBehaviour("array", asBEHAVE_LIST_FACTORY, "array@ f(int&in type, int&in list) {repeat T}", asFUNCTIONPR(CScriptArray::Create, (asITypeInfo*, void*), CScriptArray*), asCALL_CDECL); RETURN_ASSERT(r >= 0); // The memory management methods r = engine->RegisterObjectBehaviour("array", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptArray, AddRef), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptArray, Release), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // The index operator returns the template subtype r = engine->RegisterObjectMethod("array", "T &opIndex(uint index)", asMETHODPR(CScriptArray, At, (std::uint32_t), void*), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "const T &opIndex(uint index) const", asMETHODPR(CScriptArray, At, (std::uint32_t) const, const void*), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // The assignment operator r = engine->RegisterObjectMethod("array", "array &opAssign(const array&in)", asMETHOD(CScriptArray, operator=), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Other methods r = engine->RegisterObjectMethod("array", "void insertAt(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (std::uint32_t, void*), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void insertAt(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (std::uint32_t, const CScriptArray&), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void insertLast(const T&in value)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void removeAt(uint index)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void removeLast()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void removeRange(uint start, uint count)", asMETHOD(CScriptArray, RemoveRange), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // TODO: Should length() and resize() be deprecated as the property accessors do the same thing? // TODO: Register as size() for consistency with other types #if AS_USE_ACCESSORS != 1 r = engine->RegisterObjectMethod("array", "uint length() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #endif r = engine->RegisterObjectMethod("array", "void reserve(uint length)", asMETHOD(CScriptArray, Reserve), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void resize(uint length)", asMETHODPR(CScriptArray, Resize, (std::uint32_t), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void sortAsc()", asMETHODPR(CScriptArray, SortAsc, (), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void sortAsc(uint startAt, uint count)", asMETHODPR(CScriptArray, SortAsc, (std::uint32_t, std::uint32_t), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void sortDesc()", asMETHODPR(CScriptArray, SortDesc, (), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void sortDesc(uint startAt, uint count)", asMETHODPR(CScriptArray, SortDesc, (std::uint32_t, std::uint32_t), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void reverse()", asMETHOD(CScriptArray, Reverse), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // The token 'if_handle_then_const' tells the engine that if the type T is a handle, then it should refer to a read-only object r = engine->RegisterObjectMethod("array", "int find(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (void*) const, int), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // TODO: It should be "int find(const T&in value, uint startAt = 0) const" r = engine->RegisterObjectMethod("array", "int find(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, Find, (std::uint32_t, void*) const, int), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "int findByRef(const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (void*) const, int), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // TODO: It should be "int findByRef(const T&in value, uint startAt = 0) const" r = engine->RegisterObjectMethod("array", "int findByRef(uint startAt, const T&in if_handle_then_const value) const", asMETHODPR(CScriptArray, FindByRef, (std::uint32_t, void*) const, int), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "bool opEquals(const array&in) const", asMETHOD(CScriptArray, operator==), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "bool isEmpty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Sort with callback for comparison r = engine->RegisterFuncdef("bool array::less(const T&in if_handle_then_const a, const T&in if_handle_then_const b)"); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void sort(const less &in, uint startAt = 0, uint count = uint(-1))", asMETHODPR(CScriptArray, Sort, (asIScriptFunction*, std::uint32_t, std::uint32_t), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 // Register virtual properties r = engine->RegisterObjectMethod("array", "uint get_length() const property", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void set_length(uint) property", asMETHODPR(CScriptArray, Resize, (std::uint32_t), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #endif // Register GC behaviours in case the array needs to be garbage collected r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptArray, GetRefCount), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptArray, SetFlag), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptArray, GetFlag), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptArray, EnumReferences), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("array", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptArray, ReleaseAllHandles), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #if AS_USE_STLNAMES == 1 // Same as length r = engine->RegisterObjectMethod("array", "uint size() const", asMETHOD(CScriptArray, GetSize), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as isEmpty r = engine->RegisterObjectMethod("array", "bool empty() const", asMETHOD(CScriptArray, IsEmpty), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as insertLast r = engine->RegisterObjectMethod("array", "void push_back(const T&in)", asMETHOD(CScriptArray, InsertLast), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as removeLast r = engine->RegisterObjectMethod("array", "void pop_back()", asMETHOD(CScriptArray, RemoveLast), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as insertAt r = engine->RegisterObjectMethod("array", "void insert(uint index, const T&in value)", asMETHODPR(CScriptArray, InsertAt, (std::uint32_t, void*), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("array", "void insert(uint index, const array& arr)", asMETHODPR(CScriptArray, InsertAt, (std::uint32_t, const CScriptArray&), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as removeAt r = engine->RegisterObjectMethod("array", "void erase(uint)", asMETHOD(CScriptArray, RemoveAt), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #endif r = engine->RegisterDefaultArrayType("array"); RETURN_ASSERT(r >= 0); } CScriptArray& CScriptArray::operator=(const CScriptArray& other) { // Only perform the copy if the array types are the same if (&other != this && other.GetArrayObjectType() == GetArrayObjectType()) { // Make sure the arrays are of the same size Resize(other.buffer->numElements); // Copy the value of each element CopyBuffer(buffer, other.buffer); } return *this; } CScriptArray::CScriptArray(asITypeInfo* ti, void* buf) { // The object type should be the template instance of the array DEATH_ASSERT(ti != nullptr && StringView(ti->GetName()) == "array"_s); refCount = 1; gcFlag = false; objType = ti; objType->AddRef(); buffer = 0; Precache(); asIScriptEngine* engine = ti->GetEngine(); // Determine element size if (subTypeId & asTYPEID_MASK_OBJECT) { elementSize = sizeof(asPWORD); } else { elementSize = engine->GetSizeOfPrimitiveType(subTypeId); } // Determine the initial size from the buffer std::uint32_t length = *(std::uint32_t*)buf; // Make sure the array size isn't too large for us to handle if (!CheckMaxSize(length)) { // Don't continue with the initialization return; } // Copy the values of the array elements from the buffer if ((ti->GetSubTypeId() & asTYPEID_MASK_OBJECT) == 0) { CreateBuffer(&buffer, length); // Copy the values of the primitive type into the internal buffer if (length > 0) { std::memcpy(At(0), (((std::uint32_t*)buf) + 1), length * elementSize); } } else if (ti->GetSubTypeId() & asTYPEID_OBJHANDLE) { CreateBuffer(&buffer, length); // Copy the handles into the internal buffer if (length > 0) { std::memcpy(At(0), (((std::uint32_t*)buf) + 1), length * elementSize); } // With object handles it is safe to clear the memory in the received buffer // instead of increasing the ref count. It will save time both by avoiding the // call the increase ref, and also relieve the engine from having to release // its references too std::memset((((std::uint32_t*)buf) + 1), 0, length * elementSize); } else if (ti->GetSubType()->GetFlags() & asOBJ_REF) { // Only allocate the buffer, but not the objects subTypeId |= asTYPEID_OBJHANDLE; CreateBuffer(&buffer, length); subTypeId &= ~asTYPEID_OBJHANDLE; // Copy the handles into the internal buffer if (length > 0) { std::memcpy(buffer->data, (((std::uint32_t*)buf) + 1), length * elementSize); } // For ref types we can do the same as for handles, as they are // implicitly stored as handles. std::memset((((std::uint32_t*)buf) + 1), 0, length * elementSize); } else { // TODO: Optimize by calling the copy constructor of the object instead of // constructing with the default constructor and then assigning the value // TODO: With C++11 ideally we should be calling the move constructor, instead // of the copy constructor as the engine will just discard the objects in the // buffer afterwards. CreateBuffer(&buffer, length); // For value types we need to call the opAssign for each individual object for (std::uint32_t n = 0; n < length; n++) { void* obj = At(n); std::uint8_t* srcObj = (std::uint8_t*)buf; srcObj += 4 + n * ti->GetSubType()->GetSize(); engine->AssignScriptObject(obj, srcObj, ti->GetSubType()); } } // Notify the GC of the successful creation if (objType->GetFlags() & asOBJ_GC) { objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); } } CScriptArray::CScriptArray(std::uint32_t length, asITypeInfo* ti) { // The object type should be the template instance of the array DEATH_ASSERT(ti != nullptr && StringView(ti->GetName()) == "array"_s); refCount = 1; gcFlag = false; objType = ti; objType->AddRef(); buffer = 0; Precache(); // Determine element size if (subTypeId & asTYPEID_MASK_OBJECT) { elementSize = sizeof(asPWORD); } else { elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); } // Make sure the array size isn't too large for us to handle if (!CheckMaxSize(length)) { // Don't continue with the initialization return; } CreateBuffer(&buffer, length); // Notify the GC of the successful creation if (objType->GetFlags() & asOBJ_GC) { objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); } } CScriptArray::CScriptArray(const CScriptArray& other) { refCount = 1; gcFlag = false; objType = other.objType; objType->AddRef(); buffer = 0; Precache(); elementSize = other.elementSize; if (objType->GetFlags() & asOBJ_GC) { objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); } CreateBuffer(&buffer, 0); // Copy the content *this = other; } CScriptArray::CScriptArray(std::uint32_t length, void* defVal, asITypeInfo* ti) { // The object type should be the template instance of the array DEATH_ASSERT(ti != nullptr && StringView(ti->GetName()) == "array"_s); refCount = 1; gcFlag = false; objType = ti; objType->AddRef(); buffer = 0; Precache(); // Determine element size if (subTypeId & asTYPEID_MASK_OBJECT) { elementSize = sizeof(asPWORD); } else { elementSize = objType->GetEngine()->GetSizeOfPrimitiveType(subTypeId); } // Make sure the array size isn't too large for us to handle if (!CheckMaxSize(length)) { // Don't continue with the initialization return; } CreateBuffer(&buffer, length); // Notify the GC of the successful creation if (objType->GetFlags() & asOBJ_GC) { objType->GetEngine()->NotifyGarbageCollectorOfNewObject(this, objType); } // Initialize the elements with the default value for (std::uint32_t n = 0; n < GetSize(); n++) { SetValue(n, defVal); } } void CScriptArray::SetValue(std::uint32_t index, void* value) { // At() will take care of the out-of-bounds checking, though // if called from the application then nothing will be done void* ptr = At(index); if (ptr == nullptr) return; if ((subTypeId & ~asTYPEID_MASK_SEQNBR) && !(subTypeId & asTYPEID_OBJHANDLE)) { asITypeInfo* subType = objType->GetSubType(); if (subType->GetFlags() & asOBJ_ASHANDLE) { // For objects that should work as handles we must use the opHndlAssign method // TODO: Must support alternative syntaxes as well // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once String decl = StringView(subType->GetName()) + "& opHndlAssign(const "_s + StringView(subType->GetName()) + "&in)"_s; asIScriptFunction* func = subType->GetMethodByDecl(decl.data()); if (func != nullptr) { // TODO: Reuse active context if existing asIScriptEngine* engine = objType->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(func); ctx->SetObject(ptr); ctx->SetArgAddress(0, value); // TODO: Handle errors ctx->Execute(); engine->ReturnContext(ctx); } else { // opHndlAssign doesn't exist, so try ordinary value assign instead objType->GetEngine()->AssignScriptObject(ptr, value, subType); } } else { objType->GetEngine()->AssignScriptObject(ptr, value, subType); } } else if (subTypeId & asTYPEID_OBJHANDLE) { void* tmp = *(void**)ptr; *(void**)ptr = *(void**)value; objType->GetEngine()->AddRefScriptObject(*(void**)value, objType->GetSubType()); if (tmp != nullptr) objType->GetEngine()->ReleaseScriptObject(tmp, objType->GetSubType()); } else if (subTypeId == asTYPEID_BOOL || subTypeId == asTYPEID_INT8 || subTypeId == asTYPEID_UINT8) { *(char*)ptr = *(char*)value; } else if (subTypeId == asTYPEID_INT16 || subTypeId == asTYPEID_UINT16) { *(short*)ptr = *(short*)value; } else if (subTypeId == asTYPEID_INT32 || subTypeId == asTYPEID_UINT32 || subTypeId == asTYPEID_FLOAT || subTypeId > asTYPEID_DOUBLE) { // enums have a type id larger than doubles *(int*)ptr = *(int*)value; } else if (subTypeId == asTYPEID_INT64 || subTypeId == asTYPEID_UINT64 || subTypeId == asTYPEID_DOUBLE) { *(double*)ptr = *(double*)value; } } CScriptArray::~CScriptArray() { if (buffer != nullptr) { DeleteBuffer(buffer); buffer = 0; } if (objType != nullptr) objType->Release(); } std::uint32_t CScriptArray::GetSize() const { return buffer->numElements; } bool CScriptArray::IsEmpty() const { return buffer->numElements == 0; } void CScriptArray::Reserve(std::uint32_t maxElements) { if (maxElements <= buffer->maxElements) return; if (!CheckMaxSize(maxElements)) return; // Allocate memory for the buffer SArrayBuffer* newBuffer = static_cast(asAllocMem(sizeof(SArrayBuffer) - 1 + elementSize * maxElements)); if (newBuffer != nullptr) { newBuffer->numElements = buffer->numElements; newBuffer->maxElements = maxElements; } else { // Out of memory asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } return; } // As objects in arrays of objects are not stored inline, it is safe to use memcpy here // since we're just copying the pointers to objects and not the actual objects. std::memcpy(newBuffer->data, buffer->data, buffer->numElements * elementSize); // Release the old buffer asFreeMem(buffer); buffer = newBuffer; } void CScriptArray::Resize(std::uint32_t numElements) { if (!CheckMaxSize(numElements)) { return; } Resize((std::int32_t)numElements - (std::int32_t)buffer->numElements, (std::uint32_t)-1); } void CScriptArray::RemoveRange(std::uint32_t start, std::uint32_t count) { if (count == 0) { return; } if (buffer == nullptr || start > buffer->numElements) { // If this is called from a script we raise a script exception asIScriptContext* ctx = asGetActiveContext(); if (ctx) { ctx->SetException("Index out of bounds"); } return; } // Cap count to the end of the array if (start + count > buffer->numElements) { count = buffer->numElements - start; } // Destroy the elements that are being removed Destruct(buffer, start, start + count); // Compact the elements // As objects in arrays of objects are not stored inline, it is safe to use memmove here // since we're just copying the pointers to objects and not the actual objects. std::memmove(buffer->data + start * elementSize, buffer->data + (start + count) * elementSize, (buffer->numElements - start - count) * elementSize); buffer->numElements -= count; } // Internal void CScriptArray::Resize(std::int32_t delta, std::uint32_t at) { if (delta < 0) { if (-delta > (std::int32_t)buffer->numElements) { delta = -(std::int32_t)buffer->numElements; } if (at > buffer->numElements + delta) { at = buffer->numElements + delta; } } else if (delta > 0) { // Make sure the array size isn't too large for us to handle if (delta > 0 && !CheckMaxSize(buffer->numElements + delta)) { return; } if (at > buffer->numElements) { at = buffer->numElements; } } if (delta == 0) return; if (buffer->maxElements < buffer->numElements + delta) { // Allocate memory for the buffer SArrayBuffer* newBuffer = static_cast(asAllocMem(sizeof(SArrayBuffer) - 1 + elementSize * (buffer->numElements + delta))); if (newBuffer != nullptr) { newBuffer->numElements = buffer->numElements + delta; newBuffer->maxElements = newBuffer->numElements; } else { // Out of memory asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } return; } // As objects in arrays of objects are not stored inline, it is safe to use memcpy here // since we're just copying the pointers to objects and not the actual objects. std::memcpy(newBuffer->data, buffer->data, at * elementSize); if (at < buffer->numElements) { std::memcpy(newBuffer->data + (at + delta) * elementSize, buffer->data + at * elementSize, (buffer->numElements - at) * elementSize); } // Initialize the new elements with default values Construct(newBuffer, at, at + delta); // Release the old buffer asFreeMem(buffer); buffer = newBuffer; } else if (delta < 0) { Destruct(buffer, at, at - delta); // As objects in arrays of objects are not stored inline, it is safe to use memmove here // since we're just copying the pointers to objects and not the actual objects. std::memmove(buffer->data + at * elementSize, buffer->data + (at - delta) * elementSize, (buffer->numElements - (at - delta)) * elementSize); buffer->numElements += delta; } else { // As objects in arrays of objects are not stored inline, it is safe to use memmove here // since we're just copying the pointers to objects and not the actual objects. std::memmove(buffer->data + (at + delta) * elementSize, buffer->data + at * elementSize, (buffer->numElements - at) * elementSize); Construct(buffer, at, at + delta); buffer->numElements += delta; } } // internal bool CScriptArray::CheckMaxSize(std::uint32_t numElements) { // This code makes sure the size of the buffer that is allocated // for the array doesn't overflow and becomes smaller than requested std::uint32_t maxSize = 0xFFFFFFFFul - sizeof(SArrayBuffer) + 1; if (elementSize > 0) { maxSize /= elementSize; } if (numElements > maxSize) { asIScriptContext* ctx = asGetActiveContext(); if (ctx) { ctx->SetException("Too large array size"); } return false; } return true; } asITypeInfo* CScriptArray::GetArrayObjectType() const { return objType; } int CScriptArray::GetArrayTypeId() const { return objType->GetTypeId(); } int CScriptArray::GetElementTypeId() const { return subTypeId; } void CScriptArray::InsertAt(std::uint32_t index, void* value) { if (index > buffer->numElements) { // If this is called from a script we raise a script exception asIScriptContext* ctx = asGetActiveContext(); if (ctx) { ctx->SetException("Index out of bounds"); } return; } // Make room for the new element Resize(1, index); // Set the value of the new element SetValue(index, value); } void CScriptArray::InsertAt(std::uint32_t index, const CScriptArray& arr) { if (index > buffer->numElements) { asIScriptContext* ctx = asGetActiveContext(); if (ctx) { ctx->SetException("Index out of bounds"); } return; } if (objType != arr.objType) { // This shouldn't really be possible to happen when // called from a script, but let's check for it anyway asIScriptContext* ctx = asGetActiveContext(); if (ctx) { ctx->SetException("Mismatching array types"); } return; } std::uint32_t elements = arr.GetSize(); Resize(elements, index); if (&arr != this) { for (std::uint32_t n = 0; n < arr.GetSize(); n++) { // This const cast is allowed, since we know the // value will only be used to make a copy of it void* value = const_cast(arr.At(n)); SetValue(index + n, value); } } else { // The array that is being inserted is the same as this one. // So we should iterate over the elements before the index, // and then the elements after for (std::uint32_t n = 0; n < index; n++) { // This const cast is allowed, since we know the // value will only be used to make a copy of it void* value = const_cast(arr.At(n)); SetValue(index + n, value); } for (std::uint32_t n = index + elements, m = 0; n < arr.GetSize(); n++, m++) { // This const cast is allowed, since we know the // value will only be used to make a copy of it void* value = const_cast(arr.At(n)); SetValue(index + index + m, value); } } } void CScriptArray::InsertLast(void* value) { InsertAt(buffer->numElements, value); } void CScriptArray::RemoveAt(std::uint32_t index) { if (index >= buffer->numElements) { // If this is called from a script we raise a script exception asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Index out of bounds"); } return; } // Remove the element Resize(-1, index); } void CScriptArray::RemoveLast() { RemoveAt(buffer->numElements - 1); } // Return a pointer to the array element. Returns 0 if the index is out of bounds const void* CScriptArray::At(std::uint32_t index) const { if (buffer == nullptr || index >= buffer->numElements) { // If this is called from a script we raise a script exception asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Index out of bounds"); } return 0; } if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE)) { return *(void**)(buffer->data + elementSize * index); } else { return buffer->data + elementSize * index; } } void* CScriptArray::At(std::uint32_t index) { return const_cast(const_cast(this)->At(index)); } void* CScriptArray::GetBuffer() { return buffer->data; } // internal void CScriptArray::CreateBuffer(SArrayBuffer** buf, std::uint32_t numElements) { *buf = static_cast(asAllocMem(sizeof(SArrayBuffer) - 1 + elementSize * numElements)); if ((*buf) != nullptr) { (*buf)->numElements = numElements; (*buf)->maxElements = numElements; Construct(*buf, 0, numElements); } else { // Oops, out of memory asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } } } // internal void CScriptArray::DeleteBuffer(SArrayBuffer* buf) { Destruct(buf, 0, buf->numElements); // Free the buffer asFreeMem(buf); } // internal void CScriptArray::Construct(SArrayBuffer* buf, std::uint32_t start, std::uint32_t end) { if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE)) { // Create an object using the default constructor/factory for each element void** max = (void**)(buf->data + end * sizeof(void*)); void** d = (void**)(buf->data + start * sizeof(void*)); asIScriptEngine* engine = objType->GetEngine(); asITypeInfo* subType = objType->GetSubType(); for (; d < max; d++) { *d = (void*)engine->CreateScriptObject(subType); if (*d == 0) { // Set the remaining entries to null so the destructor // won't attempt to destroy invalid objects later std::memset(d, 0, sizeof(void*) * (max - d)); // There is no need to set an exception on the context, // as CreateScriptObject has already done that return; } } } else { // Set all elements to zero whether they are handles or primitives void* d = (void*)(buf->data + start * elementSize); std::memset(d, 0, (end - start) * elementSize); } } // internal void CScriptArray::Destruct(SArrayBuffer* buf, std::uint32_t start, std::uint32_t end) { if (subTypeId & asTYPEID_MASK_OBJECT) { asIScriptEngine* engine = objType->GetEngine(); void** max = (void**)(buf->data + end * sizeof(void*)); void** d = (void**)(buf->data + start * sizeof(void*)); for (; d < max; d++) { if (*d) { engine->ReleaseScriptObject(*d, objType->GetSubType()); } } } } // internal bool CScriptArray::Less(const void* a, const void* b, bool asc) { if (!asc) { // Swap items const void* TEMP = a; a = b; b = TEMP; } if (!(subTypeId & ~asTYPEID_MASK_SEQNBR)) { // Simple compare of values switch (subTypeId) { #define COMPARE(T) *((T*)a) < *((T*)b) case asTYPEID_BOOL: return COMPARE(bool); case asTYPEID_INT8: return COMPARE(std::int8_t); case asTYPEID_INT16: return COMPARE(std::int16_t); case asTYPEID_INT32: return COMPARE(std::int32_t); case asTYPEID_INT64: return COMPARE(std::int64_t); case asTYPEID_UINT8: return COMPARE(std::uint8_t); case asTYPEID_UINT16: return COMPARE(std::uint16_t); case asTYPEID_UINT32: return COMPARE(std::uint32_t); case asTYPEID_UINT64: return COMPARE(std::uint64_t); case asTYPEID_FLOAT: return COMPARE(float); case asTYPEID_DOUBLE: return COMPARE(double); default: return COMPARE(signed int); // All enums fall in this case #undef COMPARE } } return false; } void CScriptArray::Reverse() { std::uint32_t size = GetSize(); if (size >= 2) { std::uint8_t TEMP[16]; for (std::uint32_t i = 0; i < size / 2; i++) { Copy(TEMP, GetArrayItemPointer(i)); Copy(GetArrayItemPointer(i), GetArrayItemPointer(size - i - 1)); Copy(GetArrayItemPointer(size - i - 1), TEMP); } } } bool CScriptArray::operator==(const CScriptArray& other) const { if (objType != other.objType) { return false; } if (GetSize() != other.GetSize()) { return false; } asIScriptContext* cmpContext = 0; bool isNested = false; if (subTypeId & ~asTYPEID_MASK_SEQNBR) { // Try to reuse the active context cmpContext = asGetActiveContext(); if (cmpContext != nullptr) { if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) { isNested = true; } else { cmpContext = nullptr; } } if (cmpContext == nullptr) { // TODO: Ideally this context would be retrieved from a pool, so we don't have to // create a new one everytime. We could keep a context with the array object // but that would consume a lot of resources as each context is quite heavy. cmpContext = objType->GetEngine()->CreateContext(); } } // Check if all elements are equal bool isEqual = true; SArrayCache* cache = static_cast(objType->GetUserData(ARRAY_CACHE)); for (std::uint32_t n = 0; n < GetSize(); n++) if (!Equals(At(n), other.At(n), cmpContext, cache)) { isEqual = false; break; } if (cmpContext != nullptr) { if (isNested) { asEContextState state = cmpContext->GetState(); cmpContext->PopState(); if (state == asEXECUTION_ABORTED) { cmpContext->Abort(); } } else { cmpContext->Release(); } } return isEqual; } // internal bool CScriptArray::Equals(const void* a, const void* b, asIScriptContext* ctx, SArrayCache* cache) const { if (!(subTypeId & ~asTYPEID_MASK_SEQNBR)) { // Simple compare of values switch (subTypeId) { #define COMPARE(T) *((T*)a) == *((T*)b) case asTYPEID_BOOL: return COMPARE(bool); case asTYPEID_INT8: return COMPARE(std::int8_t); case asTYPEID_INT16: return COMPARE(std::int16_t); case asTYPEID_INT32: return COMPARE(std::int32_t); case asTYPEID_INT64: return COMPARE(std::int64_t); case asTYPEID_UINT8: return COMPARE(std::uint8_t); case asTYPEID_UINT16: return COMPARE(std::uint16_t); case asTYPEID_UINT32: return COMPARE(std::uint32_t); case asTYPEID_UINT64: return COMPARE(std::uint64_t); case asTYPEID_FLOAT: return COMPARE(float); case asTYPEID_DOUBLE: return COMPARE(double); default: return COMPARE(signed int); // All enums fall here. TODO: update this when enums can have different sizes and types #undef COMPARE } } else { std::int32_t r = 0; if (subTypeId & asTYPEID_OBJHANDLE) { // Allow the find to work even if the array contains null handles if (*(void**)a == *(void**)b) return true; } // Execute object opEquals if available if (cache != nullptr && cache->eqFunc != nullptr) { // TODO: Add proper error handling r = ctx->Prepare(cache->eqFunc); DEATH_ASSERT(r >= 0); if (subTypeId & asTYPEID_OBJHANDLE) { r = ctx->SetObject(*((void**)a)); DEATH_ASSERT(r >= 0); r = ctx->SetArgObject(0, *((void**)b)); DEATH_ASSERT(r >= 0); } else { r = ctx->SetObject((void*)a); DEATH_ASSERT(r >= 0); r = ctx->SetArgObject(0, (void*)b); DEATH_ASSERT(r >= 0); } r = ctx->Execute(); if (r == asEXECUTION_FINISHED) { return ctx->GetReturnByte() != 0; } return false; } // Execute object opCmp if available if (cache != nullptr && cache->cmpFunc != nullptr) { // TODO: Add proper error handling r = ctx->Prepare(cache->cmpFunc); DEATH_ASSERT(r >= 0); if (subTypeId & asTYPEID_OBJHANDLE) { r = ctx->SetObject(*((void**)a)); DEATH_ASSERT(r >= 0); r = ctx->SetArgObject(0, *((void**)b)); DEATH_ASSERT(r >= 0); } else { r = ctx->SetObject((void*)a); DEATH_ASSERT(r >= 0); r = ctx->SetArgObject(0, (void*)b); DEATH_ASSERT(r >= 0); } r = ctx->Execute(); if (r == asEXECUTION_FINISHED) { return (int)ctx->GetReturnDWord() == 0; } return false; } } return false; } int CScriptArray::FindByRef(void* ref) const { return FindByRef(0, ref); } int CScriptArray::FindByRef(std::uint32_t startAt, void* ref) const { // Find the matching element by its reference std::uint32_t size = GetSize(); if (subTypeId & asTYPEID_OBJHANDLE) { // Dereference the pointer ref = *(void**)ref; for (std::uint32_t i = startAt; i < size; i++) { if (*(void**)At(i) == ref) { return i; } } } else { // Compare the reference directly for (std::uint32_t i = startAt; i < size; i++) { if (At(i) == ref) { return i; } } } return -1; } int CScriptArray::Find(void* value) const { return Find(0, value); } int CScriptArray::Find(std::uint32_t startAt, void* value) const { // Check if the subtype really supports find() // TODO: Can't this be done at compile time too by the template callback SArrayCache* cache = 0; if (subTypeId & ~asTYPEID_MASK_SEQNBR) { cache = reinterpret_cast(objType->GetUserData(ARRAY_CACHE)); if (cache == nullptr || (cache->cmpFunc == 0 && cache->eqFunc == 0)) { asIScriptContext* ctx = asGetActiveContext(); asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); // Throw an exception if (ctx != nullptr) { char tmp[256]; if (cache && cache->eqFuncReturnCode == asMULTIPLE_FUNCTIONS) { #if defined(DEATH_TARGET_MSVC) sprintf_s(tmp, arraySize(tmp), "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); #else sprintf(tmp, "Type '%s' has multiple matching opEquals or opCmp methods", subType->GetName()); #endif } else { #if defined(DEATH_TARGET_MSVC) sprintf_s(tmp, arraySize(tmp), "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); #else sprintf(tmp, "Type '%s' does not have a matching opEquals or opCmp method", subType->GetName()); #endif } ctx->SetException(tmp); } return -1; } } asIScriptContext* cmpContext = 0; bool isNested = false; if (subTypeId & ~asTYPEID_MASK_SEQNBR) { // Try to reuse the active context cmpContext = asGetActiveContext(); if (cmpContext != nullptr) { if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) { isNested = true; } else { cmpContext = nullptr; } } if (cmpContext == nullptr) { // TODO: Ideally this context would be retrieved from a pool, so we don't have to // create a new one everytime. We could keep a context with the array object // but that would consume a lot of resources as each context is quite heavy. cmpContext = objType->GetEngine()->CreateContext(); } } // Find the matching element std::int32_t ret = -1; std::uint32_t size = GetSize(); for (std::uint32_t i = startAt; i < size; i++) { // value passed by reference if (Equals(At(i), value, cmpContext, cache)) { ret = (std::int32_t)i; break; } } if (cmpContext != nullptr) { if (isNested) { asEContextState state = cmpContext->GetState(); cmpContext->PopState(); if (state == asEXECUTION_ABORTED) { cmpContext->Abort(); } } else { cmpContext->Release(); } } return ret; } // internal // Copy object handle or primitive value // Even in arrays of objects the objects are allocated on // the heap and the array stores the pointers to the objects void CScriptArray::Copy(void* dst, void* src) { std::memcpy(dst, src, elementSize); } // internal // Swap two elements // Even in arrays of objects the objects are allocated on // the heap and the array stores the pointers to the objects. void CScriptArray::Swap(void* a, void* b) { std::uint8_t tmp[16]; Copy(tmp, a); Copy(a, b); Copy(b, tmp); } // internal // Return pointer to array item (object handle or primitive value) void* CScriptArray::GetArrayItemPointer(std::int32_t index) { return buffer->data + index * elementSize; } // internal // Return pointer to data in buffer (object or primitive) void* CScriptArray::GetDataPointer(void* buf) { if ((subTypeId & asTYPEID_MASK_OBJECT) && !(subTypeId & asTYPEID_OBJHANDLE)) { // Real address of object return reinterpret_cast(*static_cast(buf)); } else { // Primitive is just a raw data return buf; } } // Sort ascending void CScriptArray::SortAsc() { Sort(0, GetSize(), true); } // Sort ascending void CScriptArray::SortAsc(std::uint32_t startAt, std::uint32_t count) { Sort(startAt, count, true); } // Sort descending void CScriptArray::SortDesc() { Sort(0, GetSize(), false); } // Sort descending void CScriptArray::SortDesc(std::uint32_t startAt, std::uint32_t count) { Sort(startAt, count, false); } // Internal void CScriptArray::Sort(std::uint32_t startAt, std::uint32_t count, bool asc) { // Subtype isn't primitive and doesn't have opCmp SArrayCache* cache = static_cast(objType->GetUserData(ARRAY_CACHE)); if (subTypeId & ~asTYPEID_MASK_SEQNBR) { if (cache == nullptr || cache->cmpFunc == 0) { asIScriptContext* ctx = asGetActiveContext(); asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); // Throw an exception if (ctx != nullptr) { char tmp[256]; if (cache && cache->cmpFuncReturnCode == asMULTIPLE_FUNCTIONS) { #if defined(DEATH_TARGET_MSVC) sprintf_s(tmp, arraySize(tmp), "Type '%s' has multiple matching opCmp methods", subType->GetName()); #else sprintf(tmp, "Type '%s' has multiple matching opCmp methods", subType->GetName()); #endif } else { #if defined(DEATH_TARGET_MSVC) sprintf_s(tmp, arraySize(tmp), "Type '%s' does not have a matching opCmp method", subType->GetName()); #else sprintf(tmp, "Type '%s' does not have a matching opCmp method", subType->GetName()); #endif } ctx->SetException(tmp); } return; } } // No need to sort if (count < 2) { return; } std::int32_t start = startAt; std::int32_t end = startAt + count; // Check if we could access invalid item while sorting if (start >= (std::int32_t)buffer->numElements || end > (std::int32_t)buffer->numElements) { asIScriptContext* ctx = asGetActiveContext(); // Throw an exception if (ctx) { ctx->SetException("Index out of bounds"); } return; } if (subTypeId & ~asTYPEID_MASK_SEQNBR) { asIScriptContext* cmpContext = 0; bool isNested = false; // Try to reuse the active context cmpContext = asGetActiveContext(); if (cmpContext != nullptr) { if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) { isNested = true; } else { cmpContext = nullptr; } } if (cmpContext == nullptr) { cmpContext = objType->GetEngine()->RequestContext(); } // Do the sorting struct { bool asc; asIScriptContext* cmpContext; asIScriptFunction* cmpFunc; bool operator()(void* a, void* b) const { if (!asc) { // Swap items void* TEMP = a; a = b; b = TEMP; } std::int32_t r = 0; // Allow sort to work even if the array contains null handles if (a == 0) return true; if (b == 0) return false; // Execute object opCmp if (cmpFunc) { // TODO: Add proper error handling r = cmpContext->Prepare(cmpFunc); DEATH_ASSERT(r >= 0); r = cmpContext->SetObject(a); DEATH_ASSERT(r >= 0); r = cmpContext->SetArgObject(0, b); DEATH_ASSERT(r >= 0); r = cmpContext->Execute(); if (r == asEXECUTION_FINISHED) { return (std::int32_t)cmpContext->GetReturnDWord() < 0; } } return false; } } customLess = { asc, cmpContext, cache ? cache->cmpFunc : 0 }; nCine::sort((void**)GetArrayItemPointer(start), (void**)GetArrayItemPointer(end), customLess); // Clean up if (cmpContext != nullptr) { if (isNested) { asEContextState state = cmpContext->GetState(); cmpContext->PopState(); if (state == asEXECUTION_ABORTED) { cmpContext->Abort(); } } else { objType->GetEngine()->ReturnContext(cmpContext); } } } else { // TODO: Use std::sort for primitive types too // Insertion sort std::uint8_t tmp[16]; for (std::int32_t i = start + 1; i < end; i++) { Copy(tmp, GetArrayItemPointer(i)); std::int32_t j = i - 1; while (j >= start && Less(GetDataPointer(tmp), At(j), asc)) { Copy(GetArrayItemPointer(j + 1), GetArrayItemPointer(j)); j--; } Copy(GetArrayItemPointer(j + 1), tmp); } } } // Sort with script callback for comparing elements void CScriptArray::Sort(asIScriptFunction* func, std::uint32_t startAt, std::uint32_t count) { // No need to sort if (count < 2) { return; } // Check if we could access invalid item while sorting std::uint32_t start = startAt; std::uint32_t end = std::uint64_t(startAt) + std::uint64_t(count) >= (std::uint64_t(1) << 32) ? 0xFFFFFFFF : startAt + count; if (end > buffer->numElements) { end = buffer->numElements; } if (start >= buffer->numElements) { asIScriptContext* ctx = asGetActiveContext(); // Throw an exception if (ctx) { ctx->SetException("Index out of bounds"); } return; } asIScriptContext* cmpContext = 0; bool isNested = false; // Try to reuse the active context cmpContext = asGetActiveContext(); if (cmpContext != nullptr) { if (cmpContext->GetEngine() == objType->GetEngine() && cmpContext->PushState() >= 0) { isNested = true; } else { cmpContext = 0; } } if (cmpContext == nullptr) { cmpContext = objType->GetEngine()->RequestContext(); } // TODO: Security issue: If the array is accessed from the callback while the sort is going on the result may be unpredictable // For example, the callback resizes the array in the middle of the sort // Possible solution: set a lock flag on the array, and prohibit modifications while the lock flag is set // Bubble sort // TODO: optimize: Use an efficient sort algorithm for (std::uint32_t i = start; i + 1 < end; i++) { std::uint32_t best = i; for (std::uint32_t j = i + 1; j < end; j++) { cmpContext->Prepare(func); cmpContext->SetArgAddress(0, At(j)); cmpContext->SetArgAddress(1, At(best)); std::int32_t r = cmpContext->Execute(); if (r != asEXECUTION_FINISHED) { break; } if (*(bool*)(cmpContext->GetAddressOfReturnValue())) { best = j; } } // With Swap we guarantee that the array always sees all references // if the GC calls the EnumReferences in the middle of the sorting if (best != i) { Swap(GetArrayItemPointer(i), GetArrayItemPointer(best)); } } if (cmpContext != nullptr) { if (isNested) { asEContextState state = cmpContext->GetState(); cmpContext->PopState(); if (state == asEXECUTION_ABORTED) { cmpContext->Abort(); } } else { objType->GetEngine()->ReturnContext(cmpContext); } } } // internal void CScriptArray::CopyBuffer(SArrayBuffer* dst, SArrayBuffer* src) { asIScriptEngine* engine = objType->GetEngine(); if (subTypeId & asTYPEID_OBJHANDLE) { // Copy the references and increase the reference counters if (dst->numElements > 0 && src->numElements > 0) { std::int32_t count = dst->numElements > src->numElements ? src->numElements : dst->numElements; void** max = (void**)(dst->data + count * sizeof(void*)); void** d = (void**)dst->data; void** s = (void**)src->data; for (; d < max; d++, s++) { void* tmp = *d; *d = *s; if (*d) { engine->AddRefScriptObject(*d, objType->GetSubType()); } // Release the old ref after incrementing the new to avoid problem incase it is the same ref if (tmp) { engine->ReleaseScriptObject(tmp, objType->GetSubType()); } } } } else { if (dst->numElements > 0 && src->numElements > 0) { std::int32_t count = dst->numElements > src->numElements ? src->numElements : dst->numElements; if (subTypeId & asTYPEID_MASK_OBJECT) { // Call the assignment operator on all of the objects void** max = (void**)(dst->data + count * sizeof(void*)); void** d = (void**)dst->data; void** s = (void**)src->data; asITypeInfo* subType = objType->GetSubType(); if (subType->GetFlags() & asOBJ_ASHANDLE) { // For objects that should work as handles we must use the opHndlAssign method // TODO: Must support alternative syntaxes as well // TODO: Move the lookup of the opHndlAssign method to Precache() so it is only done once String decl = StringView(subType->GetName()) + "& opHndlAssign(const "_s + StringView(subType->GetName()) + "&in)"_s; asIScriptFunction* func = subType->GetMethodByDecl(decl.data()); if (func) { // TODO: Reuse active context if existing asIScriptContext* ctx = engine->RequestContext(); for (; d < max; d++, s++) { ctx->Prepare(func); ctx->SetObject(*d); ctx->SetArgAddress(0, *s); // TODO: Handle errors ctx->Execute(); } engine->ReturnContext(ctx); } else { // opHndlAssign doesn't exist, so try ordinary value assign instead for (; d < max; d++, s++) { engine->AssignScriptObject(*d, *s, subType); } } } else for (; d < max; d++, s++) { engine->AssignScriptObject(*d, *s, subType); } } else { // Primitives are copied byte for byte std::memcpy(dst->data, src->data, count * elementSize); } } } } // internal // Precache some info void CScriptArray::Precache() { subTypeId = objType->GetSubTypeId(); // Check if it is an array of objects. Only for these do we need to cache anything // Type ids for primitives and enums only has the sequence number part if (!(subTypeId & ~asTYPEID_MASK_SEQNBR)) { return; } // The opCmp and opEquals methods are cached because the searching for the // methods is quite time consuming if a lot of array objects are created. // First check if a cache already exists for this array type SArrayCache* cache = static_cast(objType->GetUserData(ARRAY_CACHE)); if (cache != nullptr) return; // We need to make sure the cache is created only once, even // if multiple threads reach the same point at the same time asAcquireExclusiveLock(); // Now that we got the lock, we need to check again to make sure the // cache wasn't created while we were waiting for the lock cache = static_cast(objType->GetUserData(ARRAY_CACHE)); if (cache != nullptr) { asReleaseExclusiveLock(); return; } // Create the cache cache = static_cast(asAllocMem(sizeof(SArrayCache))); if (cache == nullptr) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Out of memory"); } asReleaseExclusiveLock(); return; } std::memset(cache, 0, sizeof(SArrayCache)); // If the sub type is a handle to const, then the methods must be const too bool mustBeConst = (subTypeId & asTYPEID_HANDLETOCONST) ? true : false; asITypeInfo* subType = objType->GetEngine()->GetTypeInfoById(subTypeId); if (subType != nullptr) { for (std::uint32_t i = 0; i < subType->GetMethodCount(); i++) { asIScriptFunction* func = subType->GetMethodByIndex(i); if (func->GetParamCount() == 1 && (!mustBeConst || func->IsReadOnly())) { asDWORD flags = 0; std::int32_t returnTypeId = func->GetReturnTypeId(&flags); // The method must not return a reference if (flags != asTM_NONE) { continue; } // opCmp returns an int and opEquals returns a bool bool isCmp = false, isEq = false; if (returnTypeId == asTYPEID_INT32 && strcmp(func->GetName(), "opCmp") == 0) isCmp = true; if (returnTypeId == asTYPEID_BOOL && strcmp(func->GetName(), "opEquals") == 0) isEq = true; if (!isCmp && !isEq) continue; // The parameter must either be a reference to the subtype or a handle to the subtype std::int32_t paramTypeId; func->GetParam(0, ¶mTypeId, &flags); if ((paramTypeId & ~(asTYPEID_OBJHANDLE | asTYPEID_HANDLETOCONST)) != (subTypeId & ~(asTYPEID_OBJHANDLE | asTYPEID_HANDLETOCONST))) continue; if ((flags & asTM_INREF)) { if ((paramTypeId & asTYPEID_OBJHANDLE) || (mustBeConst && !(flags & asTM_CONST))) continue; } else if (paramTypeId & asTYPEID_OBJHANDLE) { if (mustBeConst && !(paramTypeId & asTYPEID_HANDLETOCONST)) continue; } else { continue; } if (isCmp) { if (cache->cmpFunc || cache->cmpFuncReturnCode) { cache->cmpFunc = 0; cache->cmpFuncReturnCode = asMULTIPLE_FUNCTIONS; } else { cache->cmpFunc = func; } } else if (isEq) { if (cache->eqFunc || cache->eqFuncReturnCode) { cache->eqFunc = 0; cache->eqFuncReturnCode = asMULTIPLE_FUNCTIONS; } else { cache->eqFunc = func; } } } } } if (cache->eqFunc == 0 && cache->eqFuncReturnCode == 0) cache->eqFuncReturnCode = asNO_FUNCTION; if (cache->cmpFunc == 0 && cache->cmpFuncReturnCode == 0) cache->cmpFuncReturnCode = asNO_FUNCTION; // Set the user data only at the end so others that retrieve it will know it is complete objType->SetUserData(cache, ARRAY_CACHE); asReleaseExclusiveLock(); } // GC behaviour void CScriptArray::EnumReferences(asIScriptEngine* engine) { // TODO: If garbage collection can be done from a separate thread, then this method must be // protected so that it doesn't get lost during the iteration if the array is modified // If the array is holding handles, then we need to notify the GC of them if (subTypeId & asTYPEID_MASK_OBJECT) { void** d = (void**)buffer->data; asITypeInfo* subType = engine->GetTypeInfoById(subTypeId); if ((subType->GetFlags() & asOBJ_REF)) { // For reference types we need to notify the GC of each instance for (std::uint32_t n = 0; n < buffer->numElements; n++) { if (d[n]) { engine->GCEnumCallback(d[n]); } } } else if ((subType->GetFlags() & asOBJ_VALUE) && (subType->GetFlags() & asOBJ_GC)) { // For value types we need to forward the enum callback // to the object so it can decide what to do for (std::uint32_t n = 0; n < buffer->numElements; n++) { if (d[n]) { engine->ForwardGCEnumReferences(d[n], subType); } } } } } // GC behaviour void CScriptArray::ReleaseAllHandles(asIScriptEngine*) { // Resizing to zero will release everything Resize(0); } void CScriptArray::AddRef() const { // Clear the GC flag then increase the counter gcFlag = false; asAtomicInc(refCount); } void CScriptArray::Release() const { // Clearing the GC flag then descrease the counter gcFlag = false; if (asAtomicDec(refCount) == 0) { // When reaching 0 no more references to this instance // exists and the object should be destroyed this->~CScriptArray(); asFreeMem(const_cast(this)); } } // GC behaviour std::int32_t CScriptArray::GetRefCount() { return refCount; } // GC behaviour void CScriptArray::SetFlag() { gcFlag = true; } // GC behaviour bool CScriptArray::GetFlag() { return gcFlag; } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterArray.h000066400000000000000000000101611512772601700264300ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include namespace Jazz2::Scripting { struct SArrayBuffer; struct SArrayCache; /** @brief **AngelScript** array */ class CScriptArray { public: // Factory functions static CScriptArray* Create(asITypeInfo* ot); static CScriptArray* Create(asITypeInfo* ot, std::uint32_t length); static CScriptArray* Create(asITypeInfo* ot, std::uint32_t length, void* defaultValue); static CScriptArray* Create(asITypeInfo* ot, void* listBuffer); // Memory management void AddRef() const; void Release() const; // Type information asITypeInfo* GetArrayObjectType() const; std::int32_t GetArrayTypeId() const; std::int32_t GetElementTypeId() const; // Get the current size std::uint32_t GetSize() const; // Returns true if the array is empty bool IsEmpty() const; // Pre-allocates memory for elements void Reserve(std::uint32_t maxElements); // Resize the array void Resize(std::uint32_t numElements); // Get a pointer to an element. Returns 0 if out of bounds void* At(std::uint32_t index); const void* At(std::uint32_t index) const; // Set value of an element. // The value arg should be a pointer to the value that will be copied to the element. // Remember, if the array holds handles the value parameter should be the // address of the handle. The refCount of the object will also be incremented void SetValue(std::uint32_t index, void* value); // Copy the contents of one array to another (only if the types are the same) CScriptArray& operator=(const CScriptArray&); // Compare two arrays bool operator==(const CScriptArray&) const; // Array manipulation void InsertAt(std::uint32_t index, void* value); void InsertAt(std::uint32_t index, const CScriptArray& arr); void InsertLast(void* value); void RemoveAt(std::uint32_t index); void RemoveLast(); void RemoveRange(std::uint32_t start, std::uint32_t count); void SortAsc(); void SortDesc(); void SortAsc(std::uint32_t startAt, std::uint32_t count); void SortDesc(std::uint32_t startAt, std::uint32_t count); void Sort(std::uint32_t startAt, std::uint32_t count, bool asc); void Sort(asIScriptFunction* less, std::uint32_t startAt, std::uint32_t count); void Reverse(); int Find(void* value) const; int Find(std::uint32_t startAt, void* value) const; int FindByRef(void* ref) const; int FindByRef(std::uint32_t startAt, void* ref) const; // Return the address of internal buffer for direct manipulation of elements void* GetBuffer(); // GC methods std::int32_t GetRefCount(); void SetFlag(); bool GetFlag(); void EnumReferences(asIScriptEngine* engine); void ReleaseAllHandles(asIScriptEngine* engine); protected: #ifndef DOXYGEN_GENERATING_OUTPUT mutable std::int32_t refCount; mutable bool gcFlag; asITypeInfo* objType; SArrayBuffer* buffer; std::int32_t elementSize; std::int32_t subTypeId; #endif // Constructors CScriptArray(asITypeInfo* ot, void* initBuf); // Called from script when initialized with list CScriptArray(std::uint32_t length, asITypeInfo* ot); CScriptArray(std::uint32_t length, void* defVal, asITypeInfo* ot); CScriptArray(const CScriptArray& other); virtual ~CScriptArray(); bool Less(const void* a, const void* b, bool asc); void* GetArrayItemPointer(std::int32_t index); void* GetDataPointer(void* buffer); void Copy(void* dst, void* src); void Swap(void* a, void* b); void Precache(); bool CheckMaxSize(std::uint32_t numElements); void Resize(std::int32_t delta, std::uint32_t at); void CreateBuffer(SArrayBuffer** buf, std::uint32_t numElements); void DeleteBuffer(SArrayBuffer* buf); void CopyBuffer(SArrayBuffer* dst, SArrayBuffer* src); void Construct(SArrayBuffer* buf, std::uint32_t start, std::uint32_t end); void Destruct(SArrayBuffer* buf, std::uint32_t start, std::uint32_t end); bool Equals(const void* a, const void* b, asIScriptContext* ctx, SArrayCache* cache) const; }; /** @relatesalso CScriptArray @brief Registers `array` type to **AngelScript** engine */ void RegisterArray(asIScriptEngine* engine); } #endif deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterDictionary.cpp000066400000000000000000001052051512772601700300160ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "RegisterDictionary.h" #include "RegisterArray.h" #include "../../Main.h" #include #include #define AS_USE_ACCESSORS 1 using namespace Death::Containers::Literals; namespace Jazz2::Scripting { // We just define a number here that we assume nobody else is using for // object type user data. The add-ons have reserved the numbers 1000 // through 1999 for this purpose, so we should be fine. const asPWORD DICTIONARY_CACHE = 1003; // This cache holds the object type of the dictionary type and array type // so it isn't necessary to look this up each time the dictionary or array // is created. struct SDictionaryCache { asITypeInfo* dictType; asITypeInfo* arrayType; asITypeInfo* keyType; // This is called from RegisterScriptDictionary static void Setup(asIScriptEngine* engine) { SDictionaryCache* cache = static_cast(engine->GetUserData(DICTIONARY_CACHE)); if (cache == nullptr) { cache = new SDictionaryCache(); engine->SetUserData(cache, DICTIONARY_CACHE); engine->SetEngineUserDataCleanupCallback(SDictionaryCache::Cleanup, DICTIONARY_CACHE); cache->dictType = engine->GetTypeInfoByName("dictionary"); cache->arrayType = engine->GetTypeInfoByDecl("array"); cache->keyType = engine->GetTypeInfoByDecl("string"); } } // This is called from the engine when shutting down static void Cleanup(asIScriptEngine* engine) { SDictionaryCache* cache = static_cast(engine->GetUserData(DICTIONARY_CACHE)); if (cache != nullptr) { delete cache; } } }; //-------------------------------------------------------------------------- // CScriptDictionary implementation CScriptDictionary* CScriptDictionary::Create(asIScriptEngine* engine) { // Use the custom memory routine from AngelScript to allow application to better control how much memory is used CScriptDictionary* obj = static_cast(asAllocMem(sizeof(CScriptDictionary))); new(obj) CScriptDictionary(engine); return obj; } CScriptDictionary* CScriptDictionary::Create(asBYTE* buffer) { // Use the custom memory routine from AngelScript to allow application to better control how much memory is used CScriptDictionary* obj = static_cast(asAllocMem(sizeof(CScriptDictionary))); new(obj) CScriptDictionary(buffer); return obj; } CScriptDictionary::CScriptDictionary(asIScriptEngine* engine) { Init(engine); } void CScriptDictionary::Init(asIScriptEngine* e) { // We start with one reference refCount = 1; gcFlag = false; // Keep a reference to the engine for as long as we live // We don't increment the reference counter, because the // engine will hold a pointer to the object in the GC. engine = e; // The dictionary object type is cached to avoid dynamically parsing it each time SDictionaryCache* cache = static_cast(engine->GetUserData(DICTIONARY_CACHE)); // Notify the garbage collector of this object engine->NotifyGarbageCollectorOfNewObject(this, cache->dictType); } CScriptDictionary::CScriptDictionary(asBYTE* buffer) { // This constructor will always be called from a script // so we can get the engine from the active context asIScriptContext* ctx = asGetActiveContext(); Init(ctx->GetEngine()); // Determine if the dictionary key type is registered as reference type or value type SDictionaryCache& cache = *static_cast(engine->GetUserData(DICTIONARY_CACHE)); bool keyAsRef = cache.keyType->GetFlags() & asOBJ_REF ? true : false; // Initialize the dictionary from the buffer std::uint32_t length = *(std::uint32_t*)buffer; buffer += 4; while (length--) { // Align the buffer pointer on a 4 byte boundary in // case previous value was smaller than 4 bytes if (asPWORD(buffer) & 0x3) { buffer += 4 - (asPWORD(buffer) & 0x3); } // Get the name value pair from the buffer and insert it in the dictionary dictKey_t name; if (keyAsRef) { name = **(dictKey_t**)buffer; buffer += sizeof(dictKey_t*); } else { name = *(dictKey_t*)buffer; buffer += sizeof(dictKey_t); } // Get the type id of the value std::int32_t typeId = *(std::int32_t*)buffer; buffer += sizeof(std::int32_t); // Depending on the type id, the value will inline in the buffer or a pointer void* ref = (void*)buffer; if (typeId >= asTYPEID_INT8 && typeId <= asTYPEID_DOUBLE) { // Convert primitive values to either int64 or double, so we can use the overloaded Set methods std::int64_t i64; double d; switch (typeId) { case asTYPEID_INT8: i64 = *(std::int8_t*)ref; break; case asTYPEID_INT16: i64 = *(std::int16_t*)ref; break; case asTYPEID_INT32: i64 = *(std::int32_t*)ref; break; case asTYPEID_INT64: i64 = *(std::int64_t*)ref; break; case asTYPEID_UINT8: i64 = *(std::uint8_t*)ref; break; case asTYPEID_UINT16: i64 = *(std::uint16_t*)ref; break; case asTYPEID_UINT32: i64 = *(std::uint32_t*)ref; break; case asTYPEID_UINT64: i64 = *(std::uint64_t*)ref; break; case asTYPEID_FLOAT: d = *(float*)ref; break; case asTYPEID_DOUBLE: d = *(double*)ref; break; } if (typeId >= asTYPEID_FLOAT) { Set(name, d); } else { Set(name, i64); } } else { if ((typeId & asTYPEID_MASK_OBJECT) && !(typeId & asTYPEID_OBJHANDLE) && (engine->GetTypeInfoById(typeId)->GetFlags() & asOBJ_REF)) { // Dereference the pointer to get the reference to the actual object ref = *(void**)ref; } Set(name, ref, typeId); } // Advance the buffer pointer with the size of the value if (typeId & asTYPEID_MASK_OBJECT) { asITypeInfo* ti = engine->GetTypeInfoById(typeId); if (ti->GetFlags() & asOBJ_VALUE) { buffer += ti->GetSize(); } else { buffer += sizeof(void*); } } else if (typeId == 0) { // null pointer buffer += sizeof(void*); } else { buffer += engine->GetSizeOfPrimitiveType(typeId); } } } CScriptDictionary::~CScriptDictionary() { // Delete all keys and values DeleteAll(); } void CScriptDictionary::AddRef() const { // We need to clear the GC flag gcFlag = false; asAtomicInc(refCount); } void CScriptDictionary::Release() const { // We need to clear the GC flag gcFlag = false; if (asAtomicDec(refCount) == 0) { this->~CScriptDictionary(); asFreeMem(const_cast(this)); } } int CScriptDictionary::GetRefCount() { return refCount; } void CScriptDictionary::SetGCFlag() { gcFlag = true; } bool CScriptDictionary::GetGCFlag() { return gcFlag; } void CScriptDictionary::EnumReferences(asIScriptEngine* inEngine) { // TODO: If garbage collection can be done from a separate thread, then this method must be // protected so that it doesn't get lost during the iteration if the dictionary is modified // Call the gc enum callback for each of the objects dictMap_t::iterator it; for (it = dict.begin(); it != dict.end(); it++) { if (it->second.m_typeId & asTYPEID_MASK_OBJECT) { asITypeInfo* subType = engine->GetTypeInfoById(it->second.m_typeId); if ((subType->GetFlags() & asOBJ_VALUE) && (subType->GetFlags() & asOBJ_GC)) { // For value types we need to forward the enum callback // to the object so it can decide what to do engine->ForwardGCEnumReferences(it->second.m_valueObj, subType); } else { // For others, simply notify the GC about the reference inEngine->GCEnumCallback(it->second.m_valueObj); } } } } void CScriptDictionary::ReleaseAllReferences(asIScriptEngine* /*engine*/) { // We're being told to release all references in // order to break circular references for dead objects DeleteAll(); } CScriptDictionary& CScriptDictionary::operator=(const CScriptDictionary& other) { // Clear everything we had before DeleteAll(); // Do a shallow copy of the dictionary dictMap_t::const_iterator it; for (it = other.dict.begin(); it != other.dict.end(); it++) { if (it->second.m_typeId & asTYPEID_OBJHANDLE) { Set(it->first, (void*)&it->second.m_valueObj, it->second.m_typeId); } else if (it->second.m_typeId & asTYPEID_MASK_OBJECT) { Set(it->first, (void*)it->second.m_valueObj, it->second.m_typeId); } else { Set(it->first, (void*)&it->second.m_valueInt, it->second.m_typeId); } } return *this; } CScriptDictValue* CScriptDictionary::operator[](const dictKey_t& key) { // Return the existing value if it exists, else insert an empty value return &dict[key]; } const CScriptDictValue* CScriptDictionary::operator[](const dictKey_t& key) const { // Return the existing value if it exists dictMap_t::const_iterator it; it = dict.find(key); if (it != dict.end()) { return &it->second; } // Else raise an exception asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Invalid access to non-existing value"); } return 0; } void CScriptDictionary::Set(const dictKey_t& key, void* value, std::int32_t typeId) { dictMap_t::iterator it; it = dict.find(key); if (it == dict.end()) { it = dict.insert(dictMap_t::value_type(key, CScriptDictValue())).first; } it->second.Set(engine, value, typeId); } // This overloaded method is implemented so that all integer and // unsigned integers types will be stored in the dictionary as int64 // through implicit conversions. This simplifies the management of the // numeric types when the script retrieves the stored value using a // different type. void CScriptDictionary::Set(const dictKey_t& key, const std::int64_t& value) { Set(key, const_cast(&value), asTYPEID_INT64); } // This overloaded method is implemented so that all floating point types // will be stored in the dictionary as double through implicit conversions. // This simplifies the management of the numeric types when the script // retrieves the stored value using a different type. void CScriptDictionary::Set(const dictKey_t& key, const double& value) { Set(key, const_cast(&value), asTYPEID_DOUBLE); } // Returns true if the value was successfully retrieved bool CScriptDictionary::Get(const dictKey_t& key, void* value, std::int32_t typeId) const { dictMap_t::const_iterator it; it = dict.find(key); if (it != dict.end()) { return it->second.Get(engine, value, typeId); } // AngelScript has already initialized the value with a default value, // so we don't have to do anything if we don't find the element, or if // the element is incompatible with the requested type. return false; } // Returns the type id of the stored value int CScriptDictionary::GetTypeId(const dictKey_t& key) const { dictMap_t::const_iterator it; it = dict.find(key); if (it != dict.end()) { return it->second.m_typeId; } return -1; } bool CScriptDictionary::Get(const dictKey_t& key, std::int64_t& value) const { return Get(key, &value, asTYPEID_INT64); } bool CScriptDictionary::Get(const dictKey_t& key, double& value) const { return Get(key, &value, asTYPEID_DOUBLE); } bool CScriptDictionary::Exists(const dictKey_t& key) const { dictMap_t::const_iterator it; it = dict.find(key); if (it != dict.end()) { return true; } return false; } bool CScriptDictionary::IsEmpty() const { if (dict.empty()) { return true; } return false; } std::uint32_t CScriptDictionary::GetSize() const { return std::uint32_t(dict.size()); } bool CScriptDictionary::Delete(const dictKey_t& key) { dictMap_t::iterator it; it = dict.find(key); if (it != dict.end()) { it->second.FreeValue(engine); dict.erase(it); return true; } return false; } void CScriptDictionary::DeleteAll() { dictMap_t::iterator it; for (it = dict.begin(); it != dict.end(); it++) { it->second.FreeValue(engine); } dict.clear(); } CScriptArray* CScriptDictionary::GetKeys() const { // Retrieve the object type for the array from the cache SDictionaryCache* cache = static_cast(engine->GetUserData(DICTIONARY_CACHE)); asITypeInfo* ti = cache->arrayType; // Create the array object CScriptArray* array = CScriptArray::Create(ti, std::uint32_t(dict.size())); std::int32_t current = -1; dictMap_t::const_iterator it; for (it = dict.begin(); it != dict.end(); it++) { current++; *(dictKey_t*)array->At(current) = it->first; } return array; } CScriptDictValue::CScriptDictValue() { m_valueObj = 0; m_typeId = 0; } CScriptDictValue::CScriptDictValue(asIScriptEngine* engine, void* value, std::int32_t typeId) { m_valueObj = 0; m_typeId = 0; Set(engine, value, typeId); } CScriptDictValue::~CScriptDictValue() { if (m_valueObj != nullptr && m_typeId != 0) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { FreeValue(ctx->GetEngine()); } else { // Must not hold an object when destroyed, as then the object will never be freed RETURN_ASSERT((m_typeId & asTYPEID_MASK_OBJECT) == 0); } } } void CScriptDictValue::FreeValue(asIScriptEngine* engine) { // If it is a handle or a ref counted object, call release if (m_typeId & asTYPEID_MASK_OBJECT) { // Let the engine release the object engine->ReleaseScriptObject(m_valueObj, engine->GetTypeInfoById(m_typeId)); m_valueObj = 0; m_typeId = 0; } // For primitives, there's nothing to do } void CScriptDictValue::EnumReferences(asIScriptEngine* inEngine) { // If we're holding a reference, we'll notify the garbage collector of it if (m_valueObj != nullptr) { inEngine->GCEnumCallback(m_valueObj); } // The object type itself is also garbage collected if (m_typeId != 0) { inEngine->GCEnumCallback(inEngine->GetTypeInfoById(m_typeId)); } } void CScriptDictValue::Set(asIScriptEngine* engine, void* value, std::int32_t typeId) { FreeValue(engine); m_typeId = typeId; if (typeId & asTYPEID_OBJHANDLE) { // We're receiving a reference to the handle, so we need to dereference it m_valueObj = *(void**)value; engine->AddRefScriptObject(m_valueObj, engine->GetTypeInfoById(typeId)); } else if (typeId & asTYPEID_MASK_OBJECT) { // Create a copy of the object m_valueObj = engine->CreateScriptObjectCopy(value, engine->GetTypeInfoById(typeId)); if (m_valueObj == 0) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { ctx->SetException("Cannot create copy of object"); } } } else { // Copy the primitive value // We receive a pointer to the value. std::int32_t size = engine->GetSizeOfPrimitiveType(typeId); memcpy(&m_valueInt, value, size); } } void CScriptDictValue::Set(asIScriptEngine* engine, CScriptDictValue& value) { if (value.m_typeId & asTYPEID_OBJHANDLE) { Set(engine, (void*)&value.m_valueObj, value.m_typeId); } else if (value.m_typeId & asTYPEID_MASK_OBJECT) { Set(engine, (void*)value.m_valueObj, value.m_typeId); } else { Set(engine, (void*)&value.m_valueInt, value.m_typeId); } } // This overloaded method is implemented so that all integer and // unsigned integers types will be stored in the dictionary as int64 // through implicit conversions. This simplifies the management of the // numeric types when the script retrieves the stored value using a // different type. void CScriptDictValue::Set(asIScriptEngine* engine, const std::int64_t& value) { Set(engine, const_cast(&value), asTYPEID_INT64); } // This overloaded method is implemented so that all floating point types // will be stored in the dictionary as double through implicit conversions. // This simplifies the management of the numeric types when the script // retrieves the stored value using a different type. void CScriptDictValue::Set(asIScriptEngine* engine, const double& value) { Set(engine, const_cast(&value), asTYPEID_DOUBLE); } bool CScriptDictValue::Get(asIScriptEngine* engine, void* value, int typeId) const { // Return the value if (typeId & asTYPEID_OBJHANDLE) { // A handle can be retrieved if the stored type is a handle of same or compatible type // or if the stored type is an object that implements the interface that the handle refer to. if ((m_typeId & asTYPEID_MASK_OBJECT)) { // Don't allow the get if the stored handle is to a const, but the desired handle is not if ((m_typeId & asTYPEID_HANDLETOCONST) && !(typeId & asTYPEID_HANDLETOCONST)) { return false; } // RefCastObject will increment the refcount if successful engine->RefCastObject(m_valueObj, engine->GetTypeInfoById(m_typeId), engine->GetTypeInfoById(typeId), reinterpret_cast(value)); return true; } } else if (typeId & asTYPEID_MASK_OBJECT) { // Verify that the copy can be made bool isCompatible = false; // Allow a handle to be value assigned if the wanted type is not a handle if ((m_typeId & ~(asTYPEID_OBJHANDLE | asTYPEID_HANDLETOCONST)) == typeId && m_valueObj != 0) { isCompatible = true; } // Copy the object into the given reference if (isCompatible) { engine->AssignScriptObject(value, m_valueObj, engine->GetTypeInfoById(typeId)); return true; } } else { if (m_typeId == typeId) { std::int32_t size = engine->GetSizeOfPrimitiveType(typeId); std::memcpy(value, &m_valueInt, size); return true; } // We know all numbers are stored as either int64 or double, since we register overloaded functions for those // Only bool and enums needs to be treated separately if (typeId == asTYPEID_DOUBLE) { if (m_typeId == asTYPEID_INT64) *(double*)value = double(m_valueInt); else if (m_typeId == asTYPEID_BOOL) { // Use memcpy instead of type cast to make sure the code is endianess agnostic char localValue; std::memcpy(&localValue, &m_valueInt, sizeof(std::int8_t)); *(double*)value = localValue ? 1.0 : 0.0; } else if (m_typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0) { // Use memcpy instead of type cast to make sure the code is endianess agnostic int localValue; std::memcpy(&localValue, &m_valueInt, sizeof(std::int32_t)); *(double*)value = double(localValue); // enums are 32bit } else { // The stored type is an object // TODO: Check if the object has a conversion operator to a primitive value *(double*)value = 0; return false; } return true; } else if (typeId == asTYPEID_INT64) { if (m_typeId == asTYPEID_DOUBLE) *(std::int64_t*)value = std::int64_t(m_valueFlt); else if (m_typeId == asTYPEID_BOOL) { // Use memcpy instead of type cast to make sure the code is endianess agnostic std::int8_t localValue; std::memcpy(&localValue, &m_valueInt, sizeof(std::int8_t)); *(std::int64_t*)value = localValue ? 1 : 0; } else if (m_typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0) { // Use memcpy instead of type cast to make sure the code is endianess agnostic std::int32_t localValue; std::memcpy(&localValue, &m_valueInt, sizeof(std::int32_t)); *(std::int64_t*)value = localValue; // enums are 32bit } else { // The stored type is an object // TODO: Check if the object has a conversion operator to a primitive value *(std::int64_t*)value = 0; return false; } return true; } else if (typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0) { // The desired type is an enum. These are always 32bit integers if (m_typeId == asTYPEID_DOUBLE) { *(std::int32_t*)value = std::int32_t(m_valueFlt); } else if (m_typeId == asTYPEID_INT64) { *(std::int32_t*)value = std::int32_t(m_valueInt); } else if (m_typeId == asTYPEID_BOOL) { // Use memcpy instead of type cast to make sure the code is endianess agnostic std::int8_t localValue; std::memcpy(&localValue, &m_valueInt, sizeof(std::int8_t)); *(std::int32_t*)value = localValue ? 1 : 0; } else if (m_typeId > asTYPEID_DOUBLE && (m_typeId & asTYPEID_MASK_OBJECT) == 0) { // Use memcpy instead of type cast to make sure the code is endianess agnostic std::int32_t localValue; std::memcpy(&localValue, &m_valueInt, sizeof(std::int32_t)); *(std::int32_t*)value = localValue; // enums are 32bit } else { // The stored type is an object // TODO: Check if the object has a conversion operator to a primitive value *(std::int32_t*)value = 0; return false; } return true; } else if (typeId == asTYPEID_BOOL) { if (m_typeId & asTYPEID_OBJHANDLE) { // TODO: Check if the object has a conversion operator to a primitive value *(bool*)value = (m_valueObj ? true : false); } else if (m_typeId & asTYPEID_MASK_OBJECT) { // TODO: Check if the object has a conversion operator to a primitive value *(bool*)value = true; } else { // Compare only the bytes that were actually set asQWORD zero = 0; std::int32_t size = engine->GetSizeOfPrimitiveType(m_typeId); *(bool*)value = std::memcmp(&m_valueInt, &zero, size) == 0 ? false : true; } return true; } } // It was not possible to retrieve the value using the desired typeId return false; } const void* CScriptDictValue::GetAddressOfValue() const { if ((m_typeId & asTYPEID_MASK_OBJECT) && !(m_typeId & asTYPEID_OBJHANDLE)) { // Return the address to the object directly return m_valueObj; } // Return the address of the primitive or the pointer to the object return static_cast(&m_valueObj); } bool CScriptDictValue::Get(asIScriptEngine* engine, std::int64_t& value) const { return Get(engine, &value, asTYPEID_INT64); } bool CScriptDictValue::Get(asIScriptEngine* engine, double& value) const { return Get(engine, &value, asTYPEID_DOUBLE); } int CScriptDictValue::GetTypeId() const { return m_typeId; } static void CScriptDictValue_Construct(void* mem) { new(mem) CScriptDictValue(); } static void CScriptDictValue_Destruct(CScriptDictValue* obj) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { asIScriptEngine* engine = ctx->GetEngine(); obj->FreeValue(engine); } obj->~CScriptDictValue(); } static CScriptDictValue& CScriptDictValue_opAssign(void* ref, std::int32_t typeId, CScriptDictValue* obj) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { asIScriptEngine* engine = ctx->GetEngine(); obj->Set(engine, ref, typeId); } return *obj; } static CScriptDictValue& CScriptDictValue_opAssign(const CScriptDictValue& other, CScriptDictValue* obj) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { asIScriptEngine* engine = ctx->GetEngine(); obj->Set(engine, const_cast(other)); } return *obj; } static CScriptDictValue& CScriptDictValue_opAssign(double val, CScriptDictValue* obj) { return CScriptDictValue_opAssign(&val, asTYPEID_DOUBLE, obj); } static CScriptDictValue& CScriptDictValue_opAssign(std::int64_t val, CScriptDictValue* obj) { return CScriptDictValue_opAssign(&val, asTYPEID_INT64, obj); } static void CScriptDictValue_opCast(void* ref, std::int32_t typeId, CScriptDictValue* obj) { asIScriptContext* ctx = asGetActiveContext(); if (ctx != nullptr) { asIScriptEngine* engine = ctx->GetEngine(); obj->Get(engine, ref, typeId); } } static std::int64_t CScriptDictValue_opConvInt(CScriptDictValue* obj) { std::int64_t value; CScriptDictValue_opCast(&value, asTYPEID_INT64, obj); return value; } static double CScriptDictValue_opConvDouble(CScriptDictValue* obj) { double value; CScriptDictValue_opCast(&value, asTYPEID_DOUBLE, obj); return value; } void ScriptDictionaryFactory_Generic(asIScriptGeneric* gen) { *(CScriptDictionary**)gen->GetAddressOfReturnLocation() = CScriptDictionary::Create(gen->GetEngine()); } void ScriptDictionaryListFactory_Generic(asIScriptGeneric* gen) { asBYTE* buffer = (asBYTE*)gen->GetArgAddress(0); *(CScriptDictionary**)gen->GetAddressOfReturnLocation() = CScriptDictionary::Create(buffer); } void RegisterDictionary(asIScriptEngine* engine) { std::int32_t r; // The array type must be available RETURN_ASSERT(engine->GetTypeInfoByDecl("array")); #if AS_CAN_USE_CPP11 // With C++11 it is possible to use asGetTypeTraits to automatically determine the correct flags that represents the C++ class r = engine->RegisterObjectType("dictionaryValue", sizeof(CScriptDictValue), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asGetTypeTraits()); RETURN_ASSERT(r >= 0); #else r = engine->RegisterObjectType("dictionaryValue", sizeof(CScriptDictValue), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asOBJ_APP_CLASS_CD); RETURN_ASSERT(r >= 0); #endif r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(CScriptDictValue_Construct), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(CScriptDictValue_Destruct), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptDictValue, EnumReferences), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionaryValue", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptDictValue, FreeValue), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(const dictionaryValue &in)", asFUNCTIONPR(CScriptDictValue_opAssign, (const CScriptDictValue&, CScriptDictValue*), CScriptDictValue&), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opHndlAssign(const ?&in)", asFUNCTIONPR(CScriptDictValue_opAssign, (void*, int, CScriptDictValue*), CScriptDictValue&), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opHndlAssign(const dictionaryValue &in)", asFUNCTIONPR(CScriptDictValue_opAssign, (const CScriptDictValue&, CScriptDictValue*), CScriptDictValue&), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(const ?&in)", asFUNCTIONPR(CScriptDictValue_opAssign, (void*, int, CScriptDictValue*), CScriptDictValue&), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(double)", asFUNCTIONPR(CScriptDictValue_opAssign, (double, CScriptDictValue*), CScriptDictValue&), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "dictionaryValue &opAssign(int64)", asFUNCTIONPR(CScriptDictValue_opAssign, (std::int64_t, CScriptDictValue*), CScriptDictValue&), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "void opCast(?&out)", asFUNCTIONPR(CScriptDictValue_opCast, (void*, int, CScriptDictValue*), void), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "void opConv(?&out)", asFUNCTIONPR(CScriptDictValue_opCast, (void*, int, CScriptDictValue*), void), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "int64 opConv()", asFUNCTIONPR(CScriptDictValue_opConvInt, (CScriptDictValue*), std::int64_t), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionaryValue", "double opConv()", asFUNCTIONPR(CScriptDictValue_opConvDouble, (CScriptDictValue*), double), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectType("dictionary", sizeof(CScriptDictionary), asOBJ_REF | asOBJ_GC); RETURN_ASSERT(r >= 0); // Use the generic interface to construct the object since we need the engine pointer, we could also have retrieved the engine pointer from the active context r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_FACTORY, "dictionary@ f()", asFUNCTION(ScriptDictionaryFactory_Generic), asCALL_GENERIC); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_LIST_FACTORY, "dictionary @f(int &in) {repeat {string, ?}}", asFUNCTION(ScriptDictionaryListFactory_Generic), asCALL_GENERIC); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_ADDREF, "void f()", asMETHOD(CScriptDictionary, AddRef), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_RELEASE, "void f()", asMETHOD(CScriptDictionary, Release), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "dictionary &opAssign(const dictionary &in)", asMETHODPR(CScriptDictionary, operator=, (const CScriptDictionary&), CScriptDictionary&), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const ?&in)", asMETHODPR(CScriptDictionary, Set, (const dictKey_t&, void*, int), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, ?&out) const", asMETHODPR(CScriptDictionary, Get, (const dictKey_t&, void*, int) const, bool), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const int64&in)", asMETHODPR(CScriptDictionary, Set, (const dictKey_t&, const std::int64_t&), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, int64&out) const", asMETHODPR(CScriptDictionary, Get, (const dictKey_t&, std::int64_t&) const, bool), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "void set(const string &in, const double&in)", asMETHODPR(CScriptDictionary, Set, (const dictKey_t&, const double&), void), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "bool get(const string &in, double&out) const", asMETHODPR(CScriptDictionary, Get, (const dictKey_t&, double&) const, bool), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "bool exists(const string &in) const", asMETHOD(CScriptDictionary, Exists), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "bool isEmpty() const", asMETHOD(CScriptDictionary, IsEmpty), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "uint getSize() const", asMETHOD(CScriptDictionary, GetSize), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "bool delete(const string &in)", asMETHOD(CScriptDictionary, Delete), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "void deleteAll()", asMETHOD(CScriptDictionary, DeleteAll), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "array @getKeys() const", asMETHOD(CScriptDictionary, GetKeys), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "dictionaryValue &opIndex(const string &in)", asMETHODPR(CScriptDictionary, operator[], (const dictKey_t&), CScriptDictValue*), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("dictionary", "const dictionaryValue &opIndex(const string &in) const", asMETHODPR(CScriptDictionary, operator[], (const dictKey_t&) const, const CScriptDictValue*), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Register GC behaviours r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CScriptDictionary, GetRefCount), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CScriptDictionary, SetGCFlag), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CScriptDictionary, GetGCFlag), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptDictionary, EnumReferences), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("dictionary", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptDictionary, ReleaseAllReferences), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #if AS_USE_STLNAMES == 1 // Same as isEmpty r = engine->RegisterObjectMethod("dictionary", "bool empty() const", asMETHOD(CScriptDictionary, IsEmpty), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as getSize r = engine->RegisterObjectMethod("dictionary", "uint size() const", asMETHOD(CScriptDictionary, GetSize), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as delete r = engine->RegisterObjectMethod("dictionary", "void erase(const string &in)", asMETHOD(CScriptDictionary, Delete), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Same as deleteAll r = engine->RegisterObjectMethod("dictionary", "void clear()", asMETHOD(CScriptDictionary, DeleteAll), asCALL_THISCALL); RETURN_ASSERT(r >= 0); #endif // Cache some things the dictionary will need at runtime SDictionaryCache::Setup(engine); } CScriptDictionary::CIterator CScriptDictionary::begin() const { return CIterator(*this, dict.begin()); } CScriptDictionary::CIterator CScriptDictionary::end() const { return CIterator(*this, dict.end()); } CScriptDictionary::CIterator CScriptDictionary::find(const dictKey_t& key) const { return CIterator(*this, dict.find(key)); } CScriptDictionary::CIterator::CIterator(const CScriptDictionary& dict, dictMap_t::const_iterator it): m_it(it), m_dict(dict) { } void CScriptDictionary::CIterator::operator++() { ++m_it; } void CScriptDictionary::CIterator::operator++(int) { ++m_it; // Normally the post increment would return a copy of the object with the original state, // but it is rarely used so we skip this extra copy to avoid unnecessary overhead } CScriptDictionary::CIterator& CScriptDictionary::CIterator::operator*() { return *this; } bool CScriptDictionary::CIterator::operator==(const CIterator& other) const { return m_it == other.m_it; } bool CScriptDictionary::CIterator::operator!=(const CIterator& other) const { return m_it != other.m_it; } const dictKey_t& CScriptDictionary::CIterator::GetKey() const { return m_it->first; } int CScriptDictionary::CIterator::GetTypeId() const { return m_it->second.m_typeId; } bool CScriptDictionary::CIterator::GetValue(std::int64_t& value) const { return m_it->second.Get(m_dict.engine, &value, asTYPEID_INT64); } bool CScriptDictionary::CIterator::GetValue(double& value) const { return m_it->second.Get(m_dict.engine, &value, asTYPEID_DOUBLE); } bool CScriptDictionary::CIterator::GetValue(void* value, std::int32_t typeId) const { return m_it->second.Get(m_dict.engine, value, typeId); } const void* CScriptDictionary::CIterator::GetAddressOfValue() const { return m_it->second.GetAddressOfValue(); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterDictionary.h000066400000000000000000000136161512772601700274670ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../nCine/Base/HashMap.h" #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Scripting { class CScriptArray; class CScriptDictionary; class CScriptDictValue; typedef String dictKey_t; typedef HashMap dictMap_t; /** @brief **AngelScript** dictionary value */ class CScriptDictValue { friend class CScriptDictionary; public: // This class must not be declared as local variable in C++, because it needs // to receive the script engine pointer in all operations. The engine pointer // is not kept as member in order to keep the size down CScriptDictValue(); CScriptDictValue(asIScriptEngine* engine, void* value, std::int32_t typeId); // Destructor must not be called without first calling FreeValue, otherwise a memory leak will occur ~CScriptDictValue(); // Replace the stored value void Set(asIScriptEngine* engine, void* value, std::int32_t typeId); void Set(asIScriptEngine* engine, const std::int64_t& value); void Set(asIScriptEngine* engine, const double& value); void Set(asIScriptEngine* engine, CScriptDictValue& value); // Gets the stored value. Returns false if the value isn't compatible with the informed typeId bool Get(asIScriptEngine* engine, void* value, std::int32_t typeId) const; bool Get(asIScriptEngine* engine, std::int64_t& value) const; bool Get(asIScriptEngine* engine, double& value) const; // Returns the address of the stored value for inspection const void* GetAddressOfValue() const; // Returns the type id of the stored value std::int32_t GetTypeId() const; // Free the stored value void FreeValue(asIScriptEngine* engine); // GC callback void EnumReferences(asIScriptEngine* engine); protected: #ifndef DOXYGEN_GENERATING_OUTPUT union { asINT64 m_valueInt; double m_valueFlt; void* m_valueObj; }; std::int32_t m_typeId; #endif }; /** @brief **AngelScript** dictionary */ class CScriptDictionary { public: // Factory functions static CScriptDictionary* Create(asIScriptEngine* engine); // Called from the script to instantiate a dictionary from an initialization list static CScriptDictionary* Create(asBYTE* buffer); // Reference counting void AddRef() const; void Release() const; // Reassign the dictionary CScriptDictionary& operator=(const CScriptDictionary& other); // Sets a key/value pair void Set(const dictKey_t& key, void* value, std::int32_t typeId); void Set(const dictKey_t& key, const std::int64_t& value); void Set(const dictKey_t& key, const double& value); // Gets the stored value. Returns false if the value isn't compatible with the informed typeId bool Get(const dictKey_t& key, void* value, std::int32_t typeId) const; bool Get(const dictKey_t& key, std::int64_t& value) const; bool Get(const dictKey_t& key, double& value) const; // Index accessors. If the dictionary is not const it inserts the value if it doesn't already exist // If the dictionary is const then a script exception is set if it doesn't exist and a null pointer is returned CScriptDictValue* operator[](const dictKey_t& key); const CScriptDictValue* operator[](const dictKey_t& key) const; // Returns the type id of the stored value, or negative if it doesn't exist std::int32_t GetTypeId(const dictKey_t& key) const; // Returns true if the key is set bool Exists(const dictKey_t& key) const; // Returns true if there are no key/value pairs in the dictionary bool IsEmpty() const; // Returns the number of key/value pairs in the dictionary asUINT GetSize() const; // Deletes the key bool Delete(const dictKey_t& key); // Deletes all keys void DeleteAll(); // Get an array of all keys CScriptArray* GetKeys() const; /** @brief **AngelScript** dictionary iterator */ class CIterator { friend class CScriptDictionary; public: void operator++(); // Pre-increment void operator++(int); // Post-increment // This is needed to support C++11 range-for CIterator& operator*(); bool operator==(const CIterator& other) const; bool operator!=(const CIterator& other) const; // Accessors const dictKey_t& GetKey() const; std::int32_t GetTypeId() const; bool GetValue(std::int64_t& value) const; bool GetValue(double& value) const; bool GetValue(void* value, std::int32_t typeId) const; const void* GetAddressOfValue() const; protected: CIterator(); CIterator(const CScriptDictionary& dict, dictMap_t::const_iterator it); CIterator& operator=(const CIterator&) { return *this; } // Not used #ifndef DOXYGEN_GENERATING_OUTPUT dictMap_t::const_iterator m_it; const CScriptDictionary& m_dict; #endif }; CIterator begin() const; CIterator end() const; CIterator find(const dictKey_t& key) const; // Garbage collections behaviours std::int32_t GetRefCount(); void SetGCFlag(); bool GetGCFlag(); void EnumReferences(asIScriptEngine* engine); void ReleaseAllReferences(asIScriptEngine* engine); protected: // Since the dictionary uses the asAllocMem and asFreeMem functions to allocate memory // the constructors are made protected so that the application cannot allocate it // manually in a different way CScriptDictionary(asIScriptEngine* engine); CScriptDictionary(asBYTE* buffer); // We don't want anyone to call the destructor directly, it should be called through the Release method virtual ~CScriptDictionary(); // Cache the object types needed void Init(asIScriptEngine* engine); #ifndef DOXYGEN_GENERATING_OUTPUT // Our properties asIScriptEngine* engine; mutable std::int32_t refCount; mutable bool gcFlag; dictMap_t dict; #endif }; /** @relatesalso CScriptDictionary @brief Registers `dictionary` type to **AngelScript** engine */ void RegisterDictionary(asIScriptEngine* engine); } #endif deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterImGuiBindings.cpp000066400000000000000000001050041512772601700303760ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) && defined(WITH_IMGUI) #include "RegisterImGuiBindings.h" #include "RegisterArray.h" #include "../../Main.h" #include "../../nCine/Primitives/Vector2.h" #include #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Scripting { void RegisterImGuiBindings(asIScriptEngine* engine) { engine->SetDefaultNamespace("ImGui"); engine->RegisterGlobalFunction("bool Begin(const string&in, bool, int=0)", asFUNCTION(+[](const String& name, bool opened, std::int32_t flags) { return ImGui::Begin(name.data(), &opened, flags); }), asCALL_CDECL); engine->RegisterGlobalFunction("void End()", asFUNCTIONPR(ImGui::End, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginChild(const string&in)", asFUNCTION(+[](const String& name) { return ImGui::Begin(name.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void EndChild()", asFUNCTIONPR(ImGui::EndChild, (), void), asCALL_CDECL); // Deprecated //engine->RegisterGlobalFunction("vec2 GetContentRegionMax()", asFUNCTION(+[]() -> Vector2f { // return ImGui::GetContentRegionMax(); }), asCALL_CDECL); //engine->RegisterGlobalFunction("vec2 GetContentRegionAvail()", asFUNCTION(+[]() -> Vector2f { // return ImGui::GetContentRegionAvail(); }), asCALL_CDECL); //engine->RegisterGlobalFunction("vec2 GetWindowContentRegionMin()", asFUNCTION(+[]() -> Vector2f { // return ImGui::GetWindowContentRegionMin(); }), asCALL_CDECL); //engine->RegisterGlobalFunction("vec2 GetWindowContentRegionMax()", asFUNCTION(+[]() -> Vector2f { // return ImGui::GetWindowContentRegionMax(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetWindowPos()", asFUNCTION(+[]() -> Vector2f { return ImGui::GetWindowPos(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetWindowSize()", asFUNCTION(+[]() -> Vector2f { return ImGui::GetWindowSize(); }), asCALL_CDECL); engine->RegisterGlobalFunction("float GetWindowWedth()", asFUNCTIONPR(ImGui::GetWindowWidth, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetWindowHeight()", asFUNCTIONPR(ImGui::GetWindowHeight, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsWindowCollapsed()", asFUNCTIONPR(ImGui::IsWindowCollapsed, (), bool), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsWindowAppearing()", asFUNCTIONPR(ImGui::IsWindowAppearing, (), bool), asCALL_CDECL); engine->RegisterGlobalFunction("void SetNextWindowPos(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetNextWindowPos(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetNextWindowSize(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetNextWindowSize(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetNextWindowContentSize(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetNextWindowContentSize(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetNextWindowCollapsed(bool)", asFUNCTION(+[](bool v) { ImGui::SetNextWindowCollapsed(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetNextWindowFocus()", asFUNCTION(+[]() { ImGui::SetNextWindowFocus(); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowPos(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetWindowPos(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowSize(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetWindowSize(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowCollapsed(bool)", asFUNCTION(+[](bool v) { ImGui::SetWindowCollapsed(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowFocus()", asFUNCTION(+[]() { ImGui::SetWindowFocus(); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowPos(const string&in, const vec2&in)", asFUNCTION(+[](const String& name, const Vector2f& v) { ImGui::SetWindowPos(name.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowSize(const string&in, const vec2&in)", asFUNCTION(+[](const String& name, const Vector2f& v) { ImGui::SetWindowSize(name.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowCollapsed(const string&in, bool)", asFUNCTION(+[](const String& name, bool v) { ImGui::SetWindowCollapsed(name.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetWindowFocus(const string&in)", asFUNCTION(+[](const String& v) { ImGui::SetWindowFocus(v.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("float GetScrollX()", asFUNCTIONPR(ImGui::GetScrollX, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetScrollY()", asFUNCTIONPR(ImGui::GetScrollY, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetScrollMaxX()", asFUNCTIONPR(ImGui::GetScrollMaxX, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetScrollMaxY()", asFUNCTIONPR(ImGui::GetScrollMaxY, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("void SetScrollX(float)", asFUNCTIONPR(ImGui::SetScrollX, (float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void SetScrollY(float)", asFUNCTIONPR(ImGui::SetScrollY, (float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void SetScrollFromPosY(float, float = 0.5f)", asFUNCTIONPR(ImGui::SetScrollFromPosY, (float,float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void Separator()", asFUNCTIONPR(ImGui::Separator, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("void SameLine(float = 0.0f, float = -1.0f)", asFUNCTIONPR(ImGui::SameLine, (float,float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void NewLine()", asFUNCTIONPR(ImGui::NewLine, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("void Spacing()", asFUNCTIONPR(ImGui::Spacing, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("void Dummy(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::Dummy(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void Indent(float = 0.0f)", asFUNCTIONPR(ImGui::Indent, (float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void Unindent(float = 0.0f)", asFUNCTIONPR(ImGui::Unindent, (float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void BeginGroup()", asFUNCTIONPR(ImGui::BeginGroup, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("void EndGroup()", asFUNCTIONPR(ImGui::EndGroup, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetCursorPos()", asFUNCTION(+[]() { return ImGui::GetCursorPos(); }), asCALL_CDECL); engine->RegisterGlobalFunction("float GetCursorPosX()", asFUNCTIONPR(ImGui::GetCursorPosX, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetCursorPosY()", asFUNCTIONPR(ImGui::GetCursorPosY, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("void SetCursorPos(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetCursorPos(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetCursorPosX(float)", asFUNCTIONPR(ImGui::SetCursorPosX, (float), void), asCALL_CDECL); engine->RegisterGlobalFunction("void SetCursorPosY(float)", asFUNCTIONPR(ImGui::SetCursorPosY, (float), void), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetCursorStartPos()", asFUNCTION(+[]() { return ImGui::GetCursorStartPos(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetCursorScreenPos()", asFUNCTION(+[]() { return ImGui::GetCursorScreenPos(); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetCursorScreenPos(const vec2&in)", asFUNCTION(+[](const Vector2f& v) { ImGui::SetCursorScreenPos(v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void AlignTextToFramePadding()", asFUNCTIONPR(ImGui::AlignTextToFramePadding, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("float GetTextLineHeight()", asFUNCTIONPR(ImGui::GetTextLineHeight, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetTextLineHeightWithSpacing()", asFUNCTIONPR(ImGui::GetTextLineHeightWithSpacing, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetFrameHeight()", asFUNCTIONPR(ImGui::GetFrameHeight, (), float), asCALL_CDECL); engine->RegisterGlobalFunction("float GetFrameHeightWithSpacing()", asFUNCTIONPR(ImGui::GetFrameHeightWithSpacing, (), float), asCALL_CDECL); // Columns engine->RegisterGlobalFunction("void Columns(int = 1, const string&in = string(), bool = true)", asFUNCTION(+[](std::int32_t a, const String& b, bool c) { ImGui::Columns(a, !b.empty() ? b.data() : nullptr, c); }), asCALL_CDECL); engine->RegisterGlobalFunction("void NextColumn()", asFUNCTION(+[]() { ImGui::NextColumn(); }), asCALL_CDECL); engine->RegisterGlobalFunction("int GetColumnIndex()", asFUNCTION(+[]() { return ImGui::GetColumnIndex(); }), asCALL_CDECL); engine->RegisterGlobalFunction("float GetColumnWidth(int = -1)", asFUNCTION(+[](std::int32_t a) { return ImGui::GetColumnWidth(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetColumnWidth(int, float)", asFUNCTION(+[](std::int32_t a, float b) { ImGui::SetColumnWidth(a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("float GetColumnOffset(int = -1)", asFUNCTION(+[](std::int32_t a) { return ImGui::GetColumnOffset(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetColumnOffset(int, float)", asFUNCTION(+[](std::int32_t a, float b) { ImGui::SetColumnOffset(a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("int GetColumnsCount()", asFUNCTION(+[]() { return ImGui::GetColumnsCount(); }), asCALL_CDECL); // ID scopes engine->RegisterGlobalFunction("void PushID(const string&in)", asFUNCTION(+[](const String& n) { ImGui::PushID(n.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void PushID(int int_id)", asFUNCTION(+[](std::int32_t id) { ImGui::PushID(id); }), asCALL_CDECL); engine->RegisterGlobalFunction("void PopID()", asFUNCTIONPR(ImGui::PopID, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("uint GetID(const string&in)", asFUNCTION(+[](const String& n) { return ImGui::GetID(n.data()); }), asCALL_CDECL); // Widgets: Text engine->RegisterGlobalFunction("void Text(const string&in)", asFUNCTION(+[](const String& n) { ImGui::TextUnformatted(n.data()); }), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("void TextColored(const Color&in, const string&in)", asFUNCTION(+[](const Colorf& c, const String& n) { ImGui::TextColored(c, n.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void TextWrapped(const string&in)", asFUNCTION(+[](const String& n) { ImGui::TextWrapped(n.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void LabelText(const string&in, const string&in)", asFUNCTION(+[](const String& l, const String& n) { ImGui::LabelText(l.data(), n.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void Bullet()", asFUNCTIONPR(ImGui::Bullet, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("void BulletText(const string&in)", asFUNCTION(+[](const String& n) { ImGui::BulletText(n.data()); }), asCALL_CDECL); // Widgets: Main engine->RegisterGlobalFunction("bool Button(const string&in, const vec2&in = vec2(0,0))", asFUNCTION(+[](const String& n, const Vector2f& v) { return ImGui::Button(n.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SmallButton(const string&in)", asFUNCTION(+[](const String& n) { return ImGui::SmallButton(n.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InvisibleButton(const string&in, const vec2&in)", asFUNCTION(+[](const String& id, const Vector2f& v) { return ImGui::InvisibleButton(id.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void Image(uint, const vec2&in)", asFUNCTION(+[](std::uint32_t u, const Vector2f& v) { ImGui::Image((ImTextureID)u, v); }), asCALL_CDECL); //engine->RegisterGlobalFunction("bool Checkbox(const string&in, bool&inout)", asFUNCTION(+[](const String& n, bool& v) { // return ImGui::Checkbox(n.data(), &v); }), asCALL_CDECL); //engine->RegisterGlobalFunction("bool CheckboxFlags(const string&in, uint&inout, uint)", asFUNCTION(+[](const String& n, unsigned& f, unsigned v) { // return ImGui::CheckboxFlags(n.data(), &f, v); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool RadioButton(const string&in, bool)", asFUNCTION(+[](const String& n, bool v) { return ImGui::RadioButton(n.data(), v); }), asCALL_CDECL); //engine->RegisterGlobalFunction("bool RadioButton(const string&in, int&inout, int)", asFUNCTION(+[](const String& n, int& v, int vv) { // return ImGui::RadioButton(n.data(), &v, vv); }), asCALL_CDECL); engine->RegisterGlobalFunction("void ProgressBar(float)", asFUNCTION(+[](float v) { ImGui::ProgressBar(v); }), asCALL_CDECL); // Widgets: Combo Box engine->RegisterGlobalFunction("bool BeginCombo(const string&in, const string&in, int = 0)", asFUNCTION(+[](const String& id, const String& prevItem, std::int32_t flags) { return ImGui::BeginCombo(id.data(), prevItem.data(), flags); }), asCALL_CDECL); engine->RegisterGlobalFunction("void EndCombo()", asFUNCTIONPR(ImGui::EndCombo, (), void), asCALL_CDECL); /*static char imgui_comboItem[4096]; engine->RegisterGlobalFunction("bool Combo(const string&in, int&inout, const array@+)", asFUNCTION(+[](const String& lbl, int& index, const CScriptArray* items) { std::memset(imgui_comboItem, 0, sizeof(char) * 4096); std::size_t offset = 0; for (unsigned i = 0; i < items->GetSize(); ++i) { String* str = ((String*)items->At(i)); strcpy(imgui_comboItem + offset, str->data()); offset += str->size() + 1; } return ImGui::Combo(lbl.data(), &index, imgui_comboItem, -1); }), asCALL_CDECL);*/ // Widgets: Drags /*engine->RegisterGlobalFunction("bool DragFloat(const string&in, float&inout, float = 1.0f, float = 0.0f, float = 0.0f)", asFUNCTION(+[](const String& n, float& v, float speed, float mn, float mx) { return ImGui::DragFloat(n.data(), &v, speed, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragFloat2(const string&in, vec2&inout)", asFUNCTION(+[](const String& n, Vector2f& v) { return ImGui::DragFloat2(n.data(), &v.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragFloat3(const string&in, vec3&inout)", asFUNCTION(+[](const String& n, Vector3f& v) { return ImGui::DragFloat3(n.data(), &v.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragFloat4(const string&in, vec4&inout)", asFUNCTION(+[](const String& n, Vector4f& v) { return ImGui::DragFloat4(n.data(), &v.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragInt(const string&in, int&inout, int = 0, int = 0)", asFUNCTIONPR(+[](const String& n, int& v, int mn, int mx) { return ImGui::DragInt(n.data(), &v, 1.0f, mn, mx); }, (const String&, int&, int, int), bool), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragInt2(const string&in, vec2i&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, Vector2i& v, int mn, int mx) { return ImGui::DragInt2(n.data(), &v.X, 1.0f, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragInt3(const string&in, vec3i&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, Vector3i& v, int mn, int mx) { return ImGui::DragInt3(n.data(), &v.X, 1.0f, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragInt4(const string&in, vec4i&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, Vector4i& v, int mn, int mx) { return ImGui::DragInt4(n.data(), &v.X, 1.0f, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragFloatRange2(const string&in, float&inout, float&inout, float = 0.0f, float = 1.0f)", asFUNCTION(+[](const String& n, float& v0, float& v1, float mn, float mx) { return ImGui::DragFloatRange2(n.data(), &v0, &v1, 1.0f, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool DragIntRange2(const string&in, int&inout, int&inout, int, int)", asFUNCTION(+[](const String& n, int& v0, int& v1, int mn, int mx) { return ImGui::DragIntRange2(n.data(), &v0, &v1, 1.0f, mn, mx); }), asCALL_CDECL);*/ // Widgets: Input with Keyboard /*static char imgui_text_buffer[4096]; // shared with multiple widgets engine->RegisterGlobalFunction("bool InputText(const string&in, string&inout)", asFUNCTION(+[](const String& id, String& val) { std::memset(imgui_text_buffer, 0, sizeof(char) * 4096); strcpy(imgui_text_buffer, val.data()); if (ImGui::InputText(id.data(), imgui_text_buffer, 4096)) { val = imgui_text_buffer; return true; } return false; }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputTextMultiline(const string&in, string&inout)", asFUNCTION(+[](const String& id, String& val) { std::memset(imgui_text_buffer, 0, sizeof(char) * 4096); strcpy(imgui_text_buffer, val.data()); if (ImGui::InputTextMultiline(id.data(), imgui_text_buffer, 4096)) { val = imgui_text_buffer; return true; } return false; }), asCALL_CDECL);*/ /*engine->RegisterGlobalFunction("bool InputFloat(const string&, float&inout)", asFUNCTION(+[](const String& id, float& val) { return ImGui::InputFloat(id.data(), &val); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputFloat2(const string&, vec2&inout)", asFUNCTION(+[](const String& id, Vector2f& val) { return ImGui::InputFloat2(id.data(), &val.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputFloat3(const string&, vec3&inout)", asFUNCTION(+[](const String& id, Vector3f& val) { return ImGui::InputFloat3(id.data(), &val.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputFloat4(const string&, vec4&inout)", asFUNCTION(+[](const String& id, Vector4f& val) { return ImGui::InputFloat4(id.data(), &val.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputInt(const string&, int&inout)", asFUNCTION(+[](const String& id, int& val) { return ImGui::InputInt(id.data(), &val); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputInt2(const string&, vec2i&inout)", asFUNCTION(+[](const String& id, Vector2i& val) { return ImGui::InputInt2(id.data(), &val.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputInt3(const string&, vec3i&inout)", asFUNCTION(+[](const String& id, Vector3i& val) { return ImGui::InputInt3(id.data(), &val.X); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool InputInt4(const string&, vec4i&inout)", asFUNCTION(+[](const String& id, Vector4i& val) { return ImGui::InputInt4(id.data(), &val.X); }), asCALL_CDECL);*/ // Widgets: Sliders (tip: ctrl+click on a slider to input with keyboard. manually input values aren't clamped, can go off-bounds) /*engine->RegisterGlobalFunction("bool SliderFloat(const string&in, float&inout, float = 0.0f, float = 0.0f)", asFUNCTION(+[](const String& n, float& v, float mn, float mx) { return ImGui::SliderFloat(n.data(), &v, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderFloat2(const string&in, vec2&inout, float, float)", asFUNCTION(+[](const String& n, Vector2f& v, float mn, float mx) { return ImGui::SliderFloat2(n.data(), &v.X, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderFloat3(const string&in, vec3&inout, float, float)", asFUNCTION(+[](const String& n, Vector3f& v, float mn, float mx) { return ImGui::SliderFloat3(n.data(), &v.X, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderFloat4(const string&in, vec4&inout, float, float)", asFUNCTION(+[](const String& n, Vector4f& v, float mn, float mx) { return ImGui::SliderFloat4(n.data(), &v.X, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderInt(const string&in, int&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, int& v, int mn, int mx) { return ImGui::SliderInt(n.data(), &v, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderInt2(const string&in, vec2i&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, Vector2i& v, int mn, int mx) { return ImGui::SliderInt2(n.data(), &v.X, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderInt3(const string&in, vec3i&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, Vector3i& v, int mn, int mx) { return ImGui::SliderInt3(n.data(), &v.X, mn, mx); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool SliderInt3(const string&in, vec4i&inout, int = 0, int = 0)", asFUNCTION(+[](const String& n, Vector4i& v, int mn, int mx) { return ImGui::SliderInt4(n.data(), &v.X, mn, mx); }), asCALL_CDECL);*/ // Widgets: Color Editor/Picker /*engine->RegisterGlobalFunction("bool ColorEdit3(const string&in, Color&inout)", asFUNCTION(+[](const String& id, Colorf& v) { return ImGui::ColorEdit3(id.data(), &v.R); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool ColorEdit4(const string&in, Color&inout)", asFUNCTION(+[](const String& id, Colorf& v) { return ImGui::ColorEdit4(id.data(), &v.R); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool ColorPicker3(const string&in, Color&inout)", asFUNCTION(+[](const String& id, Colorf& v) { return ImGui::ColorPicker3(id.data(), &v.R); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool ColorPicker4(const string&in, Color&inout)", asFUNCTION(+[](const String& id, Colorf& v) { return ImGui::ColorPicker4(id.data(), &v.R); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool ColorButton(const string&in, const Color&in)", asFUNCTION(+[](const String& id, const Colorf& v) { return ImGui::ColorButton(id.data(), v); }), asCALL_CDECL);*/ // Widgets: Trees engine->RegisterGlobalFunction("bool TreeNode(const string&in)", asFUNCTION(+[](const String& id) { return ImGui::TreeNode(id.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void TreePush(const string&in)", asFUNCTION(+[](const String& id) { ImGui::TreePush(id.data()); }), asCALL_CDECL); engine->RegisterGlobalFunction("void TreePop()", asFUNCTIONPR(ImGui::TreePop, (), void), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("void TreeAdvanceToLabelPos()", asFUNCTIONPR(ImGui::TreeAdvanceToLabelPos, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("float GetTreeNodeToLabelSpacing()", asFUNCTIONPR(ImGui::GetTreeNodeToLabelSpacing, (), float), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("void SetNextTreeNodeOpen(bool)", asFUNCTION(+[](bool val) { // ImGui::SetNextTreeNodeOpen(val); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool CollapsingHeader(const string&in)", asFUNCTION(+[](const String& n) { return ImGui::CollapsingHeader(n.data()); }), asCALL_CDECL); //engine->RegisterGlobalFunction("bool CollapsingHeader(const string&in, bool&inout)", asFUNCTION(+[](const String& n, bool& v) { // return ImGui::CollapsingHeader(n.data(), &v); }), asCALL_CDECL); // Widgets: Selectable / Lists engine->RegisterGlobalFunction("bool Selectable(const string&in, bool = false)", asFUNCTION(+[](const String& n, bool v) { return ImGui::Selectable(n.data(), v); }), asCALL_CDECL); // Values engine->RegisterGlobalFunction("void Value(const string&in, bool)", asFUNCTION(+[](const String& n, bool v) { ImGui::Value(n.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void Value(const string&in, int)", asFUNCTION(+[](const String& n, std::int32_t v) { ImGui::Value(n.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void Value(const string&in, uint)", asFUNCTION(+[](const String& n, std::uint32_t v) { ImGui::Value(n.data(), v); }), asCALL_CDECL); engine->RegisterGlobalFunction("void Value(const string&in, float)", asFUNCTION(+[](const String& n, float v) { ImGui::Value(n.data(), v); }), asCALL_CDECL); // Tooltips engine->RegisterGlobalFunction("bool BeginTooltip()", asFUNCTIONPR(ImGui::BeginTooltip, (), bool), asCALL_CDECL); engine->RegisterGlobalFunction("void EndTooltip()", asFUNCTIONPR(ImGui::EndTooltip, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("void SetTooltip(const string&in)", asFUNCTION(+[](const String& t) { ImGui::SetTooltip(t.data()); }), asCALL_CDECL); // Menus (disabled for now) /*engine->RegisterGlobalFunction("bool BeginMainMenuBar()", asFUNCTIONPR(+[]() { return ImGui::BeginMainMenuBar(); }, (), bool), asCALL_CDECL); engine->RegisterGlobalFunction("void EndMainMenuBar()", asFUNCTIONPR(+[]() { ImGui::EndMainMenuBar(); }, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginMenuBar()", asFUNCTIONPR(+[]() { return ImGui::BeginMenuBar(); }, (), bool), asCALL_CDECL); engine->RegisterGlobalFunction("void EndMenuBar()", asFUNCTIONPR(+[]() { ImGui::EndMenuBar(); }, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginMenu(const string&in, bool = true)", asFUNCTIONPR(+[](const String& a, bool b) { return ImGui::BeginMenu(!a.empty() ? a.data() : nullptr, b); }, (const String&, bool), bool), asCALL_CDECL); engine->RegisterGlobalFunction("void EndMenu()", asFUNCTIONPR(+[]() { ImGui::EndMenu(); }, (), void), asCALL_CDECL); engine->RegisterGlobalFunction("bool MenuItem(const string&in, const string&in = string(), bool = false, bool = true)", asFUNCTIONPR(+[](const String& a, const String& b, bool c, bool d) { return ImGui::MenuItem(!a.empty() ? a.data() : nullptr, !b.empty() ? b.data() : nullptr, c, d); }, (const String&, const String&, bool, bool), bool), asCALL_CDECL); engine->RegisterGlobalFunction("bool MenuItem(const string&in, const string&in, bool&inout, bool = true)", asFUNCTIONPR(+[](const String& a, const String& b, bool& c, bool d) { return ImGui::MenuItem(!a.empty() ? a.data() : nullptr, !b.empty() ? b.data() : nullptr, &c, d); }, (const String&, const String&, bool&, bool), bool), asCALL_CDECL);*/ // Popups engine->RegisterGlobalFunction("void OpenPopup(const string&in)", asFUNCTION(+[](const String& a) { ImGui::OpenPopup(!a.empty() ? a.data() : nullptr); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginPopup(const string&in, int = 0)", asFUNCTION(+[](const String& a, std::int32_t b) { return ImGui::BeginPopup(!a.empty() ? a.data() : nullptr, (ImGuiWindowFlags)b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginPopupContextItem(const string&in = string(), int = 1)", asFUNCTION(+[](const String& a, std::int32_t b) { return ImGui::BeginPopupContextItem(!a.empty() ? a.data() : nullptr, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginPopupContextWindow(const string&in = string(), int = 1)", asFUNCTION(+[](const String& a, std::int32_t b) { return ImGui::BeginPopupContextWindow(!a.empty() ? a.data() : nullptr, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool BeginPopupContextVoid(const string&in = string(), int = 1)", asFUNCTION(+[](const String& a, std::int32_t b) { return ImGui::BeginPopupContextVoid(!a.empty() ? a.data() : nullptr, b); }), asCALL_CDECL); //engine->RegisterGlobalFunction("bool BeginPopupModal(const string&in, bool&inout = null, int = 0)", asFUNCTION(+[](const String& a, bool& b, std::int32_t c) { // return ImGui::BeginPopupModal(!a.empty() ? a.data() : nullptr, &b, (ImGuiWindowFlags)c); }), asCALL_CDECL); engine->RegisterGlobalFunction("void EndPopup()", asFUNCTION(+[]() { ImGui::EndPopup(); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool OpenPopupOnItemClick(const string&in = string(), int = 1)", asFUNCTION(+[](const String& a, std::int32_t b) { return ImGui::OpenPopupOnItemClick(!a.empty() ? a.data() : nullptr, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsPopupOpen(const string&in)", asFUNCTION(+[](const String& a) { return ImGui::IsPopupOpen(!a.empty() ? a.data() : nullptr); }), asCALL_CDECL); engine->RegisterGlobalFunction("void CloseCurrentPopup()", asFUNCTION(+[]() { ImGui::CloseCurrentPopup(); }), asCALL_CDECL); // Clip-rects engine->RegisterGlobalFunction("void PushClipRect(const vec2&in, const vec2&in, bool)", asFUNCTION(+[](const Vector2f& a, const Vector2f& b, bool c) { ImGui::PushClipRect(a, b, c); }), asCALL_CDECL); engine->RegisterGlobalFunction("void PopClipRect()", asFUNCTION(+[]() { ImGui::PopClipRect(); }), asCALL_CDECL); // Focus engine->RegisterGlobalFunction("void SetItemDefaultFocus()", asFUNCTION(+[]() { ImGui::SetItemDefaultFocus(); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetKeyboardFocusHere(int = 0)", asFUNCTION(+[](std::int32_t a) { ImGui::SetKeyboardFocusHere(a); }), asCALL_CDECL); // Utilities engine->RegisterGlobalFunction("bool IsItemHovered(int = 0)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsItemHovered((ImGuiHoveredFlags)a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsItemActive()", asFUNCTION(+[]() { return ImGui::IsItemActive(); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsItemClicked(int = 0)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsItemClicked(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsItemVisible()", asFUNCTION(+[]() { return ImGui::IsItemVisible(); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsAnyItemHovered()", asFUNCTION(+[]() { return ImGui::IsAnyItemHovered(); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsAnyItemActive()", asFUNCTION(+[]() { return ImGui::IsAnyItemActive(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetItemRectMin()", asFUNCTION(+[]() { return ImGui::GetItemRectMin(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetItemRectMax()", asFUNCTION(+[]() { return ImGui::GetItemRectMax(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetItemRectSize()", asFUNCTION(+[]() { return ImGui::GetItemRectSize(); }), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("void SetItemAllowOverlap()", asFUNCTION(+[]() { // ImGui::SetItemAllowOverlap(); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsWindowFocused(int = 0)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsWindowFocused((ImGuiFocusedFlags)a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsWindowHovered(int = 0)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsWindowHovered((ImGuiHoveredFlags)a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsRectVisible(const vec2&in)", asFUNCTION(+[](const Vector2f& a) { return ImGui::IsRectVisible(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsRectVisible(const vec2&in, const vec2&in)", asFUNCTION(+[](const Vector2f& a, const Vector2f& b) { return ImGui::IsRectVisible(a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("float GetTime()", asFUNCTION(+[]() { return ImGui::GetTime(); }), asCALL_CDECL); engine->RegisterGlobalFunction("int GetFrameCount()", asFUNCTION(+[]() { return ImGui::GetFrameCount(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 CalcTextSize(const string&in, const string&in = string(), bool = false, float = -1.0f)", asFUNCTION(+[](const String& a, const String& b, bool c, float d) { return ImGui::CalcTextSize(!a.empty() ? a.data() : nullptr, !b.empty() ? b.data() : nullptr, c, d); }), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("void CalcListClipping(int, float, int&inout, int&inout)", asFUNCTION(+[](int a, float b, int& c, int& d) { // ImGui::CalcListClipping(a, b, &c, &d); }), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("bool BeginChildFrame(uint, const vec2&, int = 0)", asFUNCTION(+[](unsigned a, const Vector2f& b, int c) { // return ImGui::BeginChildFrame(a, b, (ImGuiWindowFlags)c); }), asCALL_CDECL); // TODO engine->RegisterGlobalFunction("void EndChildFrame()", asFUNCTION(+[]() { // ImGui::EndChildFrame(); }), asCALL_CDECL); //engine->RegisterGlobalFunction("int GetKeyIndex(int)", asFUNCTION(+[](int a) { // return ImGui::GetKeyIndex((ImGuiKey)a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsKeyDown(int)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsKeyDown((ImGuiKey)a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsKeyPressed(int, bool = true)", asFUNCTION(+[](std::int32_t a, bool b) { return ImGui::IsKeyPressed((ImGuiKey)a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsKeyReleased(int)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsKeyReleased((ImGuiKey)a); }), asCALL_CDECL); engine->RegisterGlobalFunction("int GetKeyPressedAmount(int, float, float)", asFUNCTION(+[](std::int32_t a, float b, float c) { return ImGui::GetKeyPressedAmount((ImGuiKey)a, b, c); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMouseDown(int)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsMouseDown(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMouseClicked(int, bool = false)", asFUNCTION(+[](std::int32_t a, bool b) { return ImGui::IsMouseClicked(a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMouseDoubleClicked(int)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsMouseDoubleClicked(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMouseReleased(int)", asFUNCTION(+[](std::int32_t a) { return ImGui::IsMouseReleased(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMouseDragging(int = 0, float = -1.0f)", asFUNCTION(+[](std::int32_t a, float b) { return ImGui::IsMouseDragging(a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMouseHoveringRect(const vec2&in, const vec2&in, bool = true)", asFUNCTION(+[](const Vector2f& a, const Vector2f& b, bool c) { return ImGui::IsMouseHoveringRect(a, b, c); }), asCALL_CDECL); engine->RegisterGlobalFunction("bool IsMousePosValid(const vec2&in)", asFUNCTION(+[](const Vector2f& a) { ImVec2 v = a; return ImGui::IsMousePosValid(&v); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetMousePos()", asFUNCTION(+[]() { return ImGui::GetMousePos(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetMousePosOnOpeningCurrentPopup()", asFUNCTION(+[]() { return ImGui::GetMousePosOnOpeningCurrentPopup(); }), asCALL_CDECL); engine->RegisterGlobalFunction("vec2 GetMouseDragDelta(int = 0, float = -1.0f)", asFUNCTION(+[](std::int32_t a, float b) { return ImGui::GetMouseDragDelta(a, b); }), asCALL_CDECL); engine->RegisterGlobalFunction("void ResetMouseDragDelta(int = 0)", asFUNCTION(+[](std::int32_t a) { ImGui::ResetMouseDragDelta(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("int GetMouseCursor()", asFUNCTION(+[]() { return ImGui::GetMouseCursor(); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetMouseCursor(int)", asFUNCTION(+[](ImGuiMouseCursor a) { ImGui::SetMouseCursor(a); }), asCALL_CDECL); engine->RegisterGlobalFunction("string GetClipboardText()", asFUNCTION(+[]() -> String { return ImGui::GetClipboardText(); }), asCALL_CDECL); engine->RegisterGlobalFunction("void SetClipboardText(const string&in)", asFUNCTION(+[](const String& a) { ImGui::SetClipboardText(!a.empty() ? a.data() : nullptr); }), asCALL_CDECL); engine->SetDefaultNamespace(""); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterImGuiBindings.h000066400000000000000000000004471512772601700300500ustar00rootroot00000000000000#pragma once #if (defined(WITH_ANGELSCRIPT) && defined(WITH_IMGUI)) || defined(DOXYGEN_GENERATING_OUTPUT) #include namespace Jazz2::Scripting { /** @brief Registers ImGui bindings to **AngelScript** engine */ void RegisterImGuiBindings(asIScriptEngine* engine); } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterMath.cpp000066400000000000000000000640641512772601700266110ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "RegisterMath.h" #include "../../Main.h" #include "../../nCine/Primitives/Colorf.h" #include "../../nCine/Primitives/Vector2.h" #include "../../nCine/Primitives/Vector3.h" #include "../../nCine/Primitives/Vector4.h" #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Scripting { void RegisterMembers_Vector2i(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "int get_length() const", asMETHODPR(Vector2i, Length, () const, std::int32_t), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opMul(int) const", asMETHODPR(Vector2i, operator*, (std::int32_t) const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opMul(const vec2i&in) const", asMETHODPR(Vector2i, operator*, (const Vector2i&) const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opMulAssign(int)", asMETHODPR(Vector2i, operator*=, (std::int32_t), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opMulAssign(const vec2i&in)", asMETHODPR(Vector2i, operator*=, (const Vector2i&), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opAdd(const vec2i&in) const", asMETHODPR(Vector2i, operator+, (const Vector2i&) const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opAddAssign(const vec2i&in)", asMETHODPR(Vector2i, operator+=, (const Vector2i&), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opNeg() const", asMETHODPR(Vector2i, operator-, () const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opSub(const vec2i&in) const", asMETHODPR(Vector2i, operator-, (const Vector2i&) const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opSubAssign(const vec2i&in)", asMETHODPR(Vector2i, operator-=, (const Vector2i&), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opDiv(int) const", asMETHODPR(Vector2i, operator/, (std::int32_t) const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i opDiv(const vec2i&in) const", asMETHODPR(Vector2i, operator/, (const Vector2i&) const, Vector2i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opDivAssign(int)", asMETHODPR(Vector2i, operator/=, (std::int32_t), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opDivAssign(const vec2i&in)", asMETHODPR(Vector2i, operator/=, (const Vector2i&), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2i& opAssign(const vec2i&in)", asMETHODPR(Vector2i, operator=, (const Vector2i&), Vector2i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const vec2i&in) const", asMETHODPR(Vector2i, operator==, (const Vector2i&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "int x", offsetof(Vector2i, X)); engine->RegisterObjectProperty(className, "int y", offsetof(Vector2i, Y)); } void RegisterMembers_Vector3i(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "int get_length() const", asMETHODPR(Vector3i, Length, () const, std::int32_t), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opMul(int) const", asMETHODPR(Vector3i, operator*, (std::int32_t) const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opMul(const vec3i&in) const", asMETHODPR(Vector3i, operator*, (const Vector3i&) const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opMulAssign(int)", asMETHODPR(Vector3i, operator*=, (std::int32_t), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opMulAssign(const vec3i&in)", asMETHODPR(Vector3i, operator*=, (const Vector3i&), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opAdd(const vec3i&in) const", asMETHODPR(Vector3i, operator+, (const Vector3i&) const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opAddAssign(const vec3i&in)", asMETHODPR(Vector3i, operator+=, (const Vector3i&), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opNeg() const", asMETHODPR(Vector3i, operator-, () const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opSub(const vec3i&in) const", asMETHODPR(Vector3i, operator-, (const Vector3i&) const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opSubAssign(const vec3i&in)", asMETHODPR(Vector3i, operator-=, (const Vector3i&), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opDiv(int) const", asMETHODPR(Vector3i, operator/, (std::int32_t) const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i opDiv(const vec3i&in) const", asMETHODPR(Vector3i, operator/, (const Vector3i&) const, Vector3i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opDivAssign(int)", asMETHODPR(Vector3i, operator/=, (std::int32_t), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opDivAssign(const vec3i&in)", asMETHODPR(Vector3i, operator/=, (const Vector3i&), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3i& opAssign(const vec3i&in)", asMETHODPR(Vector3i, operator=, (const Vector3i&), Vector3i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const vec3i&in) const", asMETHODPR(Vector3i, operator==, (const Vector3i&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "int x", offsetof(Vector3i, X)); engine->RegisterObjectProperty(className, "int y", offsetof(Vector3i, Y)); engine->RegisterObjectProperty(className, "int z", offsetof(Vector3i, Z)); } void RegisterMembers_Vector4i(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "int get_length() const", asMETHODPR(Vector4i, Length, () const, std::int32_t), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opMul(int) const", asMETHODPR(Vector4i, operator*, (std::int32_t) const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opMul(const vec4i&in) const", asMETHODPR(Vector4i, operator*, (const Vector4i&) const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opMulAssign(int)", asMETHODPR(Vector4i, operator*=, (std::int32_t), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opMulAssign(const vec4i&in)", asMETHODPR(Vector4i, operator*=, (const Vector4i&), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opAdd(const vec4i&in) const", asMETHODPR(Vector4i, operator+, (const Vector4i&) const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opAddAssign(const vec4i&in)", asMETHODPR(Vector4i, operator+=, (const Vector4i&), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opNeg() const", asMETHODPR(Vector4i, operator-, () const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opSub(const vec4i&in) const", asMETHODPR(Vector4i, operator-, (const Vector4i&) const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opSubAssign(const vec4i&in)", asMETHODPR(Vector4i, operator-=, (const Vector4i&), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opDiv(int) const", asMETHODPR(Vector4i, operator/, (std::int32_t) const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i opDiv(const vec4i&in) const", asMETHODPR(Vector4i, operator/, (const Vector4i&) const, Vector4i), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opDivAssign(int)", asMETHODPR(Vector4i, operator/=, (std::int32_t), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opDivAssign(const vec4i&in)", asMETHODPR(Vector4i, operator/=, (const Vector4i&), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4i& opAssign(const vec4i&in)", asMETHODPR(Vector4i, operator=, (const Vector4i&), Vector4i&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const vec4i&in) const", asMETHODPR(Vector4i, operator==, (const Vector4i&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "int x", offsetof(Vector4i, X)); engine->RegisterObjectProperty(className, "int y", offsetof(Vector4i, Y)); engine->RegisterObjectProperty(className, "int z", offsetof(Vector4i, Z)); engine->RegisterObjectProperty(className, "int w", offsetof(Vector4i, W)); } void RegisterMembers_Vector2f(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "float get_length() const", asMETHODPR(Vector2f, Length, () const, float), asCALL_THISCALL); engine->RegisterObjectMethod(className, "float get_lengthSquared() const", asMETHODPR(Vector2f, SqrLength, () const, float), asCALL_THISCALL); engine->RegisterObjectMethod(className, "void normalize()", asMETHODPR(Vector2f, Normalize, (), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 normalized() const", asMETHODPR(Vector2f, Normalized, () const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opMul(float) const", asMETHODPR(Vector2f, operator*, (float) const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opMul(const vec2&in) const", asMETHODPR(Vector2f, operator*, (const Vector2f&) const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opMulAssign(float)", asMETHODPR(Vector2f, operator*=, (float), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opMulAssign(const vec2&in)", asMETHODPR(Vector2f, operator*=, (const Vector2f&), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opAdd(const vec2&in) const", asMETHODPR(Vector2f, operator+, (const Vector2f&) const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opAddAssign(const vec2&in)", asMETHODPR(Vector2f, operator+=, (const Vector2f&), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opNeg() const", asMETHODPR(Vector2f, operator-, () const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opSub(const vec2&in) const", asMETHODPR(Vector2f, operator-, (const Vector2f&) const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opSubAssign(const vec2&in)", asMETHODPR(Vector2f, operator-=, (const Vector2f&), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opDiv(float) const", asMETHODPR(Vector2f, operator/, (float) const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2 opDiv(const vec2&in) const", asMETHODPR(Vector2f, operator/, (const Vector2f&) const, Vector2f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opDivAssign(float)", asMETHODPR(Vector2f, operator/=, (float), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opDivAssign(const vec2&in)", asMETHODPR(Vector2f, operator/=, (const Vector2f&), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec2& opAssign(const vec2&in)", asMETHODPR(Vector2f, operator=, (const Vector2f&), Vector2f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const vec2&in) const", asMETHODPR(Vector2f, operator==, (const Vector2f&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "float x", offsetof(Vector2f, X)); engine->RegisterObjectProperty(className, "float y", offsetof(Vector2f, Y)); } void RegisterMembers_Vector3f(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "float get_length() const", asMETHODPR(Vector3f, Length, () const, float), asCALL_THISCALL); engine->RegisterObjectMethod(className, "float get_lengthSquared() const", asMETHODPR(Vector3f, SqrLength, () const, float), asCALL_THISCALL); engine->RegisterObjectMethod(className, "void normalize()", asMETHODPR(Vector3f, Normalize, (), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 normalized() const", asMETHODPR(Vector3f, Normalized, () const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opMul(float) const", asMETHODPR(Vector3f, operator*, (float) const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opMul(const vec3&in) const", asMETHODPR(Vector3f, operator*, (const Vector3f&) const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opMulAssign(float)", asMETHODPR(Vector3f, operator*=, (float), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opMulAssign(const vec3&in)", asMETHODPR(Vector3f, operator*=, (const Vector3f&), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opAdd(const vec3&in) const", asMETHODPR(Vector3f, operator+, (const Vector3f&) const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opAddAssign(const vec3&in)", asMETHODPR(Vector3f, operator+=, (const Vector3f&), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opNeg() const", asMETHODPR(Vector3f, operator-, () const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opSub(const vec3&in) const", asMETHODPR(Vector3f, operator-, (const Vector3f&) const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opSubAssign(const vec3&in)", asMETHODPR(Vector3f, operator-=, (const Vector3f&), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opDiv(float) const", asMETHODPR(Vector3f, operator/, (float) const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3 opDiv(const vec3&in) const", asMETHODPR(Vector3f, operator/, (const Vector3f&) const, Vector3f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opDivAssign(float)", asMETHODPR(Vector3f, operator/=, (float), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opDivAssign(const vec3&in)", asMETHODPR(Vector3f, operator/=, (const Vector3f&), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec3& opAssign(const vec3&in)", asMETHODPR(Vector3f, operator=, (const Vector3f&), Vector3f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const vec3&in) const", asMETHODPR(Vector3f, operator==, (const Vector3f&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "float x", offsetof(Vector3f, X)); engine->RegisterObjectProperty(className, "float y", offsetof(Vector3f, Y)); engine->RegisterObjectProperty(className, "float z", offsetof(Vector3f, Z)); } void RegisterMembers_Vector4f(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "float get_length() const", asMETHODPR(Vector4f, Length, () const, float), asCALL_THISCALL); engine->RegisterObjectMethod(className, "float get_lengthSquared() const", asMETHODPR(Vector4f, SqrLength, () const, float), asCALL_THISCALL); engine->RegisterObjectMethod(className, "void normalize()", asMETHODPR(Vector4f, Normalize, (), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 normalized() const", asMETHODPR(Vector4f, Normalized, () const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opMul(float) const", asMETHODPR(Vector4f, operator*, (float) const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opMul(const vec4&in) const", asMETHODPR(Vector4f, operator*, (const Vector4f&) const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opMulAssign(float)", asMETHODPR(Vector4f, operator*=, (float), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opMulAssign(const vec4&in)", asMETHODPR(Vector4f, operator*=, (const Vector4f&), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opAdd(const vec4&in) const", asMETHODPR(Vector4f, operator+, (const Vector4f&) const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opAddAssign(const vec4&in)", asMETHODPR(Vector4f, operator+=, (const Vector4f&), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opNeg() const", asMETHODPR(Vector4f, operator-, () const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opSub(const vec4&in) const", asMETHODPR(Vector4f, operator-, (const Vector4f&) const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opSubAssign(const vec4&in)", asMETHODPR(Vector4f, operator-=, (const Vector4f&), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opDiv(float) const", asMETHODPR(Vector4f, operator/, (float) const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4 opDiv(const vec4&in) const", asMETHODPR(Vector4f, operator/, (const Vector4f&) const, Vector4f), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opDivAssign(float)", asMETHODPR(Vector4f, operator/=, (float), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opDivAssign(const vec4&in)", asMETHODPR(Vector4f, operator/=, (const Vector4f&), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "vec4& opAssign(const vec4&in)", asMETHODPR(Vector4f, operator=, (const Vector4f&), Vector4f&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const vec4&in) const", asMETHODPR(Vector4f, operator==, (const Vector4f&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "float x", offsetof(Vector4f, X)); engine->RegisterObjectProperty(className, "float y", offsetof(Vector4f, Y)); engine->RegisterObjectProperty(className, "float z", offsetof(Vector4f, Z)); engine->RegisterObjectProperty(className, "float w", offsetof(Vector4f, W)); } void RegisterMembers_Color(asIScriptEngine* engine, const char* className) { engine->RegisterObjectMethod(className, "Color opMul(float) const", asMETHODPR(Colorf, operator*, (float) const, Colorf), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color opMul(const Color&in) const", asMETHODPR(Colorf, operator*, (const Colorf&) const, Colorf), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color& opMulAssign(float)", asMETHODPR(Colorf, operator*=, (float), Colorf&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color& opMulAssign(const Color&in)", asMETHODPR(Colorf, operator*=, (const Colorf&), Colorf&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color opAdd(const Color&in) const", asMETHODPR(Colorf, operator+, (const Colorf&) const, Colorf), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color& opAddAssign(const Color&in)", asMETHODPR(Colorf, operator+=, (const Colorf&), Colorf&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color opSub(const Color&in) const", asMETHODPR(Colorf, operator-, (const Colorf&) const, Colorf), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color& opSubAssign(const Color&in)", asMETHODPR(Colorf, operator-=, (const Colorf&), Colorf&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "Color& opAssign(const Color&in)", asMETHODPR(Colorf, operator=, (const Colorf&), Colorf&), asCALL_THISCALL); engine->RegisterObjectMethod(className, "bool opEquals(const Color&in) const", asMETHODPR(Colorf, operator==, (const Colorf&) const, bool), asCALL_THISCALL); engine->RegisterObjectProperty(className, "float r", offsetof(Colorf, R)); engine->RegisterObjectProperty(className, "float g", offsetof(Colorf, G)); engine->RegisterObjectProperty(className, "float b", offsetof(Colorf, B)); engine->RegisterObjectProperty(className, "float a", offsetof(Colorf, A)); } void RegisterMath(asIScriptEngine* engine) { // Vectors (int) engine->RegisterObjectType("vec2i", sizeof(Vector2i), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLINTS); engine->RegisterObjectBehaviour("vec2i", asBEHAVE_CONSTRUCT, "void f(const vec2i&in)", asFUNCTION(+[](Vector2i* _ptr, const Vector2i& v) { new(_ptr) Vector2i(v.X, v.Y); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec2i", asBEHAVE_CONSTRUCT, "void f(int, int)", asFUNCTION(+[](Vector2i* _ptr, std::int32_t x, std::int32_t y) { new(_ptr) Vector2i(x, y); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Vector2i(engine, "vec2i"); engine->RegisterObjectType("vec3i", sizeof(Vector3i), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLINTS); engine->RegisterObjectBehaviour("vec3i", asBEHAVE_CONSTRUCT, "void f(const vec3i&in)", asFUNCTION(+[](Vector3i* _ptr, const Vector3i& v) { new(_ptr) Vector3i(v.X, v.Y, v.Z); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec3i", asBEHAVE_CONSTRUCT, "void f(int, int, int)", asFUNCTION(+[](Vector3i* _ptr, std::int32_t x, std::int32_t y, std::int32_t z) { new(_ptr) Vector3i(x, y, z); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Vector3i(engine, "vec3i"); engine->RegisterObjectType("vec4i", sizeof(Vector4i), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLINTS); engine->RegisterObjectBehaviour("vec4i", asBEHAVE_CONSTRUCT, "void f(const vec4i&in)", asFUNCTION(+[](Vector4i* _ptr, const Vector4i& v) { new(_ptr) Vector4i(v.X, v.Y, v.Z, v.W); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec4i", asBEHAVE_CONSTRUCT, "void f(int, int, int, int)", asFUNCTION(+[](Vector4i* _ptr, std::int32_t x, std::int32_t y, std::int32_t z, std::int32_t w) { new(_ptr) Vector4i(x, y, z, w); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Vector4i(engine, "vec4i"); // Vectors (float) engine->RegisterObjectType("vec2", sizeof(Vector2f), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLFLOATS); engine->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(const vec2&in)", asFUNCTION(+[](Vector2f* _ptr, const Vector2f& v) { new(_ptr) Vector2f(v); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(const vec2i&in)", asFUNCTION(+[](Vector2f* _ptr, const Vector2i& v) { new(_ptr) Vector2f(v.X, v.Y); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(float, float)", asFUNCTION(+[](Vector2f* _ptr, float x, float y) { new(_ptr) Vector2f(x, y); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec2", asBEHAVE_CONSTRUCT, "void f(int, int)", asFUNCTION(+[](Vector2f* _ptr, std::int32_t x, std::int32_t y) { new(_ptr) Vector2f(x, y); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Vector2f(engine, "vec2"); engine->RegisterObjectType("vec3", sizeof(Vector3f), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLFLOATS); engine->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(const vec3&in)", asFUNCTION(+[](Vector3f* _ptr, const Vector3f& v) { new(_ptr) Vector3f(v); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(const vec3i&in)", asFUNCTION(+[](Vector3f* _ptr, const Vector3i& v) { new(_ptr) Vector3f(v.X, v.Y, v.Z); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(float, float, float)", asFUNCTION(+[](Vector3f* _ptr, float x, float y, float z) { new(_ptr) Vector3f(x, y, z); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec3", asBEHAVE_CONSTRUCT, "void f(int, int, int)", asFUNCTION(+[](Vector3f* _ptr, std::int32_t x, std::int32_t y, std::int32_t z) { new(_ptr) Vector3f(x, y, z); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Vector2f(engine, "vec3"); engine->RegisterObjectType("vec4", sizeof(Vector4f), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLFLOATS); engine->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(const vec4&in)", asFUNCTION(+[](Vector4f* _ptr, const Vector4f& v) { new(_ptr) Vector4f(v); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(const vec4i&in)", asFUNCTION(+[](Vector4f* _ptr, const Vector4i& v) { new(_ptr) Vector4f(v.X, v.Y, v.Z, v.W); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(float, float, float, float)", asFUNCTION(+[](Vector4f* _ptr, float x, float y, float z, float w) { new(_ptr) Vector4f(x, y, z, w); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("vec4", asBEHAVE_CONSTRUCT, "void f(int, int, int, int)", asFUNCTION(+[](Vector4f* _ptr, std::int32_t x, std::int32_t y, std::int32_t z, std::int32_t w) { new(_ptr) Vector4f(x, y, z, w); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Vector2f(engine, "vec4"); // Color engine->RegisterObjectType("Color", sizeof(Colorf), asOBJ_VALUE | asGetTypeTraits() | asOBJ_POD | asOBJ_APP_CLASS_ALLFLOATS); engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(const Color&in)", asFUNCTION(+[](Vector4f* _ptr, const Colorf& v) { new(_ptr) Colorf(v); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(const vec3&in, float)", asFUNCTION(+[](Vector4f* _ptr, const Vector3f& v, float a) { new(_ptr) Colorf(v.X, v.Y, v.Z, a); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(const vec4&in)", asFUNCTION(+[](Vector4f* _ptr, const Vector4f& v) { new(_ptr) Colorf(v.X, v.Y, v.Z, v.W); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(float, float, float)", asFUNCTION(+[](Vector4f* _ptr, float r, float g, float b) { new(_ptr) Colorf(r, g, b); }), asCALL_CDECL_OBJFIRST); engine->RegisterObjectBehaviour("Color", asBEHAVE_CONSTRUCT, "void f(float, float, float, float)", asFUNCTION(+[](Vector4f* _ptr, float r, float g, float b, float a) { new(_ptr) Colorf(r, g, b, a); }), asCALL_CDECL_OBJFIRST); RegisterMembers_Color(engine, "Color"); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterMath.h000066400000000000000000000004611512772601700262450ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include namespace Jazz2::Scripting { /** @brief Registers `vec2`/`vec3`/`vec4`/`vec2i`/`vec3i`/`vec4i`/`Color` types to **AngelScript** engine */ void RegisterMath(asIScriptEngine* engine); } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterRef.cpp000066400000000000000000000173171512772601700264330ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "RegisterRef.h" #include "../../Main.h" #include #include namespace Jazz2::Scripting { static void Construct(CScriptHandle* self) { new(self) CScriptHandle(); } static void Construct(CScriptHandle* self, const CScriptHandle& o) { new(self) CScriptHandle(o); } // This one is not static because it needs to be friend with the CScriptHandle class void Construct(CScriptHandle* self, void* ref, std::int32_t typeId) { new(self) CScriptHandle(ref, typeId); } static void Destruct(CScriptHandle* self) { self->~CScriptHandle(); } CScriptHandle::CScriptHandle() { m_ref = nullptr; m_type = nullptr; } CScriptHandle::CScriptHandle(const CScriptHandle& other) { m_ref = other.m_ref; m_type = other.m_type; AddRefHandle(); } CScriptHandle::CScriptHandle(void* ref, asITypeInfo* type) { m_ref = ref; m_type = type; AddRefHandle(); } // This constructor shouldn't be called from the application // directly as it requires an active script context CScriptHandle::CScriptHandle(void* ref, std::int32_t typeId) { m_ref = nullptr; m_type = nullptr; Assign(ref, typeId); } CScriptHandle::~CScriptHandle() { ReleaseHandle(); } void CScriptHandle::ReleaseHandle() { if (m_ref != nullptr && m_type != nullptr) { asIScriptEngine* engine = m_type->GetEngine(); engine->ReleaseScriptObject(m_ref, m_type); engine->Release(); m_ref = nullptr; m_type = nullptr; } } void CScriptHandle::AddRefHandle() { if (m_ref != nullptr && m_type != nullptr) { asIScriptEngine* engine = m_type->GetEngine(); engine->AddRefScriptObject(m_ref, m_type); // Hold on to the engine so it isn't destroyed while // a reference to a script object is still held engine->AddRef(); } } CScriptHandle& CScriptHandle::operator=(const CScriptHandle& other) { Set(other.m_ref, other.m_type); return *this; } void CScriptHandle::Set(void* ref, asITypeInfo* type) { if (m_ref == ref) return; ReleaseHandle(); m_ref = ref; m_type = type; AddRefHandle(); } void* CScriptHandle::GetRef() { return m_ref; } asITypeInfo* CScriptHandle::GetType() const { return m_type; } int CScriptHandle::GetTypeId() const { if (m_type == nullptr) return 0; return m_type->GetTypeId() | asTYPEID_OBJHANDLE; } // This method shouldn't be called from the application // directly as it requires an active script context CScriptHandle& CScriptHandle::Assign(void* ref, std::int32_t typeId) { // When receiving a null handle we just clear our memory if (typeId == 0) { Set(0, 0); return *this; } // Dereference received handles to get the object if (typeId & asTYPEID_OBJHANDLE) { // Store the actual reference ref = *(void**)ref; typeId &= ~asTYPEID_OBJHANDLE; } // Get the object type asIScriptContext* ctx = asGetActiveContext(); asIScriptEngine* engine = ctx->GetEngine(); asITypeInfo* type = engine->GetTypeInfoById(typeId); // If the argument is another CScriptHandle, we should copy the content instead if (type != nullptr && strcmp(type->GetName(), "ref") == 0) { CScriptHandle* r = static_cast(ref); ref = r->m_ref; type = r->m_type; } Set(ref, type); return *this; } bool CScriptHandle::operator==(const CScriptHandle& o) const { if (m_ref == o.m_ref && m_type == o.m_type) return true; // TODO: If type is not the same, we should attempt to do a dynamic cast, // which may change the pointer for application registered classes return false; } bool CScriptHandle::operator!=(const CScriptHandle& o) const { return !(*this == o); } bool CScriptHandle::Equals(void* ref, std::int32_t typeId) const { // Null handles are received as reference to a null handle if (typeId == 0) ref = nullptr; // Dereference handles to get the object if (typeId & asTYPEID_OBJHANDLE) { // Compare the actual reference ref = *(void**)ref; typeId &= ~asTYPEID_OBJHANDLE; } // TODO: If typeId is not the same, we should attempt to do a dynamic cast, // which may change the pointer for application registered classes if (ref == m_ref) return true; return false; } // AngelScript: used as '@obj = cast(ref);' void CScriptHandle::Cast(void** outRef, std::int32_t typeId) { // If we hold a null handle, then just return null if (m_type == nullptr) { *outRef = nullptr; return; } // It is expected that the outRef is always a handle DEATH_ASSERT(typeId & asTYPEID_OBJHANDLE); // Compare the type id of the actual object typeId &= ~asTYPEID_OBJHANDLE; asIScriptEngine* engine = m_type->GetEngine(); asITypeInfo* type = engine->GetTypeInfoById(typeId); *outRef = nullptr; // RefCastObject will increment the refCount of the returned object if successful engine->RefCastObject(m_ref, m_type, type, outRef); } void CScriptHandle::EnumReferences(asIScriptEngine* inEngine) { // If we're holding a reference, we'll notify the garbage collector of it if (m_ref != nullptr) inEngine->GCEnumCallback(m_ref); // The object type itself is also garbage collected if (m_type != nullptr) inEngine->GCEnumCallback(m_type); } void CScriptHandle::ReleaseReferences(asIScriptEngine* /*inEngine*/) { // Simply clear the content to release the references Set(0, 0); } void RegisterRef(asIScriptEngine* engine) { std::int32_t r; #if AS_CAN_USE_CPP11 // With C++11 it is possible to use asGetTypeTraits to automatically determine the flags that represent the C++ class r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asGetTypeTraits()); DEATH_ASSERT(r >= 0); #else r = engine->RegisterObjectType("ref", sizeof(CScriptHandle), asOBJ_VALUE | asOBJ_ASHANDLE | asOBJ_GC | asOBJ_APP_CLASS_CDAK); DEATH_ASSERT(r >= 0); #endif r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f()", asFUNCTIONPR(Construct, (CScriptHandle*), void), asCALL_CDECL_OBJFIRST); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ref &in)", asFUNCTIONPR(Construct, (CScriptHandle*, const CScriptHandle&), void), asCALL_CDECL_OBJFIRST); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("ref", asBEHAVE_CONSTRUCT, "void f(const ?&in)", asFUNCTIONPR(Construct, (CScriptHandle*, void*, std::int32_t), void), asCALL_CDECL_OBJFIRST); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("ref", asBEHAVE_DESTRUCT, "void f()", asFUNCTIONPR(Destruct, (CScriptHandle*), void), asCALL_CDECL_OBJFIRST); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("ref", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CScriptHandle, EnumReferences), asCALL_THISCALL); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("ref", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CScriptHandle, ReleaseReferences), asCALL_THISCALL); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectMethod("ref", "void opCast(?&out)", asMETHODPR(CScriptHandle, Cast, (void**, std::int32_t), void), asCALL_THISCALL); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ref &in)", asMETHOD(CScriptHandle, operator=), asCALL_THISCALL); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectMethod("ref", "ref &opHndlAssign(const ?&in)", asMETHOD(CScriptHandle, Assign), asCALL_THISCALL); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectMethod("ref", "bool opEquals(const ref &in) const", asMETHODPR(CScriptHandle, operator==, (const CScriptHandle&) const, bool), asCALL_THISCALL); DEATH_ASSERT(r >= 0); r = engine->RegisterObjectMethod("ref", "bool opEquals(const ?&in) const", asMETHODPR(CScriptHandle, Equals, (void*, std::int32_t) const, bool), asCALL_THISCALL); DEATH_ASSERT(r >= 0); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterRef.h000066400000000000000000000034221512772601700260700ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include namespace Jazz2::Scripting { /** @brief **AngelScript** reference handle */ class CScriptHandle { public: // Constructors CScriptHandle(); CScriptHandle(const CScriptHandle& other); CScriptHandle(void* ref, asITypeInfo* type); ~CScriptHandle(); // Copy the stored value from another any object CScriptHandle& operator=(const CScriptHandle& other); // Set the reference void Set(void* ref, asITypeInfo* type); // Compare equalness bool operator==(const CScriptHandle& o) const; bool operator!=(const CScriptHandle& o) const; bool Equals(void* ref, std::int32_t typeId) const; // Dynamic cast to desired handle type void Cast(void** outRef, std::int32_t typeId); // Returns the type of the reference held asITypeInfo* GetType() const; std::int32_t GetTypeId() const; // Get the reference void* GetRef(); // GC callback void EnumReferences(asIScriptEngine* engine); void ReleaseReferences(asIScriptEngine* engine); protected: // These functions need to have access to protected members in order to call them from the script engine friend void Construct(CScriptHandle* self, void* ref, std::int32_t typeId); friend void RegisterRef(asIScriptEngine* engine); void ReleaseHandle(); void AddRefHandle(); // These shouldn't be called directly by the application as they requires an active context CScriptHandle(void* ref, std::int32_t typeId); CScriptHandle &Assign(void* ref, std::int32_t typeId); #ifndef DOXYGEN_GENERATING_OUTPUT void* m_ref; asITypeInfo* m_type; #endif }; /** @relatesalso CScriptHandle @brief Registers `ref` type to **AngelScript** engine */ void RegisterRef(asIScriptEngine* engine); } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterString.cpp000066400000000000000000000706521512772601700271660ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "RegisterString.h" #include "../../Main.h" #include "../../nCine/Base/Algorithms.h" #include "../../nCine/Base/HashMap.h" #include #include #include #if !defined(DEATH_TARGET_ANDROID) && !defined(_WIN32_WCE) && !defined(__psp2__) # include // setlocale() #endif #include #include #define AS_USE_ACCESSORS 1 using namespace Death::Containers; using namespace Death::Containers::Literals; using namespace nCine; namespace Jazz2::Scripting { class StringFactory : public asIStringFactory { public: struct InternalData { std::unique_ptr Data; std::int32_t RefCount; InternalData(std::int32_t refCount) : RefCount(refCount) {} }; StringFactory() {} ~StringFactory() { // The script engine must release each string constant that it has requested DEATH_ASSERT(_stringCache.size() == 0); } const void* GetStringConstant(const char* data, std::uint32_t length) { // The string factory might be modified from multiple threads, so it is necessary to use a mutex asAcquireExclusiveLock(); // Recreate non-owned string const String stringView(data, length, [](char*, std::size_t) {}); auto it = _stringCache.find(stringView); if (it != _stringCache.end()) { it->second.RefCount++;; } else { auto data = std::make_unique(stringView); it = _stringCache.emplace(*data.get(), 1).first; it->second.Data = std::move(data); //LOGD("Allocated \"{}\" string", it->second.Data.get()); } asReleaseExclusiveLock(); return static_cast(it->second.Data.get()); } int ReleaseStringConstant(const void* str) { if (str == nullptr) return asERROR; std::int32_t ret = asSUCCESS; // The string factory might be modified from multiple threads, so it is necessary to use a mutex asAcquireExclusiveLock(); const String& stringView = *static_cast(str); auto it = _stringCache.find(stringView); if (it == _stringCache.end()) { ret = asERROR; } else { it->second.RefCount--; if (it->second.RefCount == 0) { //LOGD("Released \"{}\" string", it->second.Data.get()); _stringCache.erase(it); } } asReleaseExclusiveLock(); return ret; } int GetRawStringData(const void* str, char* data, std::uint32_t* length) const { if (str == nullptr) return asERROR; const String& stringView = *static_cast(str); if (length) { *length = (std::uint32_t)stringView.size(); } if (data != nullptr) { std::memcpy(data, stringView.data(), stringView.size()); } return asSUCCESS; } struct StringRefEqualTo { inline bool operator()(const Reference& a, const Reference& b) const noexcept { return a.get() == b.get(); } }; HashMap, InternalData, #if defined(DEATH_TARGET_32BIT) xxHash32Func, #else xxHash64Func, #endif StringRefEqualTo> _stringCache; }; static StringFactory* _stringFactory = nullptr; StringFactory* GetStringFactoryInstance() { if (_stringFactory == nullptr) { // The following instance will be destroyed by the global StringFactoryCleaner instance upon application shutdown _stringFactory = new StringFactory(); } return _stringFactory; } class StringFactoryCleaner { public: ~StringFactoryCleaner() { if (_stringFactory != nullptr) { // Only delete the string factory if the stringCache is empty. If it is not empty, it means that someone might still attempt // to release string constants, so if we delete the string factory, the application might crash. Not deleting the cache would // lead to a memory leak, but since this is only happens when the application is shutting down anyway, it is not important. if (_stringFactory->_stringCache.empty()) { delete _stringFactory; _stringFactory = nullptr; } } } }; static StringFactoryCleaner cleaner; static void ConstructString(String* thisPointer) { new(thisPointer) String(); } static void CopyConstructString(const String& other, String* thisPointer) { new(thisPointer) String(other); } static void DestructString(String* thisPointer) { thisPointer->~String(); } static String& AddAssignStringToString(const String& str, String& dest) { // We don't register the method directly because some compilers and standard libraries inline the definition, // resulting in the linker being unable to find the declaration. Example: CLang/LLVM with XCode 4.3 on OSX 10.7 dest += str; return dest; } // bool string::empty() static bool StringIsEmpty(const String& str) { // We don't register the method directly because some compilers // and standard libraries inline the definition, resulting in the // linker being unable to find the declaration // Example: CLang/LLVM with XCode 4.3 on OSX 10.7 return str.empty(); } static String& AssignUInt64ToString(std::uint64_t i, String& dest) { char tmpBuffer[32]; u64tos(i, tmpBuffer); dest = String(tmpBuffer); return dest; } static String& AddAssignUInt64ToString(std::uint64_t i, String& dest) { char tmpBuffer[32]; u64tos(i, tmpBuffer); dest += StringView(tmpBuffer); return dest; } static String AddStringUInt64(const String& str, std::uint64_t i) { char tmpBuffer[32]; u64tos(i, tmpBuffer); return str + StringView(tmpBuffer); } static String AddInt64String(std::int64_t i, const String& str) { char tmpBuffer[32]; i64tos(i, tmpBuffer); return StringView(tmpBuffer) + str; } static String& AssignInt64ToString(std::int64_t i, String& dest) { char tmpBuffer[32]; i64tos(i, tmpBuffer); dest = String(tmpBuffer); return dest; } static String& AddAssignInt64ToString(std::int64_t i, String& dest) { char tmpBuffer[32]; i64tos(i, tmpBuffer); dest += StringView(tmpBuffer); return dest; } static String AddStringInt64(const String& str, std::int64_t i) { char tmpBuffer[32]; i64tos(i, tmpBuffer); return str + StringView(tmpBuffer); } static String AddUInt64String(std::uint64_t i, const String& str) { char tmpBuffer[32]; u64tos(i, tmpBuffer); return StringView(tmpBuffer) + str; } static String& AssignDoubleToString(double f, String& dest) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); dest = String(tmpBuffer); return dest; } static String& AddAssignDoubleToString(double f, String& dest) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); dest += StringView(tmpBuffer); return dest; } static String& AssignFloatToString(float f, String& dest) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); dest = String(tmpBuffer); return dest; } static String& AddAssignFloatToString(float f, String& dest) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); dest += StringView(tmpBuffer); return dest; } static String& AssignBoolToString(bool b, String& dest) { dest = (b ? "true"_s : "false"_s); return dest; } static String& AddAssignBoolToString(bool b, String& dest) { dest += (b ? "true"_s : "false"_s); return dest; } static String AddStringDouble(const String& str, double f) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); return str + StringView(tmpBuffer); } static String AddDoubleString(double f, const String& str) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); return StringView(tmpBuffer) + str; } static String AddStringFloat(const String& str, float f) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); return str + StringView(tmpBuffer); } static String AddFloatString(float f, const String& str) { char tmpBuffer[32]; ftos(f, tmpBuffer, arraySize(tmpBuffer)); return StringView(tmpBuffer) + str; } static String AddStringBool(const String& str, bool b) { return str + (b ? "true"_s : "false"_s); } static String AddBoolString(bool b, const String& str) { return (b ? "true"_s : "false"_s) + str; } static char* StringCharAt(unsigned int i, String& str) { if (i >= str.size()) { asIScriptContext* ctx = asGetActiveContext(); ctx->SetException("Out of range"); return nullptr; } return &str[i]; } // int string::opCmp(const string &in) const static std::int32_t StringCmp(const String& a, const String& b) { std::int32_t cmp = 0; if (a < b) cmp = -1; else if (a > b) cmp = 1; return cmp; } // This function returns the index of the first position where the substring // exists in the input string. If the substring doesn't exist in the input // string -1 is returned. // // int string::findFirst(const string &in sub, uint start = 0) const static std::int32_t StringFindFirst(const String& sub, std::uint32_t start, const String& str) { auto found = str.exceptPrefix(start).find(sub); return (found != nullptr ? (std::int32_t)(found.begin() - str.begin()) : -1); } // This function returns the index of the first position where the one of the bytes in substring // exists in the input string. If the characters in the substring doesn't exist in the input // string -1 is returned. // // int string::findFirstOf(const string &in sub, uint start = 0) const static std::int32_t StringFindFirstOf(const String& sub, std::uint32_t start, const String& str) { auto found = str.exceptPrefix(start).findAny(sub); return (found != nullptr ? (std::int32_t)(found.begin() - str.begin()) : -1); } // This function returns the index of the last position where the one of the bytes in substring // exists in the input string. If the characters in the substring doesn't exist in the input // string -1 is returned. // // int string::findLastOf(const string &in sub, uint start = 0) const static std::int32_t StringFindLastOf(const String& sub, std::uint32_t start, const String& str) { auto found = str.exceptSuffix(start).findLastAny(sub); return (found != nullptr ? (std::int32_t)(found.begin() - str.begin()) : -1); } /* // This function returns the index of the first position where a byte other than those in substring // exists in the input string. If none is found -1 is returned. // // int string::findFirstNotOf(const string &in sub, uint start = 0) const static int StringFindFirstNotOf(const String& sub, std::uint32_t start, const String& str) { // We don't register the method directly because the argument types change between 32-bit and 64-bit platforms return (int)str.find_first_not_of(sub, (size_t)(start < 0 ? string::npos : start)); } // This function returns the index of the last position where a byte other than those in substring // exists in the input string. If none is found -1 is returned. // // int string::findLastNotOf(const string &in sub, uint start = -1) const static int StringFindLastNotOf(const String& sub, std::uint32_t start, const String& str) { // We don't register the method directly because the argument types change between 32-bit and 64-bit platforms return (int)str.find_last_not_of(sub, (size_t)(start < 0 ? string::npos : start)); }*/ // This function returns the index of the last position where the substring // exists in the input string. If the substring doesn't exist in the input // string -1 is returned. // // int string::findLast(const string &in sub, int start = 0) const static std::int32_t StringFindLast(const String& sub, std::int32_t start, const String& str) { auto found = str.exceptSuffix(start).findLast(sub); return (found != nullptr ? (std::int32_t)(found.begin() - str.begin()) : -1); } /* // void string::insert(uint pos, const string &in other) static void StringInsert(unsigned int pos, const String& other, String& str) { // We don't register the method directly because the argument types change between 32-bit and 64-bit platforms str.insert(pos, other); } // void string::erase(uint pos, int count = -1) static void StringErase(unsigned int pos, int count, String& str) { // We don't register the method directly because the argument types change between 32-bit and 64-bit platforms str.erase(pos, (size_t)(count < 0 ? string::npos : count)); }*/ // uint string::size() const static std::uint32_t StringSize(const String& str) { // We don't register the method directly because the return type changes between 32-bit and 64-bit platforms return (std::uint32_t)str.size(); } // void string::resize(uint l) /*static void StringResize(std::uint32_t l, String& str) { // We don't register the method directly because the argument types change between 32bit and 64bit platforms str.resize(l); }*/ // string formatInt(int64 val, const string &in options, uint width) static String formatInt(std::int64_t value, const String& options, std::uint32_t width) { bool leftJustify = options.find("l") != nullptr; bool padWithZero = options.find("0") != nullptr; bool alwaysSign = options.find("+") != nullptr; bool spaceOnSign = options.find(" ") != nullptr; bool hexSmall = options.find("h") != nullptr; bool hexLarge = options.find("H") != nullptr; String fmt = "%"; if (leftJustify) fmt += "-"; if (alwaysSign) fmt += "+"; if (spaceOnSign) fmt += " "; if (padWithZero) fmt += "0"; #if defined(DEATH_TARGET_WINDOWS) fmt += "*I64"; #elif defined(_LP64) fmt += "*l"; #else fmt += "*ll"; #endif if (hexSmall) fmt += "x"; else if (hexLarge) fmt += "X"; else fmt += "d"; char buffer[64]; #if defined(DEATH_TARGET_MSVC) // MSVC 8.0 / 2005 or newer sprintf_s(buffer, sizeof(buffer), fmt.data(), width, value); #else sprintf(buffer, fmt.data(), width, value); #endif return buffer; } // string formatUInt(uint64 val, const string &in options, uint width) static String formatUInt(std::uint64_t value, const String& options, std::uint32_t width) { bool leftJustify = options.find('l') != nullptr; bool padWithZero = options.find('0') != nullptr; bool alwaysSign = options.find('+') != nullptr; bool spaceOnSign = options.find(' ') != nullptr; bool hexSmall = options.find('h') != nullptr; bool hexLarge = options.find('H') != nullptr; String fmt = "%"; if (leftJustify) fmt += "-"; if (alwaysSign) fmt += "+"; if (spaceOnSign) fmt += " "; if (padWithZero) fmt += "0"; #if defined(DEATH_TARGET_WINDOWS) fmt += "*I64"; #elif defined(_LP64) fmt += "*l"; #else fmt += "*ll"; #endif if (hexSmall) fmt += "x"; else if (hexLarge) fmt += "X"; else fmt += "u"; char buffer[64]; #if defined(DEATH_TARGET_MSVC) // MSVC 8.0 / 2005 or newer sprintf_s(buffer, sizeof(buffer), fmt.data(), width, value); #else sprintf(buffer, fmt.data(), width, value); #endif return buffer; } // string formatFloat(double val, const string &in options, uint width, uint precision) static String formatFloat(double value, const String& options, std::uint32_t width, std::uint32_t precision) { bool leftJustify = options.find("l") != nullptr; bool padWithZero = options.find("0") != nullptr; bool alwaysSign = options.find("+") != nullptr; bool spaceOnSign = options.find(" ") != nullptr; bool expSmall = options.find("e") != nullptr; bool expLarge = options.find("E") != nullptr; String fmt = "%"; if (leftJustify) fmt += "-"; if (alwaysSign) fmt += "+"; if (spaceOnSign) fmt += " "; if (padWithZero) fmt += "0"; fmt += "*.*"; if (expSmall) fmt += "e"; else if (expLarge) fmt += "E"; else fmt += "f"; char buffer[64]; #if defined(DEATH_TARGET_MSVC) // MSVC 8.0 / 2005 or newer sprintf_s(buffer, sizeof(buffer), fmt.data(), width, precision, value); #else sprintf(buffer, fmt.data(), width, precision, value); #endif return buffer; } // int64 parseInt(const string &in val, uint base = 10, uint &out byteCount = 0) static std::int64_t parseInt(const String& val, std::uint32_t base, std::uint32_t* byteCount) { if (base != 10 && base != 16) { if (byteCount) *byteCount = 0; return 0; } const char* end = val.data(); bool sign = false; if (*end == '-') { sign = true; end++; } else if (*end == '+') { end++; } std::int64_t res = 0; if (base == 10) { while (*end >= '0' && *end <= '9') { res *= 10; res += *end++ - '0'; } } else if (base == 16) { while ((*end >= '0' && *end <= '9') || (*end >= 'a' && *end <= 'f') || (*end >= 'A' && *end <= 'F')) { res *= 16; if (*end >= '0' && *end <= '9') { res += *end++ - '0'; } else if (*end >= 'a' && *end <= 'f') { res += *end++ - 'a' + 10; } else if (*end >= 'A' && *end <= 'F') { res += *end++ - 'A' + 10; } } } if (byteCount) { *byteCount = std::uint32_t(size_t(end - val.data())); } if (sign) { res = -res; } return res; } // uint64 parseUInt(const string &in val, uint base = 10, uint &out byteCount = 0) static std::uint64_t parseUInt(const String& val, std::uint32_t base, std::uint32_t* byteCount) { if (base != 10 && base != 16) { if (byteCount) *byteCount = 0; return 0; } const char* end = val.data(); std::uint64_t res = 0; if (base == 10) { while (*end >= '0' && *end <= '9') { res *= 10; res += *end++ - '0'; } } else if (base == 16) { while ((*end >= '0' && *end <= '9') || (*end >= 'a' && *end <= 'f') || (*end >= 'A' && *end <= 'F')) { res *= 16; if (*end >= '0' && *end <= '9') { res += *end++ - '0'; } else if (*end >= 'a' && *end <= 'f') { res += *end++ - 'a' + 10; } else if (*end >= 'A' && *end <= 'F') { res += *end++ - 'A' + 10; } } } if (byteCount) { *byteCount = std::uint32_t(size_t(end - val.data())); } return res; } // double parseFloat(const string &in val, uint &out byteCount = 0) double parseFloat(const String& val, std::uint32_t* byteCount) { char* end; // WinCE doesn't have setlocale. Some quick testing on my current platform still manages to parse the numbers // such as "3.14" even if the decimal for the locale is ",". #if !defined(DEATH_TARGET_ANDROID) && !defined(_WIN32_WCE) && !defined(__psp2__) // Set the locale to C so that we are guaranteed to parse the float value correctly char* tmp = setlocale(LC_NUMERIC, nullptr); String orig = (tmp ? tmp : "C"_s); setlocale(LC_NUMERIC, "C"); #endif double res = strtod(val.data(), &end); #if !defined(DEATH_TARGET_ANDROID) && !defined(_WIN32_WCE) && !defined(__psp2__) // Restore the locale setlocale(LC_NUMERIC, orig.data()); #endif if (byteCount) { *byteCount = std::uint32_t(size_t(end - val.data())); } return res; } // This function returns a string containing the substring of the input string // determined by the starting index and count of characters. // // string string::substr(uint start = 0, int count = -1) const static String StringSubString(std::uint32_t start, std::int32_t count, const String& str) { String ret; if (start < str.size() && count != 0) { ret = str.slice(start, (size_t)(count < 0 ? str.size() : start + count)); } return ret; } // String equality comparison. // Returns true iff lhs is equal to rhs. // // For some reason gcc 4.7 has difficulties resolving the asFUNCTIONPR(operator==, (const string &, const string &) macro, so this wrapper was introduced as work around. static bool StringEquals(const String& lhs, const String& rhs) { return lhs == rhs; } static String StringConcat(const String& lhs, const String& rhs) { return lhs + rhs; } void RegisterString(asIScriptEngine* engine) { std::int32_t r; // Register the string type r = engine->RegisterObjectType("string", sizeof(String), asOBJ_VALUE | asGetTypeTraits()); RETURN_ASSERT(r >= 0); r = engine->RegisterStringFactory("string", GetStringFactoryInstance()); // Register the object operator overloads r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(ConstructString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("string", asBEHAVE_CONSTRUCT, "void f(const string &in)", asFUNCTION(CopyConstructString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour("string", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(DestructString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAssign(const string &in)", asMETHODPR(String, operator=, (const String&), String&), asCALL_THISCALL); RETURN_ASSERT(r >= 0); // Need to use a wrapper on Mac OS X 10.7/XCode 4.3 and CLang/LLVM, otherwise the linker fails r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asFUNCTION(AddAssignStringToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); //r = engine->RegisterObjectMethod("string", "string &opAddAssign(const string &in)", asMETHODPR(string, operator+=, (const string&), string&), asCALL_THISCALL); RETURN_ASSERT( r >= 0 ); // Need to use a wrapper for operator== otherwise gcc 4.7 fails to compile r = engine->RegisterObjectMethod("string", "bool opEquals(const string &in) const", asFUNCTIONPR(StringEquals, (const String&, const String&), bool), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "int opCmp(const string &in) const", asFUNCTION(StringCmp), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd(const string &in) const", asFUNCTION(StringConcat), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); // The string length can be accessed through methods or through virtual property #if AS_USE_ACCESSORS != 1 r = engine->RegisterObjectMethod("string", "uint length() const", asFUNCTION(StringSize), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); #endif //r = engine->RegisterObjectMethod("string", "void resize(uint)", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); #if AS_USE_STLNAMES != 1 && AS_USE_ACCESSORS == 1 // Don't register these if STL names is used, as they conflict with the method size() r = engine->RegisterObjectMethod("string", "uint get_length() const property", asFUNCTION(StringSize), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); //r = engine->RegisterObjectMethod("string", "void set_length(uint) property", asFUNCTION(StringResize), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); #endif // Need to use a wrapper on Mac OS X 10.7/XCode 4.3 and CLang/LLVM, otherwise the linker fails //r = engine->RegisterObjectMethod("string", "bool empty() const", asMETHOD(string, empty), asCALL_THISCALL); RETURN_ASSERT( r >= 0 ); r = engine->RegisterObjectMethod("string", "bool empty() const", asFUNCTION(StringIsEmpty), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); // Register the index operator, both as a mutator and as an inspector // Note that we don't register the operator[] directly, as it doesn't do bounds checking r = engine->RegisterObjectMethod("string", "uint8 &opIndex(uint)", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "const uint8 &opIndex(uint) const", asFUNCTION(StringCharAt), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); // Automatic conversion from values r = engine->RegisterObjectMethod("string", "string &opAssign(double)", asFUNCTION(AssignDoubleToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAddAssign(double)", asFUNCTION(AddAssignDoubleToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd(double) const", asFUNCTION(AddStringDouble), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd_r(double) const", asFUNCTION(AddDoubleString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAssign(float)", asFUNCTION(AssignFloatToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAddAssign(float)", asFUNCTION(AddAssignFloatToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd(float) const", asFUNCTION(AddStringFloat), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd_r(float) const", asFUNCTION(AddFloatString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAssign(int64)", asFUNCTION(AssignInt64ToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAddAssign(int64)", asFUNCTION(AddAssignInt64ToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd(int64) const", asFUNCTION(AddStringInt64), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd_r(int64) const", asFUNCTION(AddInt64String), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAssign(uint64)", asFUNCTION(AssignUInt64ToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAddAssign(uint64)", asFUNCTION(AddAssignUInt64ToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd(uint64) const", asFUNCTION(AddStringUInt64), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd_r(uint64) const", asFUNCTION(AddUInt64String), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAssign(bool)", asFUNCTION(AssignBoolToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string &opAddAssign(bool)", asFUNCTION(AddAssignBoolToString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd(bool) const", asFUNCTION(AddStringBool), asCALL_CDECL_OBJFIRST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "string opAdd_r(bool) const", asFUNCTION(AddBoolString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); // Utilities r = engine->RegisterObjectMethod("string", "string substr(uint start = 0, int count = -1) const", asFUNCTION(StringSubString), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "int findFirst(const string &in, uint start = 0) const", asFUNCTION(StringFindFirst), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "int findFirstOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstOf), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); //r = engine->RegisterObjectMethod("string", "int findFirstNotOf(const string &in, uint start = 0) const", asFUNCTION(StringFindFirstNotOf), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "int findLast(const string &in, int start = -1) const", asFUNCTION(StringFindLast), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod("string", "int findLastOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastOf), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); //r = engine->RegisterObjectMethod("string", "int findLastNotOf(const string &in, int start = -1) const", asFUNCTION(StringFindLastNotOf), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); //r = engine->RegisterObjectMethod("string", "void insert(uint pos, const string &in other)", asFUNCTION(StringInsert), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); //r = engine->RegisterObjectMethod("string", "void erase(uint pos, int count = -1)", asFUNCTION(StringErase), asCALL_CDECL_OBJLAST); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("string formatInt(int64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatInt), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("string formatUInt(uint64 val, const string &in options = \"\", uint width = 0)", asFUNCTION(formatUInt), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("string formatFloat(double val, const string &in options = \"\", uint width = 0, uint precision = 0)", asFUNCTION(formatFloat), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("int64 parseInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseInt), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("uint64 parseUInt(const string &in, uint base = 10, uint &out byteCount = 0)", asFUNCTION(parseUInt), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterGlobalFunction("double parseFloat(const string &in, uint &out byteCount = 0)", asFUNCTION(parseFloat), asCALL_CDECL); RETURN_ASSERT(r >= 0); } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/RegisterString.h000066400000000000000000000004501512772601700266200ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include namespace Jazz2::Scripting { /** @brief Registers @ref Death::Containers::String as `string` type to **AngelScript** engine */ void RegisterString(asIScriptEngine* engine); } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/ScriptActorWrapper.cpp000066400000000000000000000606401512772601700300050ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "ScriptActorWrapper.h" #include "ScriptPlayerWrapper.h" #include "LevelScriptLoader.h" #include "RegisterArray.h" #include "RegisterRef.h" #include "../ILevelHandler.h" #include "../Events/EventSpawner.h" #include "../Actors/Explosion.h" #include "../Actors/Player.h" #include "../Actors/Weapons/ShotBase.h" #include "../Actors/Weapons/TNT.h" #include "../Actors/Enemies/TurtleShell.h" #include "../../nCine/Base/FrameTimer.h" #include "../../nCine/Base/Random.h" using namespace Jazz2::Actors; using namespace Jazz2::Tiles; using namespace nCine; #define AsClassName "ActorBase" #define AsClassNameInternal "ActorBaseInternal" namespace Jazz2::Scripting { ScriptActorWrapper::ScriptActorWrapper(LevelScriptLoader* levelScripts, asIScriptObject* obj) : _levelScripts(levelScripts), _obj(obj), _refCount(1), _scoreValue(0) { _isDead = obj->GetWeakRefFlag(); _isDead->AddRef(); _onTileDeactivated = _obj->GetObjectType()->GetMethodByDecl("bool OnTileDeactivated()"); _onHealthChanged = _obj->GetObjectType()->GetMethodByDecl("void OnHealthChanged()"); _onUpdate = _obj->GetObjectType()->GetMethodByDecl("void OnUpdate(float)"); _onUpdateHitbox = _obj->GetObjectType()->GetMethodByDecl("void OnUpdateHitbox()"); _onHandleCollision = _obj->GetObjectType()->GetMethodByDecl("bool OnHandleCollision(ref other)"); _onHitFloor = _obj->GetObjectType()->GetMethodByDecl("void OnHitFloor(float)"); _onHitCeiling = _obj->GetObjectType()->GetMethodByDecl("void OnHitCeiling(float)"); _onHitWall = _obj->GetObjectType()->GetMethodByDecl("void OnHitWall(float)"); _onAnimationStarted = _obj->GetObjectType()->GetMethodByDecl("void OnAnimationStarted()"); _onAnimationFinished = _obj->GetObjectType()->GetMethodByDecl("void OnAnimationFinished()"); } ScriptActorWrapper::~ScriptActorWrapper() { // This had to be added to release the object properly _obj->Release(); _isDead->Release(); } void ScriptActorWrapper::RegisterFactory(asIScriptEngine* engine, asIScriptModule* module) { static const char AsLibrary[] = R"( shared abstract class )" AsClassName R"( { // Allow scripts to create instances )" AsClassName R"(() { // Create the C++ side of the proxy @_obj = )" AsClassNameInternal R"((GetActorType()); } // The copy constructor performs a deep copy )" AsClassName R"((const )" AsClassName R"( &o) { // Create a new C++ instance and copy content @_obj = )" AsClassNameInternal R"((GetActorType()); _obj = o._obj; } // Do a deep a copy of the C++ object )" AsClassName R"( &opAssign(const )" AsClassName R"( &o) { // copy content of C++ instance _obj = o._obj; return this; } // The script class can be implicitly cast to the C++ type through the opImplCast method )" AsClassNameInternal R"( @opImplCast() { return _obj; } // Hold a reference to the C++ side of the proxy protected )" AsClassNameInternal R"( @_obj; protected int GetActorType() { return 0; } // Overridable events //bool OnActivated(array &in eventParams) { return false; } //bool OnTileDeactivate(int tx1, int ty1, int tx2, int ty2) { return true; } //void OnHealthChanged() { } //bool OnPerish() { return true; } //void OnUpdate(float timeMult) { } //void OnUpdateHitbox() { } //void OnHitFloor(float timeMult) { } //void OnHitCeiling(float timeMult) { } //void OnHitWall(float timeMult) { } //void OnAnimationStarted() { } //void OnAnimationFinished() { } // Properties float X { get const { return _obj.X; } } float Y { get const { return _obj.Y; } } float SpeedX { get const { return _obj.SpeedX; } set { _obj.SpeedX = value; } } float SpeedY { get const { return _obj.SpeedY; } set { _obj.SpeedY = value; } } float ExternalForceX { get const { return _obj.ExternalForceX; } set { _obj.ExternalForceX = value; } } float ExternalForceY { get const { return _obj.ExternalForceY; } set { _obj.ExternalForceY = value; } } float Elasticity { get const { return _obj.Elasticity; } set { _obj.Elasticity = value; } } float Friction { get const { return _obj.Friction; } set { _obj.Friction = value; } } int Health { get const { return _obj.Health; } set { _obj.Health = value; } } float Alpha { get const { return _obj.Alpha; } set { _obj.Alpha = value; } } uint16 Layer { get const { return _obj.Layer; } set { _obj.Layer = value; } } // Methods void DecreaseHealth(int amount) { _obj.DecreaseHealth(amount); } bool MoveTo(float x, float y, bool force = false) { return _obj.MoveTo(x, y, force); } bool MoveBy(float x, float y, bool force = false) { return _obj.MoveBy(x, y, force); } void TryStandardMovement(float timeMult) { _obj.TryStandardMovement(timeMult); } void RequestMetadata(const string &in path) { _obj.RequestMetadata(path); } void PlaySfx(const string &in identifier, float gain = 1.0, float pitch = 1.0) { _obj.PlaySfx(identifier, gain, pitch); } void SetAnimation(int state) { _obj.SetAnimation(state); } } shared abstract class CollectibleBase : )" AsClassName R"( { protected int GetActorType() final { return 1; } // Overridable events //bool OnCollect(Player@ player) { return true; } // Properties int ScoreValue { get const { return _obj.ScoreValue; } set { _obj.ScoreValue = value; } } } )"; int r; r = engine->RegisterObjectType(AsClassNameInternal, 0, asOBJ_REF); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour(AsClassNameInternal, asBEHAVE_FACTORY, AsClassNameInternal " @f(int)", asFUNCTION(ScriptActorWrapper::Factory), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour(AsClassNameInternal, asBEHAVE_ADDREF, "void f()", asMETHOD(ScriptActorWrapper, AddRef), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour(AsClassNameInternal, asBEHAVE_RELEASE, "void f()", asMETHOD(ScriptActorWrapper, Release), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, AsClassNameInternal " &opAssign(const " AsClassNameInternal " &in)", asMETHOD(ScriptActorWrapper, operator=), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float X", asOFFSET(ScriptActorWrapper, _pos.X)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float Y", asOFFSET(ScriptActorWrapper, _pos.Y)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float SpeedX", asOFFSET(ScriptActorWrapper, _speed.X)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float SpeedY", asOFFSET(ScriptActorWrapper, _speed.Y)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float ExternalForceX", asOFFSET(ScriptActorWrapper, _externalForce.X)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float ExternalForceY", asOFFSET(ScriptActorWrapper, _externalForce.Y)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float Elasticity", asOFFSET(ScriptActorWrapper, _elasticity)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "float Friction", asOFFSET(ScriptActorWrapper, _friction)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "int Health", asOFFSET(ScriptActorWrapper, _health)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectProperty(AsClassNameInternal, "int ScoreValue", asOFFSET(ScriptActorWrapper, _scoreValue)); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "float get_Alpha() const property", asMETHOD(ScriptActorWrapper, asGetAlpha), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void set_Alpha(float) property", asMETHOD(ScriptActorWrapper, asSetAlpha), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "uint16 get_Layer() const property", asMETHOD(ScriptActorWrapper, asGetLayer), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void set_Layer(uint16) property", asMETHOD(ScriptActorWrapper, asSetLayer), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void DecreaseHealth(int)", asMETHOD(ScriptActorWrapper, asDecreaseHealth), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "bool MoveTo(float, float, bool)", asMETHOD(ScriptActorWrapper, asMoveTo), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "bool MoveBy(float, float, bool)", asMETHOD(ScriptActorWrapper, asMoveBy), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void TryStandardMovement(float)", asMETHOD(ScriptActorWrapper, asTryStandardMovement), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void RequestMetadata(const string &in)", asMETHOD(ScriptActorWrapper, asRequestMetadata), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void PlaySfx(const string &in, float, float)", asMETHOD(ScriptActorWrapper, asPlaySfx), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassNameInternal, "void SetAnimation(int)", asMETHOD(ScriptActorWrapper, asSetAnimationState), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = module->AddScriptSection("__" AsClassName, AsLibrary, arraySize(AsLibrary) - 1, 0); RETURN_ASSERT(r >= 0); } ScriptActorWrapper* ScriptActorWrapper::Factory(std::int32_t actorType) { auto ctx = asGetActiveContext(); auto owner = ScriptLoader::FromActiveContext(); // Get the function that is calling the factory, so we can be certain it is the our internal script class asIScriptFunction* func = ctx->GetFunction(0); if (func->GetObjectType() == nullptr || StringView(func->GetObjectType()->GetName()) != StringView(AsClassName)) { ctx->SetException("Cannot manually instantiate " AsClassNameInternal); return nullptr; } asIScriptObject* obj = static_cast(ctx->GetThisPointer()); switch (actorType) { default: case 0: { void* mem = asAllocMem(sizeof(ScriptActorWrapper)); return new(mem) ScriptActorWrapper(owner, obj); } case 1: { void* mem = asAllocMem(sizeof(ScriptCollectibleWrapper)); return new(mem) ScriptCollectibleWrapper(owner, obj); } } } void ScriptActorWrapper::AddRef() { _refCount++; // Increment also the reference counter to the script side, so it isn't accidentally destroyed before the C++ side if (!_isDead->Get()) { _obj->AddRef(); } } void ScriptActorWrapper::Release() { // Release the script instance too if (!_isDead->Get()) { _obj->Release(); } if (--_refCount == 0) { this->~ScriptActorWrapper(); asFreeMem(this); } } Task ScriptActorWrapper::OnActivatedAsync(const Actors::ActorActivationDetails& details) { if (_isDead->Get()) { async_return false; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptFunction* func = _obj->GetObjectType()->GetMethodByDecl("bool OnActivated(array &in)"); if (func == nullptr) { async_return false; } SetState(ActorState::CollideWithOtherActors, _onHandleCollision != nullptr); CScriptArray* eventParams = CScriptArray::Create(engine->GetTypeInfoByDecl("array"), Events::EventSpawner::SpawnParamsSize); std::memcpy(eventParams->At(0), details.Params, Events::EventSpawner::SpawnParamsSize); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(func); ctx->SetObject(_obj); ctx->SetArgObject(0, eventParams); std::int32_t r = ctx->Execute(); bool result; if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); result = true; } else { result = (ctx->GetReturnByte() != 0); } eventParams->Release(); engine->ReturnContext(ctx); async_return result; } bool ScriptActorWrapper::OnTileDeactivated() { if (_onTileDeactivated == nullptr || _isDead->Get()) { return true; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onTileDeactivated); ctx->SetObject(_obj); std::int32_t r = ctx->Execute(); bool result; if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); result = true; } else { result = (ctx->GetReturnByte() != 0); } engine->ReturnContext(ctx); return result; } void ScriptActorWrapper::OnHealthChanged(ActorBase* collider) { if (_onHealthChanged == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onHealthChanged); ctx->SetObject(_obj); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } bool ScriptActorWrapper::OnPerish(ActorBase* collider) { if (_isDead->Get()) { return ActorBase::OnPerish(collider); } asIScriptFunction* func = _obj->GetObjectType()->GetMethodByDecl("bool OnPerish()"); if (func == nullptr) { return ActorBase::OnPerish(collider); } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(func); ctx->SetObject(_obj); std::int32_t r = ctx->Execute(); bool result; if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); result = true; } else { result = (ctx->GetReturnByte() != 0); } engine->ReturnContext(ctx); return (result && ActorBase::OnPerish(collider)); } void ScriptActorWrapper::OnUpdate(float timeMult) { if (_onUpdate == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onUpdate); ctx->SetObject(_obj); ctx->SetArgFloat(0, timeMult); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } void ScriptActorWrapper::OnUpdateHitbox() { if (_onUpdateHitbox == nullptr || _isDead->Get()) { // Call base implementation if not overriden ActorBase::OnUpdateHitbox(); return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onUpdateHitbox); ctx->SetObject(_obj); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } bool ScriptActorWrapper::OnHandleCollision(std::shared_ptr other) { if (_onHandleCollision != nullptr) { if (auto* otherWrapper = runtime_cast(other.get())) { asIScriptEngine* engine = _obj->GetEngine(); asITypeInfo* typeInfo = _levelScripts->GetMainModule()->GetTypeInfoByName(AsClassName); if (typeInfo != nullptr) { asIScriptContext* ctx = engine->RequestContext(); CScriptHandle handle(otherWrapper->_obj, typeInfo); ctx->Prepare(_onHandleCollision); ctx->SetObject(_obj); std::int32_t p = ctx->SetArgObject(0, &handle); std::int32_t r = ctx->Execute(); bool result; if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); result = true; } else { result = (ctx->GetReturnByte() != 0); } engine->ReturnContext(ctx); if (result) { return true; } } } else if (auto* player = runtime_cast(other.get())) { asIScriptEngine* engine = _obj->GetEngine(); asITypeInfo* typeInfo = engine->GetTypeInfoByName("Player"); if (typeInfo != nullptr) { asIScriptContext* ctx = engine->RequestContext(); void* mem = asAllocMem(sizeof(ScriptPlayerWrapper)); ScriptPlayerWrapper* playerWrapper = new(mem) ScriptPlayerWrapper(_levelScripts, player); CScriptHandle handle(playerWrapper, typeInfo); ctx->Prepare(_onHandleCollision); ctx->SetObject(_obj); std::int32_t p = ctx->SetArgObject(0, &handle); std::int32_t r = ctx->Execute(); bool result; if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); result = true; } else { result = (ctx->GetReturnByte() != 0); } engine->ReturnContext(ctx); playerWrapper->Release(); if (result) { return true; } } } } return ActorBase::OnHandleCollision(other); } bool ScriptActorWrapper::OnDraw(RenderQueue& renderQueue) { // TODO return ActorBase::OnDraw(renderQueue); } void ScriptActorWrapper::OnHitFloor(float timeMult) { if (_onHitFloor == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onHitFloor); ctx->SetObject(_obj); ctx->SetArgFloat(0, timeMult); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } void ScriptActorWrapper::OnHitCeiling(float timeMult) { if (_onHitCeiling == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onHitCeiling); ctx->SetObject(_obj); ctx->SetArgFloat(0, timeMult); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } void ScriptActorWrapper::OnHitWall(float timeMult) { if (_onHitWall == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onHitWall); ctx->SetObject(_obj); ctx->SetArgFloat(0, timeMult); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } void ScriptActorWrapper::OnAnimationStarted() { if (_onAnimationStarted == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onAnimationStarted); ctx->SetObject(_obj); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } void ScriptActorWrapper::OnAnimationFinished() { // Always call base implementation ActorBase::OnAnimationFinished(); if (_onAnimationFinished == nullptr || _isDead->Get()) { return; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); ctx->Prepare(_onAnimationFinished); ctx->SetObject(_obj); std::int32_t r = ctx->Execute(); if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); } engine->ReturnContext(ctx); } float ScriptActorWrapper::asGetAlpha() const { return _renderer.alpha(); } void ScriptActorWrapper::asSetAlpha(float value) { _renderer.setAlphaF(value); } uint16_t ScriptActorWrapper::asGetLayer() const { return _renderer.layer(); } void ScriptActorWrapper::asSetLayer(std::uint16_t value) { _renderer.setLayer(value); } void ScriptActorWrapper::asDecreaseHealth(std::int32_t amount) { DecreaseHealth(amount); } bool ScriptActorWrapper::asMoveTo(float x, float y, bool force) { return MoveInstantly(Vector2f(x, y), (force ? MoveType::Absolute | MoveType::Force : MoveType::Absolute)); } bool ScriptActorWrapper::asMoveBy(float x, float y, bool force) { return MoveInstantly(Vector2f(x, y), (force ? MoveType::Relative | MoveType::Force : MoveType::Relative)); } void ScriptActorWrapper::asTryStandardMovement(float timeMult) { TileCollisionParams params = { TileDestructType::None, _speed.Y >= 0.0f }; TryStandardMovement(timeMult, params); } void ScriptActorWrapper::asRequestMetadata(const String& path) { RequestMetadata(path); } void ScriptActorWrapper::asPlaySfx(const String& identifier, float gain, float pitch) { PlaySfx(identifier, gain, pitch); } void ScriptActorWrapper::asSetAnimationState(std::int32_t state) { SetAnimation((AnimState)state); } ScriptCollectibleWrapper::ScriptCollectibleWrapper(LevelScriptLoader* levelScripts, asIScriptObject* obj) : ScriptActorWrapper(levelScripts, obj), _untouched(true), _phase(0.0f), _timeLeft(0.0f), _startingY(0.0f) { _onCollect = _obj->GetObjectType()->GetMethodByDecl("bool OnCollect(Player@)"); } Task ScriptCollectibleWrapper::OnActivatedAsync(const ActorActivationDetails& details) { _elasticity = 0.6f; Vector2f pos = _pos; _phase = ((pos.X / 32) + (pos.Y / 32)) * 2.0f; if ((GetState() & (ActorState::IsCreatedFromEventMap | ActorState::IsFromGenerator)) != ActorState::None) { _untouched = true; SetState(ActorState::ApplyGravitation, false); _startingY = pos.Y; } else { _untouched = false; SetState(ActorState::ApplyGravitation, true); _timeLeft = 90.0f * FrameTimer::FramesPerSecond; } bool success = async_await ScriptActorWrapper::OnActivatedAsync(details); SetState(ActorState::CollideWithOtherActors | ActorState::SkipPerPixelCollisions, true); async_return success; } bool ScriptCollectibleWrapper::OnHandleCollision(std::shared_ptr other) { if (auto* player = runtime_cast(other.get())) { if (OnCollect(player)) { return true; } } else { bool shouldDrop = _untouched && (runtime_cast(other.get()) || runtime_cast(other.get()) || runtime_cast(other.get())); if (shouldDrop) { Vector2f speed = other->GetSpeed(); _externalForce.X += speed.X / 2.0f * (0.9f + Random().NextFloat(0.0f, 0.2f)); _externalForce.Y += -speed.Y / 4.0f * (0.9f + Random().NextFloat(0.0f, 0.2f)); _untouched = false; SetState(ActorState::ApplyGravitation, true); } } return ScriptActorWrapper::OnHandleCollision(std::move(other)); } bool ScriptCollectibleWrapper::OnCollect(Player* player) { if (_onCollect == nullptr || _isDead->Get()) { return false; } asIScriptEngine* engine = _obj->GetEngine(); asIScriptContext* ctx = engine->RequestContext(); void* mem = asAllocMem(sizeof(ScriptPlayerWrapper)); ScriptPlayerWrapper* playerWrapper = new(mem) ScriptPlayerWrapper(_levelScripts, player); ctx->Prepare(_onCollect); ctx->SetObject(_obj); ctx->SetArgObject(0, playerWrapper); std::int32_t r = ctx->Execute(); bool result; if (r == asEXECUTION_EXCEPTION) { LOGE("An exception \"{}\" occurred in \"{}\". Please correct the code and try again.", ctx->GetExceptionString(), ctx->GetExceptionFunction()->GetDeclaration()); result = true; } else { result = (ctx->GetReturnByte() != 0); } engine->ReturnContext(ctx); playerWrapper->Release(); if (result) { player->AddScore(_scoreValue); Explosion::Create(_levelHandler, Vector3i((std::int32_t)_pos.X, (std::int32_t)_pos.Y, _renderer.layer()), Explosion::Type::Generator); DecreaseHealth(INT32_MAX); return true; } else { return false; } } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/ScriptActorWrapper.h000066400000000000000000000062271512772601700274530ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../Actors/ActorBase.h" #include "../Actors/Collectibles/CollectibleBase.h" class asIScriptEngine; class asIScriptModule; class asIScriptObject; class asIScriptFunction; class asILockableSharedBool; namespace Jazz2::Actors { class Player; } namespace Jazz2::Scripting { class LevelScriptLoader; class ScriptActorWrapper : public Actors::ActorBase { public: ScriptActorWrapper(LevelScriptLoader* levelScripts, asIScriptObject* obj); ~ScriptActorWrapper(); static void RegisterFactory(asIScriptEngine* engine, asIScriptModule* module); static ScriptActorWrapper* Factory(std::int32_t actorType); void AddRef(); void Release(); // Assignment operator ScriptActorWrapper& operator=(const ScriptActorWrapper& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } bool OnHandleCollision(std::shared_ptr other) override; protected: #ifndef DOXYGEN_GENERATING_OUTPUT LevelScriptLoader* _levelScripts; asIScriptObject* _obj; asILockableSharedBool* _isDead; std::uint32_t _scoreValue; #endif Task OnActivatedAsync(const Actors::ActorActivationDetails& details) override; bool OnTileDeactivated() override; void OnHealthChanged(ActorBase* collider) override; bool OnPerish(ActorBase* collider) override; void OnUpdate(float timeMult) override; void OnUpdateHitbox() override; bool OnDraw(RenderQueue& renderQueue) override; void OnHitFloor(float timeMult) override; void OnHitCeiling(float timeMult) override; void OnHitWall(float timeMult) override; //void OnTriggeredEvent(EventType eventType, uint8_t* eventParams) override; void OnAnimationStarted() override; void OnAnimationFinished() override; float asGetAlpha() const; void asSetAlpha(float value); uint16_t asGetLayer() const; void asSetLayer(std::uint16_t value); void asDecreaseHealth(std::int32_t amount); bool asMoveTo(float x, float y, bool force); bool asMoveBy(float x, float y, bool force); void asTryStandardMovement(float timeMult); void asRequestMetadata(const String& path); void asPlaySfx(const String& identifier, float gain, float pitch); void asSetAnimationState(std::int32_t state); private: std::int32_t _refCount; asIScriptFunction* _onTileDeactivated; asIScriptFunction* _onHealthChanged; asIScriptFunction* _onUpdate; asIScriptFunction* _onUpdateHitbox; asIScriptFunction* _onHandleCollision; asIScriptFunction* _onHitFloor; asIScriptFunction* _onHitCeiling; asIScriptFunction* _onHitWall; asIScriptFunction* _onAnimationStarted; asIScriptFunction* _onAnimationFinished; }; class ScriptCollectibleWrapper : public ScriptActorWrapper { public: ScriptCollectibleWrapper(LevelScriptLoader* levelScripts, asIScriptObject* obj); bool OnHandleCollision(std::shared_ptr other) override; protected: Task OnActivatedAsync(const Actors::ActorActivationDetails& details) override; bool OnCollect(Actors::Player* player); private: bool _untouched; float _phase, _timeLeft; float _startingY; asIScriptFunction* _onCollect; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/ScriptLoader.cpp000066400000000000000000000762451512772601700266120ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "ScriptLoader.h" #include "../ContentResolver.h" #include #include #include #if defined(DEATH_TARGET_WINDOWS) && !defined(CMAKE_BUILD) # if defined(_M_X64) # if defined(_DEBUG) # pragma comment(lib, "../Libs/Windows/x64/angelscriptd.lib") # else # pragma comment(lib, "../Libs/Windows/x64/angelscript.lib") # endif # elif defined(_M_IX86) # if defined(_DEBUG) # pragma comment(lib, "../Libs/Windows/x86/angelscriptd.lib") # else # pragma comment(lib, "../Libs/Windows/x86/angelscript.lib") # endif # else # error Unsupported architecture # endif #endif using namespace Death::IO; namespace Jazz2::Scripting { ScriptLoader::ScriptLoader() : _module(nullptr), _scriptContextType(ScriptContextType::Unknown) { _engine = asCreateScriptEngine(); _engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true); _engine->SetEngineProperty(asEP_PROPERTY_ACCESSOR_MODE, 2); // Required to allow chained assignment to properties #if ANGELSCRIPT_VERSION >= 23600 _engine->SetEngineProperty(asEP_IGNORE_DUPLICATE_SHARED_INTF, true); #endif _engine->SetEngineProperty(asEP_COMPILER_WARNINGS, true); #if !defined(DEATH_DEBUG) _engine->SetEngineProperty(asEP_BUILD_WITHOUT_LINE_CUES, true); #endif _engine->SetUserData(this, EngineToOwner); _engine->SetContextCallbacks(RequestContextCallback, ReturnContextCallback, this); std::int32_t r = _engine->SetMessageCallback(asMETHOD(ScriptLoader, Message), this, asCALL_THISCALL); RETURN_ASSERT(r >= 0); _module = _engine->GetModule("Main", asGM_ALWAYS_CREATE); RETURN_ASSERT(_module != nullptr); } ScriptLoader::~ScriptLoader() { for (auto ctx : _contextPool) { ctx->Release(); } if (_engine != nullptr) { _engine->ShutDownAndRelease(); _engine = nullptr; } } ScriptContextType ScriptLoader::AddScriptFromFile(StringView path, const HashMap& definedSymbols) { String absolutePath = fs::GetAbsolutePath(path); if (absolutePath.empty()) { return ScriptContextType::Unknown; } // Include each file only once auto it = _includedFiles.find(absolutePath); if (it != _includedFiles.end()) { return ScriptContextType::AlreadyIncluded; } _includedFiles.emplace(absolutePath, true); auto s = fs::Open(absolutePath, FileAccess::Read); if (s->GetSize() <= 0) { return ScriptContextType::Unknown; } String scriptContent(NoInit, s->GetSize()); s->Read(scriptContent.data(), s->GetSize()); s->Dispose(); ScriptContextType contextType = ScriptContextType::Legacy; SmallVector metadata; SmallVector includes; String currentClass, currentNamespace, metadataName, metadataDeclaration; std::int32_t scriptSize = (std::int32_t)scriptContent.size(); // First perform the checks for #if directives to exclude code that shouldn't be compiled std::int32_t pos = 0; std::int32_t nested = 0; while (pos < scriptSize) { std::uint32_t len = 0; asETokenClass t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_UNKNOWN && scriptContent[pos] == '#' && (pos + 1 < scriptSize)) { std::int32_t start = pos++; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); StringView token = scriptContent.slice(pos, pos + len); pos += len; if (token == "if"_s) { t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_WHITESPACE) { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); } if (t == asTC_IDENTIFIER) { StringView word = scriptContent.slice(pos, pos + len); pos += len; auto it = definedSymbols.find(String::nullTerminatedView(word)); bool defined = (it != definedSymbols.end() && it->second); for (std::int32_t i = start; i < pos; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } if (defined) { nested++; } else { pos = ExcludeCode(scriptContent, pos); } } } else if (token == "endif"_s) { // Only remove the #endif if there was a matching #if if (nested > 0) { for (std::int32_t i = start; i < pos; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } nested--; } } } else pos += len; } pos = 0; while (pos < scriptSize) { std::uint32_t len = 0; asETokenClass t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_COMMENT || t == asTC_WHITESPACE) { pos += len; continue; } StringView token = scriptContent.slice(pos, pos + len); // Skip possible decorators before class and interface declarations if (token == "shared"_s || token == "abstract"_s || token == "mixin"_s || token == "external"_s) { pos += len; continue; } // Check if class or interface so the metadata for members can be gathered if (currentClass.empty() && (token == "class"_s || token == "interface"_s)) { do { pos += len; if (pos >= scriptSize) { t = asTC_UNKNOWN; break; } t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); } while (t == asTC_COMMENT || t == asTC_WHITESPACE); if (t == asTC_IDENTIFIER) { currentClass = scriptContent.slice(pos, pos + len); while (pos < scriptSize) { _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (scriptContent[pos] == '{') { pos += len; break; } else if (scriptContent[pos] == ';') { currentClass = ""; pos += len; break; } pos += len; } } continue; } // Check if end of class if (!currentClass.empty() && token == "}"_s) { currentClass = { }; pos += len; continue; } // Check if namespace so the metadata for members can be gathered if (token == "namespace"_s) { do { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); } while (t == asTC_COMMENT || t == asTC_WHITESPACE); if (!currentNamespace.empty()) { currentNamespace += "::"_s; } currentNamespace += scriptContent.slice(pos, pos + len); // Search until first { is encountered while (pos < scriptSize) { _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); // If start of namespace section encountered stop if (scriptContent[pos] == '{') { pos += len; break; } pos += len; } continue; } // Check if end of namespace if (!currentNamespace.empty() && token == "}"_s) { StringView found = currentNamespace.findLast("::"_s); if (found != nullptr) { currentNamespace = currentNamespace.prefix(found.begin()); } else { currentNamespace = { }; } pos += len; continue; } if (token == "["_s) { pos = ExtractMetadata(scriptContent, pos, metadata); MetadataType type; ExtractDeclaration(scriptContent, pos, metadataName, metadataDeclaration, type); if (type != MetadataType::Unknown) { _foundDeclarations.emplace_back(std::move(metadata), metadataName, metadataDeclaration, type, currentClass, currentNamespace); } } else if (token == "#"_s && (pos + 1 < scriptSize)) { std::int32_t start = pos++; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_IDENTIFIER) { token = scriptContent.slice(pos, pos + len); if (token == "include"_s) { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_WHITESPACE) { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); } if (t == asTC_VALUE && len > 2 && (scriptContent[pos] == '"' || scriptContent[pos] == '\'')) { StringView filename = StringView(&scriptContent[pos + 1], len - 2); StringView invalidChar = filename.findAny("\n\r\t"); if (invalidChar != nullptr) { String str = "Invalid file name for #include - it contains a line-break or tab: \""_s + filename.prefix(invalidChar.begin()) + "\""_s; _engine->WriteMessage(path.data(), 0, 0, asMSGTYPE_ERROR, str.data()); } else { String filenameProcessed = OnProcessInclude(filename, absolutePath); if (!filenameProcessed.empty()) { includes.push_back(filenameProcessed); } } pos += len; for (std::int32_t i = start; i < pos; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } } } else if (token == "pragma"_s) { pos += len; for (; pos < scriptSize && scriptContent[pos] != '\n'; pos++); OnProcessPragma(scriptContent.slice(start + 7, pos).trimmed(), contextType); for (std::int32_t i = start; i < pos; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } } } else { // Check for lines starting with #!, e.g. shebang interpreter directive. These will be treated as comments and removed by the preprocessor if (scriptContent[pos] == '!') { pos += len; for (; pos < scriptSize && scriptContent[pos] != '\n'; pos++); for (std::int32_t i = start; i < pos; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } } } } else { // Don't search for metadata/includes within statement blocks or between tokens in statements pos = SkipStatement(scriptContent, pos); } } // Append the actual script _module->AddScriptSection(path.data(), scriptContent.data(), scriptSize, 0); if (includes.size() > 0) { // Load all included scripts for (auto& include : includes) { if (AddScriptFromFile(include, definedSymbols) == ScriptContextType::Unknown) { return ScriptContextType::Unknown; } } } return contextType; } ScriptBuildResult ScriptLoader::Build() { std::int32_t r = _module->Build(); if (r < 0) { return (ScriptBuildResult)r; } // After the script has been built, the metadata strings should be stored for later lookup for (auto& decl : _foundDeclarations) { _module->SetDefaultNamespace(decl.Namespace.data()); switch (decl.Type) { case MetadataType::Type: { std::int32_t typeId = _module->GetTypeIdByDecl(decl.Declaration.data()); if (typeId >= 0) { auto entry = _typeMetadataMap.emplace(typeId, Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } break; } case MetadataType::Function: { if (decl.ParentClass.empty()) { asIScriptFunction* func = _module->GetFunctionByDecl(decl.Declaration.data()); if (func != nullptr) { auto entry = _funcMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } else { std::int32_t typeId = _module->GetTypeIdByDecl(decl.ParentClass.data()); asITypeInfo* type = _engine->GetTypeInfoById(typeId); asIScriptFunction* func = type->GetMethodByDecl(decl.Declaration.data()); if (func != nullptr) { auto it = _classMetadataMap.find(typeId); if (it == _classMetadataMap.end()) { it = _classMetadataMap.emplace(typeId, ClassMetadata()).first; } auto entry = it->second.FuncMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } break; } case MetadataType::VirtualProperty: { if (decl.ParentClass.empty()) { asIScriptFunction* func = _module->GetFunctionByName(String("get_"_s + decl.Declaration).data()); if (func != nullptr) { auto entry = _funcMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_copy(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } func = _module->GetFunctionByName(String("set_"_s + decl.Declaration).data()); if (func != nullptr) { auto entry = _funcMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } else { std::int32_t typeId = _module->GetTypeIdByDecl(decl.ParentClass.data()); auto it = _classMetadataMap.find(typeId); if (it == _classMetadataMap.end()) { it = _classMetadataMap.emplace(typeId, ClassMetadata()).first; } asITypeInfo* type = _engine->GetTypeInfoById(typeId); asIScriptFunction* func = type->GetMethodByName(String("get_" + decl.Declaration).data()); if (func != nullptr) { auto entry = it->second.FuncMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_copy(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } func = type->GetMethodByName(String("set_" + decl.Declaration).data()); if (func != nullptr) { auto entry = it->second.FuncMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } break; } case MetadataType::Variable: { if (decl.ParentClass.empty()) { std::int32_t varIdx = _module->GetGlobalVarIndexByName(decl.Declaration.data()); if (varIdx >= 0) { auto entry = _varMetadataMap.emplace(varIdx, Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } else { std::int32_t typeId = _module->GetTypeIdByDecl(decl.ParentClass.data()); auto it = _classMetadataMap.find(typeId); if (it == _classMetadataMap.end()) { it = _classMetadataMap.emplace(typeId, ClassMetadata()).first; } asITypeInfo* objectType = _engine->GetTypeInfoById(typeId); std::int32_t idx = -1; for (std::uint32_t i = 0; i < (std::uint32_t)objectType->GetPropertyCount(); i++) { const char* name; objectType->GetProperty(i, &name); if (decl.Declaration == StringView(name)) { idx = i; break; } } if (idx >= 0) { auto entry = it->second.VarMetadataMap.emplace(idx, Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } break; } case MetadataType::FunctionOrVariable: { if (decl.ParentClass .empty()) { std::int32_t varIdx = _module->GetGlobalVarIndexByName(decl.Name.data()); if (varIdx >= 0) { auto entry = _varMetadataMap.emplace(varIdx, Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } else { asIScriptFunction* func = _module->GetFunctionByDecl(decl.Declaration.data()); if (func != nullptr) { auto entry = _funcMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } } else { std::int32_t typeId = _module->GetTypeIdByDecl(decl.ParentClass.data()); auto it = _classMetadataMap.find(typeId); if (it == _classMetadataMap.end()) { it = _classMetadataMap.emplace(typeId, ClassMetadata()).first; } asITypeInfo* objectType = _engine->GetTypeInfoById(typeId); std::int32_t idx = -1; for (std::uint32_t i = 0; i < (std::uint32_t)objectType->GetPropertyCount(); i++) { const char* name; objectType->GetProperty(i, &name); if (decl.Name == StringView(name)) { idx = i; break; } } if (idx >= 0) { auto entry = it->second.VarMetadataMap.emplace(idx, Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } else { asITypeInfo* type = _engine->GetTypeInfoById(typeId); asIScriptFunction* func = type->GetMethodByDecl(decl.Declaration.data()); if (func != nullptr) { auto entry = it->second.FuncMetadataMap.emplace(func->GetId(), Array(NoInit, decl.Metadata.size())).first; std::uninitialized_move(decl.Metadata.begin(), decl.Metadata.end(), entry->second.data()); } } } break; } } } _module->SetDefaultNamespace(""); // _foundDeclarations is not needed anymore _foundDeclarations.clear(); return ScriptBuildResult::Success; } void ScriptLoader::SetContextType(ScriptContextType value) { _scriptContextType = value; } std::int32_t ScriptLoader::ExcludeCode(String& scriptContent, std::int32_t pos) { std::int32_t scriptSize = (std::int32_t)scriptContent.size(); std::uint32_t len = 0; std::int32_t nested = 0; while (pos < scriptSize) { _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (scriptContent[pos] == '#') { scriptContent[pos] = ' '; pos++; _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); StringView token = scriptContent.slice(pos, pos + len); if (token == "if"_s) { nested++; } else if (token == "endif"_s) { if (nested-- == 0) { for (std::uint32_t i = pos; i < pos + len; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } pos += len; break; } } } if (scriptContent[pos] != '\n') { for (std::uint32_t i = pos; i < pos + len; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } } pos += len; } return pos; } std::int32_t ScriptLoader::SkipStatement(String& scriptContent, std::int32_t pos) { std::int32_t scriptSize = (std::int32_t)scriptContent.size(); std::uint32_t len = 0; // Skip until ; or { whichever comes first while (pos < scriptSize && scriptContent[pos] != ';' && scriptContent[pos] != '{') { _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); pos += len; } // Skip entire statement block if (pos < scriptSize && scriptContent[pos] == '{') { pos += 1; std::int32_t level = 1; while (level > 0 && pos < scriptSize) { asETokenClass t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_KEYWORD) { if (scriptContent[pos] == '{') { level++; } else if (scriptContent[pos] == '}') { level--; } } else if (t == asTC_IDENTIFIER) { // Convert all length() function calls to virtual properties for JJ2+ backward compatibility auto identifier = MutableStringView(&scriptContent[pos], len); if (identifier == "length"_s) { std::int32_t pos1 = pos + len; std::int32_t pos2 = pos1; std::uint32_t len2 = 0; asETokenClass t2 = asTC_UNKNOWN; while (pos2 < scriptSize) { t2 = _engine->ParseToken(&scriptContent[pos2], scriptSize - pos2, &len2); if (t2 != asTC_COMMENT && t2 != asTC_WHITESPACE) { break; } pos2 += len2; } if (t2 == asTC_KEYWORD && scriptContent[pos2] == '(') { std::int32_t pos3 = pos2 + len2; std::uint32_t len3 = 0; asETokenClass t3 = asTC_UNKNOWN; while (pos3 < scriptSize) { t3 = _engine->ParseToken(&scriptContent[pos3], scriptSize - pos3, &len3); if (t3 != asTC_COMMENT && t3 != asTC_WHITESPACE) { break; } pos3 += len3; } if (t3 == asTC_KEYWORD && scriptContent[pos3] == ')') { pos3 += len3; std::memset(&scriptContent[pos + len], ' ', pos3 - pos1); pos = pos3; continue; } } } } pos += len; } } else { pos += 1; } return pos; } std::int32_t ScriptLoader::ExtractMetadata(MutableStringView scriptContent, std::int32_t pos, SmallVectorImpl& metadata) { std::int32_t scriptSize = (std::int32_t)scriptContent.size(); metadata.clear(); // Extract all metadata, they can be separated by whitespace and comments while (true) { Array metadataString; // Overwrite the metadata with space characters to allow compilation scriptContent[pos++] = ' '; std::int32_t level = 1; std::uint32_t len = 0; while (level > 0 && pos < scriptSize) { asETokenClass t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); if (t == asTC_KEYWORD) { if (scriptContent[pos] == '[') { level++; } else if (scriptContent[pos] == ']') { level--; } } // Copy the metadata to our buffer if (level > 0) { arrayAppend(metadataString, arrayView((const char*)&scriptContent[pos], len)); } if (t != asTC_WHITESPACE) { for (uint32_t i = pos; i < pos + len; i++) { if (scriptContent[i] != '\n') { scriptContent[i] = ' '; } } } pos += len; } metadata.emplace_back(String(std::move(metadataString))); // Check for more metadata, possibly separated by comments asETokenClass t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); while (t == asTC_COMMENT || t == asTC_WHITESPACE) { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); } if (scriptContent[pos] != '[') { break; } } return pos; } std::int32_t ScriptLoader::ExtractDeclaration(StringView scriptContent, std::int32_t pos, String& name, String& declaration, MetadataType& type) { std::int32_t scriptSize = (std::int32_t)scriptContent.size(); std::int32_t start = pos; declaration = {}; type = MetadataType::Unknown; StringView token; std::uint32_t len = 0; asETokenClass t = asTC_WHITESPACE; // Skip white spaces, comments and leading decorators do { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); token = scriptContent.sliceSize(pos, len); } while (t == asTC_WHITESPACE || t == asTC_COMMENT || token == "private"_s || token == "protected"_s || token == "shared"_s || token == "external"_s || token == "final"_s || token == "abstract"_s); // We're expecting, either a class, interface, function, or variable declaration if (t == asTC_KEYWORD || t == asTC_IDENTIFIER) { token = scriptContent.sliceSize(pos, len); if (token == "interface"_s || token == "class"_s || token == "enum"_s) { do { pos += len; t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); } while (t == asTC_WHITESPACE || t == asTC_COMMENT); if (t == asTC_IDENTIFIER) { type = MetadataType::Type; declaration = scriptContent.slice(pos, pos + len); pos += len; return pos; } } else { // For function declarations, store everything up to the start of the // statement block, except for succeeding decorators (final, override, etc) // For variable declaration store just the name as there can only be one // We'll only know if the declaration is a variable or function declaration // when we see the statement block, or absense of a statement block. bool hasParenthesis = false; std::int32_t nestedParenthesis = 0; declaration += scriptContent.slice(pos, pos + len); pos += len; for (; pos < scriptSize;) { t = _engine->ParseToken(&scriptContent[pos], scriptSize - pos, &len); token = scriptContent.sliceSize(pos, len); if (t == asTC_KEYWORD) { if (token == "{"_s && nestedParenthesis == 0) { if (hasParenthesis) { type = MetadataType::Function; } else { declaration = name; type = MetadataType::VirtualProperty; } return pos; } if ((token == "="_s && !hasParenthesis) || token == ";"_s) { if (hasParenthesis) { // The declaration is ambigous, it can be variable with initialization, or function prototype type = MetadataType::FunctionOrVariable; } else { declaration = name; type = MetadataType::Variable; } return pos; } else if (token == "("_s) { nestedParenthesis++; // This is the first parenthesis we encounter. If the parenthesis isn't followed // by a statement block, then this is a variable declaration, in which case we // should only store the type and name of the variable, not the initialization parameters. hasParenthesis = true; } else if (token == ")"_s) { nestedParenthesis--; } } else if (t == asTC_IDENTIFIER) { name = token; } if (!hasParenthesis || nestedParenthesis > 0 || t != asTC_IDENTIFIER || (token != "final"_s && token != "override"_s)) { declaration += token; } pos += len; } } } return start; } ArrayView ScriptLoader::GetMetadataForType(std::int32_t typeId) { auto it = _typeMetadataMap.find(typeId); if (it != _typeMetadataMap.end()) { return it->second; } return {}; } ArrayView ScriptLoader::GetMetadataForFunction(asIScriptFunction* func) { if (func != nullptr) { auto it = _funcMetadataMap.find(func->GetId()); if (it != _funcMetadataMap.end()) { return it->second; } } return {}; } ArrayView ScriptLoader::GetMetadataForVariable(std::int32_t varIdx) { auto it = _varMetadataMap.find(varIdx); if (it != _varMetadataMap.end()) { return it->second; } return {}; } ArrayView ScriptLoader::GetMetadataForTypeProperty(std::int32_t typeId, std::int32_t varIdx) { auto typeIt = _classMetadataMap.find(typeId); if (typeIt == _classMetadataMap.end()) { return {}; } auto propIt = typeIt->second.VarMetadataMap.find(varIdx); if (propIt == typeIt->second.VarMetadataMap.end()) { return {}; } return propIt->second; } ArrayView ScriptLoader::GetMetadataForTypeMethod(std::int32_t typeId, asIScriptFunction* method) { if (method == nullptr) { return {}; } auto typeIt = _classMetadataMap.find(typeId); if (typeIt == _classMetadataMap.end()) { return {}; } auto methodIt = typeIt->second.FuncMetadataMap.find(method->GetId()); if (methodIt == typeIt->second.FuncMetadataMap.end()) { return {}; } return methodIt->second; } String ScriptLoader::MakeRelativePath(StringView path, StringView relativeToFile) { if (path.empty() || path.size() > fs::MaxPathLength) return {}; char result[fs::MaxPathLength + 1]; std::size_t length = 0; if (path[0] == '/' || path[0] == '\\') { // Absolute path from "Content" directory const char* src = &path[1]; const char* srcLast = src; auto contentPath = ContentResolver::Get().GetContentPath(); std::memcpy(result, contentPath.data(), contentPath.size()); char* dst = result + contentPath.size(); if (*(dst - 1) == '/' || *(dst - 1) == '\\') { dst--; } char* dstStart = dst; char* dstLast = dstStart; while (true) { bool end = (src - path.begin()) >= path.size(); if (end || *src == '/' || *src == '\\') { if (src > srcLast) { size_t length = src - srcLast; if (length == 1 && srcLast[0] == '.') { // Ignore this } else if (length == 2 && srcLast[0] == '.' && srcLast[1] == '.') { if (dst != dstStart) { if (dst == dstLast && dstStart <= dstLast - 1) { dstLast--; while (dstStart <= dstLast) { if (*dstLast == '/' || *dstLast == '\\') { break; } dstLast--; } } dst = dstLast; } } else { dstLast = dst; if ((dst - result) + (sizeof(fs::PathSeparator) - 1) + (src - srcLast) >= fs::MaxPathLength) { return {}; } if (dst != result) { std::memcpy(dst, fs::PathSeparator, sizeof(fs::PathSeparator) - 1); dst += sizeof(fs::PathSeparator) - 1; } std::memcpy(dst, srcLast, src - srcLast); dst += src - srcLast; } } if (end) { break; } srcLast = src + 1; } src++; } length = dst - result; } else { // Relative path to script file String dirPath = fs::GetDirectoryName(relativeToFile); if (dirPath.empty()) return {}; const char* src = &path[0]; const char* srcLast = src; std::memcpy(result, dirPath.data(), dirPath.size()); char* dst = result + dirPath.size(); if (*(dst - 1) == '/' || *(dst - 1) == '\\') { dst--; } char* searchBack = dst - 2; char* dstStart = dst; while (result <= searchBack) { if (*searchBack == '/' || *searchBack == '\\') { dstStart = searchBack + 1; break; } searchBack--; } char* dstLast = dstStart; while (true) { bool end = (src - path.begin()) >= path.size(); if (end || *src == '/' || *src == '\\') { if (src > srcLast) { size_t length = src - srcLast; if (length == 1 && srcLast[0] == '.') { // Ignore this } else if (length == 2 && srcLast[0] == '.' && srcLast[1] == '.') { if (dst != dstStart) { if (dst == dstLast && dstStart <= dstLast - 1) { dstLast--; while (dstStart <= dstLast) { if (*dstLast == '/' || *dstLast == '\\') { break; } dstLast--; } } dst = dstLast; } } else { dstLast = dst; if ((dst - result) + (sizeof(fs::PathSeparator) - 1) + (src - srcLast) >= fs::MaxPathLength) { return {}; } if (dst != result) { std::memcpy(dst, fs::PathSeparator, sizeof(fs::PathSeparator) - 1); dst += sizeof(fs::PathSeparator) - 1; } std::memcpy(dst, srcLast, src - srcLast); dst += src - srcLast; } } if (end) { break; } srcLast = src + 1; } src++; } length = dst - result; } return String(result, length); } asIScriptContext* ScriptLoader::RequestContextCallback(asIScriptEngine* engine, void* param) { // Check if there is a free context available in the pool auto _this = static_cast(param); if (!_this->_contextPool.empty()) { return _this->_contextPool.pop_back_val(); } else { // No free context was available so we'll have to create a new one return engine->CreateContext(); } } void ScriptLoader::ReturnContextCallback(asIScriptEngine* engine, asIScriptContext* ctx, void* param) { // Unprepare the context to free any objects it may still hold (e.g. return value) // This must be done before making the context available for re-use, as the clean // up may trigger other script executions, e.g. if a destructor needs to call a function. ctx->Unprepare(); // Place the context into the pool for when it will be needed again auto _this = static_cast(param); _this->_contextPool.push_back(ctx); } void ScriptLoader::Message(const asSMessageInfo& msg) { TraceLevel level; switch (msg.type) { case asMSGTYPE_ERROR: level = TraceLevel::Error; break; case asMSGTYPE_WARNING: level = TraceLevel::Warning; break; default: level = TraceLevel::Info; break; } if (msg.section != nullptr && msg.section[0] != '\0') { __DEATH_TRACE(level, "AS!", "{}:{}({}): {}", msg.section, msg.row, msg.col, msg.message); } else { __DEATH_TRACE(level, "AS!", "{}", msg.message); } } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/ScriptLoader.h000066400000000000000000000135271512772601700262510ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../Main.h" #include "../../nCine/Base/HashMap.h" #include #include #include #include using namespace Death::Containers; using namespace Death::Containers::Literals; using namespace nCine; namespace Jazz2::Scripting { class CScriptArray; /** @brief Script context type */ enum class ScriptContextType { /** @brief Unknown/unsupported script part */ Unknown, /** @brief Already included script part */ AlreadyIncluded, /** @brief Legacy (JJ2+ compatible) script part */ Legacy, /** @brief Standard (Jazz² Resurrection compatible) script part */ Standard }; /** @brief Result of @ref ScriptLoader::Build() */ enum class ScriptBuildResult { /** @brief The engine succeeded */ Success = asSUCCESS, /** @brief The engine configuration is invalid */ InvalidConfiguration = asINVALID_CONFIGURATION, /** @brief The script failed to build */ BuildFailed = asERROR, /** @brief Another thread is currently building */ BuildInProgress = asBUILD_IN_PROGRESS, /** @brief It was not possible to initialize at least one of the global variables */ InitGlobalVarsFailed = asINIT_GLOBAL_VARS_FAILED, /** @brief Compiler support is disabled in the engine */ NotSupported = asNOT_SUPPORTED, /** @brief The code in the module is still being used and and cannot be removed */ ModuleIsInUse = asMODULE_IS_IN_USE }; /** @brief Generic **AngelScript** script loader with `#include` and `#pragma` directive support @experimental */ class ScriptLoader { public: /** @brief Returns @ref ScriptLoader instance from active **AngelScript** context if exists */ template::value>::type> static T* FromActiveContext() { auto* ctx = asGetActiveContext(); DEATH_ASSERT(ctx != nullptr, "Active context is not set", nullptr); return static_cast(ctx->GetEngine()->GetUserData(EngineToOwner)); } ScriptLoader(); virtual ~ScriptLoader(); /** @brief Returns **AngelScript** engine */ asIScriptEngine* GetEngine() const { return _engine; } /** @brief Returns **AngelScript** main module */ asIScriptModule* GetMainModule() const { return _module; } /** @brief Returns context type */ ScriptContextType GetContextType() const { return _scriptContextType; } protected: /** @brief Adds a script path from file to the main module */ ScriptContextType AddScriptFromFile(StringView path, const HashMap& definedSymbols); /** @brief Builds the main module and extracts metadata */ ScriptBuildResult Build(); /** @brief Sets context type */ void SetContextType(ScriptContextType value); /** @brief Returns metadata for specified type */ ArrayView GetMetadataForType(std::int32_t typeId); /** @brief Returns metadata for specified function */ ArrayView GetMetadataForFunction(asIScriptFunction* func); /** @brief Returns metadata for specified variable */ ArrayView GetMetadataForVariable(std::int32_t varIdx); /** @brief Returns metadata for specified property */ ArrayView GetMetadataForTypeProperty(std::int32_t typeId, std::int32_t varIdx); /** @brief Returns metadata for specified method */ ArrayView GetMetadataForTypeMethod(std::int32_t typeId, asIScriptFunction* method); /** @brief Called when `#include` directive is encountered in a script file */ virtual String OnProcessInclude(StringView includePath, StringView scriptPath) = 0; /** @brief Called when `#pragma` directive is encountered in a script file */ virtual void OnProcessPragma(StringView content, ScriptContextType& contextType) = 0; /** @brief Creates a relative path */ static String MakeRelativePath(StringView path, StringView relativeToFile); private: enum class MetadataType { Unknown, Type, Function, Variable, VirtualProperty, FunctionOrVariable }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct RawMetadataDeclaration { RawMetadataDeclaration(SmallVectorImpl&& m, String n, String d, MetadataType t, String c, String ns) : Metadata(std::move(m)), Name(std::move(n)), Declaration(std::move(d)), Type(t), ParentClass(std::move(c)), Namespace(std::move(ns)) {} SmallVector Metadata; String Name; String Declaration; MetadataType Type; String ParentClass; String Namespace; }; struct ClassMetadata { HashMap> FuncMetadataMap; HashMap> VarMetadataMap; }; #endif static constexpr asPWORD EngineToOwner = 0; asIScriptEngine* _engine; asIScriptModule* _module; ScriptContextType _scriptContextType; SmallVector _contextPool; HashMap _includedFiles; SmallVector _foundDeclarations; HashMap> _typeMetadataMap; HashMap> _funcMetadataMap; HashMap> _varMetadataMap; HashMap _classMetadataMap; std::int32_t ExcludeCode(String& scriptContent, std::int32_t pos); std::int32_t SkipStatement(String& scriptContent, std::int32_t pos); std::int32_t ExtractMetadata(MutableStringView scriptContent, std::int32_t pos, SmallVectorImpl& metadata); std::int32_t ExtractDeclaration(StringView scriptContent, std::int32_t pos, String& name, String& declaration, MetadataType& type); static asIScriptContext* RequestContextCallback(asIScriptEngine* engine, void* param); static void ReturnContextCallback(asIScriptEngine* engine, asIScriptContext* ctx, void* param); void Message(const asSMessageInfo& msg); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/ScriptPlayerWrapper.cpp000066400000000000000000000234321512772601700301670ustar00rootroot00000000000000#if defined(WITH_ANGELSCRIPT) #include "ScriptPlayerWrapper.h" #include "LevelScriptLoader.h" #include "RegisterArray.h" #include "../ILevelHandler.h" using namespace Jazz2::Actors; #define AsClassName "Player" namespace Jazz2::Scripting { ScriptPlayerWrapper::ScriptPlayerWrapper(LevelScriptLoader* levelScripts, std::int32_t playerIndex) : _levelScripts(levelScripts), _refCount(1) { auto players = levelScripts->GetPlayers(); _player = (playerIndex < players.size() ? players[playerIndex] : nullptr); } ScriptPlayerWrapper::ScriptPlayerWrapper(LevelScriptLoader* levelScripts, Player* player) : _levelScripts(levelScripts), _refCount(1), _player(player) { } ScriptPlayerWrapper::~ScriptPlayerWrapper() { } void ScriptPlayerWrapper::RegisterFactory(asIScriptEngine* engine) { std::int32_t r; r = engine->RegisterObjectType(AsClassName, 0, asOBJ_REF); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour(AsClassName, asBEHAVE_FACTORY, AsClassName " @f(int = 0)", asFUNCTION(ScriptPlayerWrapper::Factory), asCALL_CDECL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour(AsClassName, asBEHAVE_ADDREF, "void f()", asMETHOD(ScriptPlayerWrapper, AddRef), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectBehaviour(AsClassName, asBEHAVE_RELEASE, "void f()", asMETHOD(ScriptPlayerWrapper, Release), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, AsClassName " &opAssign(const " AsClassName " &in)", asMETHOD(ScriptPlayerWrapper, operator=), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "bool get_IsInGame() const property", asMETHOD(ScriptPlayerWrapper, asIsInGame), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_GetIndex() const property", asMETHOD(ScriptPlayerWrapper, asGetIndex), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_GetPlayerType() const property", asMETHOD(ScriptPlayerWrapper, asGetPlayerType), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "float get_X() const property", asMETHOD(ScriptPlayerWrapper, asGetX), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "float get_Y() const property", asMETHOD(ScriptPlayerWrapper, asGetY), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "float get_SpeedX() const property", asMETHOD(ScriptPlayerWrapper, asGetSpeedX), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "float get_SpeedY() const property", asMETHOD(ScriptPlayerWrapper, asGetSpeedY), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_Health() const property", asMETHOD(ScriptPlayerWrapper, asGetHealth), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_Lives() const property", asMETHOD(ScriptPlayerWrapper, asGetLives), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_FoodEaten() const property", asMETHOD(ScriptPlayerWrapper, asGetFoodEaten), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_Score() const property", asMETHOD(ScriptPlayerWrapper, asGetScore), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void set_Score(int) property", asMETHOD(ScriptPlayerWrapper, asSetScore), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "uint16 get_Layer() const property", asMETHOD(ScriptPlayerWrapper, asGetLayer), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void set_Layer(uint16) property", asMETHOD(ScriptPlayerWrapper, asSetLayer), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "bool get_WeaponAllowed() const property", asMETHOD(ScriptPlayerWrapper, asGetWeaponAllowed), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void set_WeaponAllowed(bool) property", asMETHOD(ScriptPlayerWrapper, asSetWeaponAllowed), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "int get_WeaponAmmo(int) const property", asMETHOD(ScriptPlayerWrapper, asGetWeaponAmmo), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void set_WeaponAmmo(int, int) property", asMETHOD(ScriptPlayerWrapper, asSetWeaponAmmo), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void DecreaseHealth(int)", asMETHOD(ScriptPlayerWrapper, asDecreaseHealth), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void MoveTo(float, float)", asMETHOD(ScriptPlayerWrapper, asMoveTo), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void WarpTo(float, float)", asMETHOD(ScriptPlayerWrapper, asWarpTo), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void MoveBy(float, float)", asMETHOD(ScriptPlayerWrapper, asMoveBy), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void PlaySfx(const string &in, float = 1.0, float = 1.0)", asMETHOD(ScriptPlayerWrapper, asPlaySfx), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void SetAnimation(int)", asMETHOD(ScriptPlayerWrapper, asSetAnimationState), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void MorphTo(int)", asMETHOD(ScriptPlayerWrapper, asMorphTo), asCALL_THISCALL); RETURN_ASSERT(r >= 0); r = engine->RegisterObjectMethod(AsClassName, "void MorphRevert()", asMETHOD(ScriptPlayerWrapper, asMorphRevert), asCALL_THISCALL); RETURN_ASSERT(r >= 0); } ScriptPlayerWrapper* ScriptPlayerWrapper::Factory(std::int32_t playerIndex) { auto owner = ScriptLoader::FromActiveContext(); void* mem = asAllocMem(sizeof(ScriptPlayerWrapper)); return new(mem) ScriptPlayerWrapper(owner, playerIndex); } void ScriptPlayerWrapper::AddRef() { _refCount++; } void ScriptPlayerWrapper::Release() { if (--_refCount == 0) { this->~ScriptPlayerWrapper(); asFreeMem(this); } } bool ScriptPlayerWrapper::asIsInGame() const { return (_player != nullptr); } std::int32_t ScriptPlayerWrapper::asGetIndex() const { return (_player != nullptr ? _player->_playerIndex : -1); } std::int32_t ScriptPlayerWrapper::asGetPlayerType() const { return (_player != nullptr ? (std::int32_t)_player->_playerType : -1); } float ScriptPlayerWrapper::asGetX() const { return (_player != nullptr ? _player->_pos.X : -1); } float ScriptPlayerWrapper::asGetY() const { return (_player != nullptr ? _player->_pos.Y : -1); } float ScriptPlayerWrapper::asGetSpeedX() const { return (_player != nullptr ? _player->_speed.X : -1); } float ScriptPlayerWrapper::asGetSpeedY() const { return (_player != nullptr ? _player->_speed.Y : -1); } std::int32_t ScriptPlayerWrapper::asGetHealth() const { return (_player != nullptr ? _player->_health : -1); } std::int32_t ScriptPlayerWrapper::asGetLives() const { return (_player != nullptr ? _player->_lives : -1); } std::int32_t ScriptPlayerWrapper::asGetFoodEaten() const { return (_player != nullptr ? _player->_foodEaten : -1); } std::int32_t ScriptPlayerWrapper::asGetScore() const { return (_player != nullptr ? _player->_score : -1); } void ScriptPlayerWrapper::asSetScore(int value) { if (_player != nullptr) { _player->_score = value; } } std::uint16_t ScriptPlayerWrapper::asGetLayer() const { return (_player != nullptr ? _player->_renderer.layer() : 0); } void ScriptPlayerWrapper::asSetLayer(std::uint16_t value) { if (_player != nullptr) { _player->_renderer.setLayer(value); } } bool ScriptPlayerWrapper::asGetWeaponAllowed() const { return (_player != nullptr && _player->_weaponAllowed); } void ScriptPlayerWrapper::asSetWeaponAllowed(bool value) { if (_player != nullptr) { _player->_weaponAllowed = value; } } std::int32_t ScriptPlayerWrapper::asGetWeaponAmmo(std::int32_t weaponType) const { return (_player != nullptr && weaponType >= 0 && weaponType < (std::int32_t)WeaponType::Count ? _player->_weaponAmmo[weaponType] : -1); } void ScriptPlayerWrapper::asSetWeaponAmmo(std::int32_t weaponType, std::int32_t value) { if (_player != nullptr && weaponType >= 0 && weaponType < (std::int32_t)WeaponType::Count) { _player->_weaponAmmo[weaponType] = value; } } void ScriptPlayerWrapper::asDecreaseHealth(std::int32_t amount) { if (_player != nullptr) { _player->DecreaseHealth(amount); } } void ScriptPlayerWrapper::asMoveTo(float x, float y) { if (_player != nullptr) { _player->WarpToPosition(Vector2f(x, y), WarpFlags::Fast); } } void ScriptPlayerWrapper::asWarpTo(float x, float y) { if (_player != nullptr) { _player->WarpToPosition(Vector2f(x, y), WarpFlags::Default); } } void ScriptPlayerWrapper::asMoveBy(float x, float y) { if (_player != nullptr) { _player->WarpToPosition(Vector2f(_player->_pos.X + x, _player->_pos.Y + y), WarpFlags::Fast); } } void ScriptPlayerWrapper::asPlaySfx(const String& identifier, float gain, float pitch) { if (_player != nullptr) { _player->PlayPlayerSfx(identifier, gain, pitch); } } void ScriptPlayerWrapper::asSetAnimationState(std::int32_t state) { if (_player != nullptr) { _player->SetAnimation((AnimState)state); } } void ScriptPlayerWrapper::asMorphTo(std::int32_t playerType) { if (_player != nullptr) { _player->MorphTo((PlayerType)playerType); } } void ScriptPlayerWrapper::asMorphRevert() { if (_player != nullptr) { _player->MorphRevert(); } } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Scripting/ScriptPlayerWrapper.h000066400000000000000000000040551512772601700276340ustar00rootroot00000000000000#pragma once #if defined(WITH_ANGELSCRIPT) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../Actors/Player.h" class asIScriptEngine; class asIScriptModule; class asIScriptObject; class asIScriptFunction; class asILockableSharedBool; namespace Jazz2::Actors { class Player; } namespace Jazz2::Scripting { class LevelScriptLoader; class ScriptPlayerWrapper { public: ScriptPlayerWrapper(LevelScriptLoader* levelScripts, std::int32_t playerIndex); ScriptPlayerWrapper(LevelScriptLoader* levelScripts, Actors::Player* player); ~ScriptPlayerWrapper(); static void RegisterFactory(asIScriptEngine* engine); static ScriptPlayerWrapper* Factory(std::int32_t playerIndex); void AddRef(); void Release(); // Assignment operator ScriptPlayerWrapper& operator=(const ScriptPlayerWrapper& o) { // Copy only the content, not the script proxy class //_value = o._value; return *this; } protected: #ifndef DOXYGEN_GENERATING_OUTPUT LevelScriptLoader* _levelScripts; Actors::Player* _player; #endif bool asIsInGame() const; std::int32_t asGetIndex() const; std::int32_t asGetPlayerType() const; float asGetX() const; float asGetY() const; float asGetSpeedX() const; float asGetSpeedY() const; std::int32_t asGetHealth() const; std::int32_t asGetLives() const; std::int32_t asGetFoodEaten() const; std::int32_t asGetScore() const; void asSetScore(std::int32_t value); std::uint16_t asGetLayer() const; void asSetLayer(uint16_t value); bool asGetWeaponAllowed() const; void asSetWeaponAllowed(bool value); std::int32_t asGetWeaponAmmo(std::int32_t weaponType) const; void asSetWeaponAmmo(std::int32_t weaponType, std::int32_t value); void asDecreaseHealth(std::int32_t amount); void asMoveTo(float x, float y); void asWarpTo(float x, float y); void asMoveBy(float x, float y); void asPlaySfx(const String& identifier, float gain, float pitch); void asSetAnimationState(std::int32_t state); void asMorphTo(std::int32_t playerType); void asMorphRevert(); private: std::int32_t _refCount; }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/ShieldType.h000066400000000000000000000005271512772601700237620ustar00rootroot00000000000000#pragma once #include "../Main.h" namespace Jazz2 { /** @brief Shield type */ enum class ShieldType : std::uint8_t { None, /**< No shield */ Fire, /**< Fire shield */ Water, /**< Water shield */ Lightning, /**< Lightning shield */ Laser, /**< Laser shield */ Count /**< Count of supported shield types */ }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/SuspendType.h000066400000000000000000000003151512772601700241660ustar00rootroot00000000000000#pragma once namespace Jazz2 { /** @brief Suspend type */ enum class SuspendType { None, /**< None */ Vine, /**< Vine */ Hook, /**< Hook */ SwingingVine /**< Swinging vine */ }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/000077500000000000000000000000001512772601700226135ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/ITileMapOwner.h000066400000000000000000000012351512772601700254440ustar00rootroot00000000000000#pragma once #include "../Actors/ActorBase.h" namespace Jazz2::Tiles { /** @brief Interface used to notify tile map owner of various events */ class ITileMapOwner { public: /** @brief Plays a common sound effect */ virtual std::shared_ptr PlayCommonSfx(StringView identifier, const Vector3f& pos, float gain = 1.0f, float pitch = 1.0f) = 0; /** @brief Called when a destructible tile animation is advanced */ virtual void OnAdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount) = 0; /** @brief Called when a tile is frozen */ virtual void OnTileFrozen(std::int32_t x, std::int32_t y) = 0; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/TileCollisionParams.h000066400000000000000000000012061512772601700267000ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "TileDestructType.h" #include "../WeaponType.h" namespace Jazz2::Tiles { /** @brief Describes how the object interacts and collides with the environment */ struct TileCollisionParams { /** @brief Destruction type */ TileDestructType DestructType; /** @brief Whether movement direction is downwards */ bool Downwards; /** @brief Used weapon type */ WeaponType UsedWeaponType; /** @brief Remaining weapon strength */ std::int32_t WeaponStrength; /** @brief Movement speed */ float Speed; /** @brief Number of destroyed tiles */ /*out*/ std::int32_t TilesDestroyed; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/TileDestructType.h000066400000000000000000000006401512772601700262410ustar00rootroot00000000000000#pragma once #include "../../Main.h" namespace Jazz2::Tiles { /** @brief Flags that specify type of collision with tile map, supports a bitwise combination of its member values */ enum class TileDestructType { None = 0x00, Weapon = 0x01, Speed = 0x02, Collapse = 0x04, Special = 0x08, Trigger = 0x10, IgnoreSolidTiles = 0x20, VerticalMove = 0x40, }; DEATH_ENUM_FLAGS(TileDestructType); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/TileMap.cpp000066400000000000000000001716701512772601700246660ustar00rootroot00000000000000#include "TileMap.h" #include "../ContentResolver.h" #include "../LevelHandler.h" #include "../PreferencesCache.h" #include "../../nCine/tracy.h" #include "../../nCine/Base/Random.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Graphics/RenderResources.h" #include namespace Jazz2::Tiles { TileMap::TileMap(StringView tileSetPath, std::uint16_t captionTileId, bool applyPalette) : _owner(nullptr), _sprLayerIndex(-1), _pitType(PitType::FallForever), _renderCommandsCount(0), _collapsingTimer(0.0f), _animatedTilesOffset(0), _triggerState(ValueInit, TriggerCount), _triggerStateForRollback(ValueInit, TriggerCount), _texturedBackgroundLayer(-1), _texturedBackgroundPass(this) { auto& tileSetPart = _tileSets.emplace_back(); tileSetPart.Data = ContentResolver::Get().RequestTileSet(tileSetPath, captionTileId, applyPalette); DEATH_ASSERT(tileSetPart.Data != nullptr, ("Failed to load main tileset \"{}\"", tileSetPath), ); tileSetPart.Offset = 0; tileSetPart.Count = tileSetPart.Data->TileCount; _renderCommands.reserve(128); } TileMap::~TileMap() { TracyPlot("TileMap Render Commands", 0LL); } bool TileMap::IsValid() const { std::size_t count = _tileSets.size(); if (count == 0) { return false; } for (std::size_t i = 0; i < count; i++) { if (_tileSets[i].Data == nullptr) { return false; } } return true; } void TileMap::SetOwner(ITileMapOwner* owner) { _owner = owner; } Vector2i TileMap::GetSize() const { if (_sprLayerIndex == -1) { return {}; } return _layers[_sprLayerIndex].LayoutSize; } Vector2i TileMap::GetLevelBounds() const { if (_sprLayerIndex == -1) { return {}; } Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; return Vector2i(layoutSize.X * TileSet::DefaultTileSize, layoutSize.Y * TileSet::DefaultTileSize); } PitType TileMap::GetPitType() const { return _pitType; } void TileMap::SetPitType(PitType value) { _pitType = value; } void TileMap::OnUpdate(float timeMult) { ZoneScopedC(0xA09359); // Update animated tiles for (auto& animTile : _animatedTiles) { if (animTile.FrameDuration <= 0.0f || animTile.Tiles.size() < 2) { continue; } animTile.FramesLeft -= timeMult; while (animTile.FramesLeft <= 0.0f) { if (animTile.Forwards) { if (animTile.CurrentTileIdx == animTile.Tiles.size() - 1) { if (animTile.IsPingPong) { animTile.Forwards = false; animTile.FramesLeft += (animTile.FrameDuration * (1 + animTile.PingPongDelay)); } else { animTile.CurrentTileIdx = 0; std::int32_t delayFrames = 1 + animTile.Delay; if (animTile.DelayJitter > 0) { delayFrames += Random().Next(0, animTile.DelayJitter + 1); } animTile.FramesLeft += animTile.FrameDuration * delayFrames; } } else { animTile.CurrentTileIdx++; animTile.FramesLeft += animTile.FrameDuration; } } else { if (animTile.CurrentTileIdx == 0) { // Reverse only occurs on ping pong mode so no need to check for that here animTile.Forwards = true; std::int32_t delayFrames = 1 + animTile.Delay; if (animTile.DelayJitter > 0) { delayFrames += Random().Next(0, animTile.DelayJitter + 1); } animTile.FramesLeft += animTile.FrameDuration * delayFrames; } else { animTile.CurrentTileIdx--; animTile.FramesLeft += animTile.FrameDuration; } } } } // Update layer scrolling for (auto& layer : _layers) { if (layer.Description.SpeedModelX != LayerSpeedModel::SpeedMultipliers && std::abs(layer.Description.AutoSpeedX) > 0) { layer.Description.OffsetX += layer.Description.AutoSpeedX * timeMult; if (layer.Description.RepeatX) { if (layer.Description.AutoSpeedX > 0) { while (layer.Description.OffsetX > (layer.LayoutSize.X * 32)) { layer.Description.OffsetX -= (layer.LayoutSize.X * 32); } } else { while (layer.Description.OffsetX < 0) { layer.Description.OffsetX += (layer.LayoutSize.X * 32); } } } } if (layer.Description.SpeedModelY != LayerSpeedModel::SpeedMultipliers && std::abs(layer.Description.AutoSpeedY) > 0) { layer.Description.OffsetY += layer.Description.AutoSpeedY * timeMult; if (layer.Description.RepeatY) { if (layer.Description.AutoSpeedY > 0) { while (layer.Description.OffsetY > (layer.LayoutSize.Y * 32)) { layer.Description.OffsetY -= (layer.LayoutSize.Y * 32); } } else { while (layer.Description.OffsetY < 0) { layer.Description.OffsetY += (layer.LayoutSize.Y * 32); } } } } } AdvanceCollapsingTileTimers(timeMult); UpdateDebris(timeMult); } void TileMap::OnEndFrame() { // The command cache must be reset every frame, // OnDraw() is called multiple times if multiple viewports are active _renderCommandsCount = 0; } bool TileMap::OnDraw(RenderQueue& renderQueue) { ZoneScopedC(0xA09359); const Viewport* viewport = RenderResources::GetCurrentViewport(); Rectf cullingRect = viewport->GetCullingRect(); Vector2f viewCenter = cullingRect.Center(); for (auto& layer : _layers) { DrawLayer(renderQueue, layer, cullingRect, viewCenter); } if (_sprLayerIndex != -1) { auto& spriteLayer = _layers[_sprLayerIndex]; // Render black bars if layout width is smaller than viewport width if (spriteLayer.LayoutSize.X * TileSet::DefaultTileSize < cullingRect.W) { std::int32_t w = (cullingRect.W - (spriteLayer.LayoutSize.X * TileSet::DefaultTileSize)) / 2; // Left { auto command = RentRenderCommand(LayerRendererType::Solid); command->SetType(RenderCommand::Type::TileMap); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(w, cullingRect.H); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(0.0f, 0.0f, 0.0f, 1.0f); command->SetTransformation(Matrix4x4f::Translation(cullingRect.X, cullingRect.Y, 0.0f)); command->SetLayer(spriteLayer.Description.Depth); renderQueue.AddCommand(command); } // Right { auto command = RentRenderCommand(LayerRendererType::Solid); command->SetType(RenderCommand::Type::TileMap); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(w, cullingRect.H); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatValue(0.0f, 0.0f, 0.0f, 1.0f); command->SetTransformation(Matrix4x4f::Translation(cullingRect.X + cullingRect.W - w, cullingRect.Y, 0.0f)); command->SetLayer(spriteLayer.Description.Depth); renderQueue.AddCommand(command); } } } DrawDebris(renderQueue); TracyPlot("TileMap Render Commands", static_cast(_renderCommandsCount)); return true; } bool TileMap::IsTileEmpty(std::int32_t tx, std::int32_t ty) { if (_sprLayerIndex == -1) { return true; } Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; if (tx < 0 || tx >= layoutSize.X) { return false; } if (ty >= layoutSize.Y) { if (_pitType == PitType::StandOnPlatform) { return false; } ty = layoutSize.Y - 1; } else if (ty < 0) { ty = 0; } LayerTile& tile = _layers[_sprLayerIndex].Layout[ty * layoutSize.X + tx]; std::int32_t tileId = ResolveTileID(tile); TileSet* tileSet = ResolveTileSet(tileId); return (tileSet == nullptr || tileSet->IsTileMaskEmpty(tileId)); } bool TileMap::IsTileEmpty(const AABBf& aabb, TileCollisionParams& params) { if (_sprLayerIndex == -1) { return true; } Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; std::int32_t limitRightPx = layoutSize.X * TileSet::DefaultTileSize; std::int32_t limitBottomPx = layoutSize.Y * TileSet::DefaultTileSize; // Consider out-of-level coordinates as solid walls if (aabb.L < 0 || aabb.R >= limitRightPx) { return false; } if (aabb.B >= limitBottomPx && _pitType == PitType::StandOnPlatform) { return false; } // Check all covered tiles for collisions; if all are empty, no need to do pixel collision checking std::int32_t hx1 = std::max((std::int32_t)aabb.L, 0); std::int32_t hx2 = std::min((std::int32_t)std::ceil(aabb.R), limitRightPx - 1); std::int32_t hy1 = std::clamp((std::int32_t)aabb.T, 0, limitBottomPx - 2); std::int32_t hy2 = std::clamp((std::int32_t)std::ceil(aabb.B), 1, limitBottomPx - 1); std::int32_t hx1t = hx1 / TileSet::DefaultTileSize; std::int32_t hx2t = hx2 / TileSet::DefaultTileSize; std::int32_t hy1t = hy1 / TileSet::DefaultTileSize; std::int32_t hy2t = hy2 / TileSet::DefaultTileSize; auto* sprLayerLayout = _layers[_sprLayerIndex].Layout.get(); for (std::int32_t y = hy1t; y <= hy2t; y++) { for (std::int32_t x = hx1t; x <= hx2t; x++) { RecheckTile: LayerTile& tile = sprLayerLayout[y * layoutSize.X + x]; if (tile.DestructType == TileDestructType::Weapon && (params.DestructType & TileDestructType::Weapon) == TileDestructType::Weapon) { if ((tile.TileParams & (1 << (std::uint16_t)params.UsedWeaponType)) != 0) { if (AdvanceDestructibleTileAnimation(tile, x, y, params.WeaponStrength, "SceneryDestruct"_s)) { params.TilesDestroyed++; if (params.WeaponStrength <= 0) { return false; } else { goto RecheckTile; } } } else if (params.UsedWeaponType == WeaponType::Freezer && tile.DestructFrameIndex < GetTileDestructibleFrameCount(tile)) { std::int32_t tx = x * TileSet::DefaultTileSize + TileSet::DefaultTileSize / 2; std::int32_t ty = y * TileSet::DefaultTileSize + TileSet::DefaultTileSize / 2; _owner->OnTileFrozen(tx, ty); return false; } } else if (tile.DestructType == TileDestructType::Special && (params.DestructType & TileDestructType::Special) == TileDestructType::Special) { if ((params.DestructType & TileDestructType::VerticalMove) != TileDestructType::VerticalMove || (y + 1) * TileSet::DefaultTileSize <= (hy1 + 8) || (hy2 - 8) <= y * TileSet::DefaultTileSize) { std::int32_t amount = 1; if (AdvanceDestructibleTileAnimation(tile, x, y, amount, "SceneryDestruct"_s)) { params.TilesDestroyed++; goto RecheckTile; } } } else if (tile.DestructType == TileDestructType::Speed && (params.DestructType & TileDestructType::Speed) == TileDestructType::Speed) { std::int32_t amount = 1; if (tile.TileParams <= params.Speed && AdvanceDestructibleTileAnimation(tile, x, y, amount, "SceneryDestruct"_s)) { params.TilesDestroyed++; goto RecheckTile; } } else if (tile.DestructType == TileDestructType::Collapse && (params.DestructType & TileDestructType::Collapse) == TileDestructType::Collapse) { bool found = false; for (auto& current : _activeCollapsingTiles) { if (current == Vector2i(x, y)) { found = true; break; } } if (!found) { _activeCollapsingTiles.emplace_back(x, y); params.TilesDestroyed++; } } if ((params.DestructType & TileDestructType::IgnoreSolidTiles) != TileDestructType::IgnoreSolidTiles && tile.HasSuspendType == SuspendType::None && ((tile.Flags & LayerTileFlags::OneWay) != LayerTileFlags::OneWay || params.Downwards)) { std::int32_t tileId = ResolveTileID(tile); TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr || tileSet->IsTileMaskEmpty(tileId)) { continue; } std::int32_t tx = x * TileSet::DefaultTileSize; std::int32_t ty = y * TileSet::DefaultTileSize; std::int32_t left = std::max(hx1 - tx, 0); std::int32_t right = std::min(hx2 - tx, TileSet::DefaultTileSize - 1); std::int32_t top = std::max(hy1 - ty, 0); std::int32_t bottom = std::min(hy2 - ty, TileSet::DefaultTileSize - 1); if ((tile.Flags & LayerTileFlags::FlipX) == LayerTileFlags::FlipX) { std::int32_t left2 = left; left = (TileSet::DefaultTileSize - 1 - right); right = (TileSet::DefaultTileSize - 1 - left2); } if ((tile.Flags & LayerTileFlags::FlipY) == LayerTileFlags::FlipY) { std::int32_t top2 = top; top = (TileSet::DefaultTileSize - 1 - bottom); bottom = (TileSet::DefaultTileSize - 1 - top2); } top *= TileSet::DefaultTileSize; bottom *= TileSet::DefaultTileSize; std::uint8_t* mask = tileSet->GetTileMask(tileId); for (std::int32_t ry = top; ry <= bottom; ry += TileSet::DefaultTileSize) { for (std::int32_t rx = left; rx <= right; rx++) { if (mask[ry | rx]) { return false; } } } } } } return true; } bool TileMap::CanBeDestroyed(const AABBf& aabb, TileCollisionParams& params) { if (_sprLayerIndex == -1) { return true; } Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; std::int32_t limitRightPx = layoutSize.X * TileSet::DefaultTileSize; std::int32_t limitBottomPx = layoutSize.Y * TileSet::DefaultTileSize; // Consider out-of-level coordinates as solid walls if (aabb.L < 0 || aabb.R >= limitRightPx) { return false; } if (aabb.B >= limitBottomPx && _pitType == PitType::StandOnPlatform) { return false; } // Check all covered tiles for collisions; if all are empty, no need to do pixel collision checking std::int32_t hx1 = std::max((std::int32_t)aabb.L, 0); std::int32_t hx2 = std::min((std::int32_t)std::ceil(aabb.R), limitRightPx - 1); std::int32_t hy1 = std::clamp((std::int32_t)aabb.T, 0, limitBottomPx - 2); std::int32_t hy2 = std::clamp((std::int32_t)std::ceil(aabb.B), 1, limitBottomPx - 1); std::int32_t hx1t = hx1 / TileSet::DefaultTileSize; std::int32_t hx2t = hx2 / TileSet::DefaultTileSize; std::int32_t hy1t = hy1 / TileSet::DefaultTileSize; std::int32_t hy2t = hy2 / TileSet::DefaultTileSize; auto* sprLayerLayout = _layers[_sprLayerIndex].Layout.get(); for (std::int32_t y = hy1t; y <= hy2t; y++) { for (std::int32_t x = hx1t; x <= hx2t; x++) { LayerTile& tile = sprLayerLayout[y * layoutSize.X + x]; if ((tile.DestructType & TileDestructType::Weapon) == TileDestructType::Weapon && (params.DestructType & TileDestructType::Weapon) == TileDestructType::Weapon) { if (tile.DestructFrameIndex < GetTileDestructibleFrameCount(tile) && ((tile.TileParams & (1 << (std::uint16_t)params.UsedWeaponType)) != 0 || params.UsedWeaponType == WeaponType::Freezer)) { return true; } } else if ((tile.DestructType & TileDestructType::Special) == TileDestructType::Special && (params.DestructType & TileDestructType::Special) == TileDestructType::Special) { if ((params.DestructType & TileDestructType::VerticalMove) != TileDestructType::VerticalMove || (y + 1) * TileSet::DefaultTileSize <= (hy1 + 8) || (hy2 - 8) <= y * TileSet::DefaultTileSize) { if (tile.DestructFrameIndex < GetTileDestructibleFrameCount(tile)) { return true; } } } else if ((tile.DestructType & TileDestructType::Speed) == TileDestructType::Speed && (params.DestructType & TileDestructType::Speed) == TileDestructType::Speed) { if (tile.DestructFrameIndex < GetTileDestructibleFrameCount(tile) && tile.TileParams <= params.Speed) { return true; } } else if ((tile.DestructType & TileDestructType::Collapse) == TileDestructType::Collapse && (params.DestructType & TileDestructType::Collapse) == TileDestructType::Collapse) { bool found = false; for (auto& current : _activeCollapsingTiles) { if (current == Vector2i(x, y)) { found = true; break; } } if (!found) { return true; } } if ((params.DestructType & TileDestructType::IgnoreSolidTiles) != TileDestructType::IgnoreSolidTiles && tile.HasSuspendType == SuspendType::None && ((tile.Flags & LayerTileFlags::OneWay) != LayerTileFlags::OneWay || params.Downwards)) { std::int32_t tileId = ResolveTileID(tile); TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr || tileSet->IsTileMaskEmpty(tileId)) { continue; } std::int32_t tx = x * TileSet::DefaultTileSize; std::int32_t ty = y * TileSet::DefaultTileSize; std::int32_t left = std::max(hx1 - tx, 0); std::int32_t right = std::min(hx2 - tx, TileSet::DefaultTileSize - 1); std::int32_t top = std::max(hy1 - ty, 0); std::int32_t bottom = std::min(hy2 - ty, TileSet::DefaultTileSize - 1); if ((tile.Flags & LayerTileFlags::FlipX) == LayerTileFlags::FlipX) { std::int32_t left2 = left; left = (TileSet::DefaultTileSize - 1 - right); right = (TileSet::DefaultTileSize - 1 - left2); } if ((tile.Flags & LayerTileFlags::FlipY) == LayerTileFlags::FlipY) { std::int32_t top2 = top; top = (TileSet::DefaultTileSize - 1 - bottom); bottom = (TileSet::DefaultTileSize - 1 - top2); } top *= TileSet::DefaultTileSize; bottom *= TileSet::DefaultTileSize; std::uint8_t* mask = tileSet->GetTileMask(tileId); for (std::int32_t ry = top; ry <= bottom; ry += TileSet::DefaultTileSize) { for (std::int32_t rx = left; rx <= right; rx++) { if (mask[ry | rx]) { return false; } } } } } } return false; } SuspendType TileMap::GetTileSuspendState(float x, float y) { constexpr std::int32_t Tolerance = 4; if (_sprLayerIndex == -1) { return SuspendType::None; } std::int32_t tx = (std::int32_t)x / TileSet::DefaultTileSize; std::int32_t ty = (std::int32_t)y / TileSet::DefaultTileSize; Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; if (tx < 0 || ty < 0 || tx >= layoutSize.X || ty >= layoutSize.Y) { return SuspendType::None; } TileMapLayer& layer = _layers[_sprLayerIndex]; LayerTile& tile = layer.Layout[tx + ty * layer.LayoutSize.X]; if (tile.HasSuspendType == SuspendType::None) { return SuspendType::None; } std::int32_t tileId = ResolveTileID(tile); TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr) { return SuspendType::None; } std::uint8_t* mask = tileSet->GetTileMask(tileId); std::int32_t rx = (std::int32_t)x & 31; std::int32_t ry = (std::int32_t)y & 31; if ((tile.Flags & LayerTileFlags::FlipX) == LayerTileFlags::FlipX) { rx = (TileSet::DefaultTileSize - 1 - rx); } if ((tile.Flags & LayerTileFlags::FlipY) == LayerTileFlags::FlipY) { ry = (TileSet::DefaultTileSize - 1 - ry); } std::int32_t top = std::max(ry - Tolerance, 0) << 5; std::int32_t bottom = std::min(ry + Tolerance, TileSet::DefaultTileSize - 1) << 5; for (std::int32_t ti = bottom | rx; ti >= top; ti -= TileSet::DefaultTileSize) { if (mask[ti]) { return tile.HasSuspendType; } } return SuspendType::None; } bool TileMap::AdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount) { Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; LayerTile& tile = _layers[_sprLayerIndex].Layout[tx + ty * layoutSize.X]; return AdvanceDestructibleTileAnimation(tile, tx, ty, amount, {}); } bool TileMap::AdvanceDestructibleTileAnimation(LayerTile& tile, std::int32_t tx, std::int32_t ty, std::int32_t& amount, StringView soundName) { if (amount <= 0) { return false; } if (tile.DestructAnimation >= _animatedTilesOffset) { AnimatedTile& anim = _animatedTiles[tile.DestructAnimation - _animatedTilesOffset]; std::int32_t max = (std::int32_t)anim.Tiles.size() - 2; if (tile.DestructFrameIndex < max) { // Tile not destroyed yet, advance counter by one std::int32_t frameCount = std::min(amount, max - tile.DestructFrameIndex); tile.DestructFrameIndex += frameCount; tile.TileID = anim.Tiles[tile.DestructFrameIndex].TileID; if (tile.DestructFrameIndex >= max) { if (!soundName.empty()) { _owner->PlayCommonSfx(soundName, Vector3f(tx * TileSet::DefaultTileSize + (TileSet::DefaultTileSize / 2), ty * TileSet::DefaultTileSize + (TileSet::DefaultTileSize / 2), 0.0f), 1.0f, Random().FastFloat(0.9f, 1.1f)); } CreateTileDebris(anim.Tiles[anim.Tiles.size() - 1].TileID, tx, ty); } amount -= frameCount; _owner->OnAdvanceDestructibleTileAnimation(tx, ty, frameCount); return true; } } else { if (tile.DestructFrameIndex == 0) { std::int32_t frameCount = 1; tile.DestructFrameIndex += frameCount; tile.TileID = 0; // Set to empty tile if (!soundName.empty()) { _owner->PlayCommonSfx(soundName, Vector3f(tx * TileSet::DefaultTileSize + (TileSet::DefaultTileSize / 2), ty * TileSet::DefaultTileSize + (TileSet::DefaultTileSize / 2), 0.0f), 1.0f, Random().FastFloat(0.9f, 1.1f)); } CreateTileDebris(tile.DestructAnimation, tx, ty); amount -= frameCount; _owner->OnAdvanceDestructibleTileAnimation(tx, ty, frameCount); return true; } } return false; } void TileMap::AdvanceCollapsingTileTimers(float timeMult) { ZoneScopedC(0xA09359); _collapsingTimer -= timeMult; if (_collapsingTimer > 0.0f) { return; } _collapsingTimer = 1.0f; const Vector2i& layoutSize = _layers[_sprLayerIndex].LayoutSize; auto it = _activeCollapsingTiles.begin(); while (it != _activeCollapsingTiles.end()) { Vector2i tilePos = *it; auto& tile = _layers[_sprLayerIndex].Layout[tilePos.X + tilePos.Y * layoutSize.X]; if (tile.TileParams == 0) { std::int32_t amount = 1; if (!AdvanceDestructibleTileAnimation(tile, tilePos.X, tilePos.Y, amount, "SceneryCollapse"_s)) { tile.DestructType = TileDestructType::None; it = _activeCollapsingTiles.eraseUnordered(it); continue; } else { tile.TileParams = 4; } } else { tile.TileParams--; } ++it; } } void TileMap::DrawLayer(RenderQueue& renderQueue, TileMapLayer& layer, const Rectf& cullingRect, Vector2f viewCenter) { ZoneScopedNC("Layer", 0xA09359); if (!layer.Visible) { return; } Vector2i tileCount = layer.LayoutSize; // Get current layer offsets and speeds float loX = layer.Description.OffsetX; float loY = layer.Description.OffsetY - (layer.Description.UseInherentOffset ? (cullingRect.H - 200) / 2 : 0) + 1; // Find out coordinates for a tile from outside the boundaries from topleft corner of the screen float x1 = cullingRect.X - HardcodedOffset; float y1 = cullingRect.Y - HardcodedOffset; if (layer.Description.RendererType >= LayerRendererType::Sky && layer.Description.RendererType <= LayerRendererType::Circle && tileCount.Y == 8 && tileCount.X == 8) { constexpr float PerspectiveSpeedX = 0.4f; constexpr float PerspectiveSpeedY = 0.16f; RenderTexturedBackground(renderQueue, cullingRect, viewCenter, layer, x1 * PerspectiveSpeedX + loX, y1 * PerspectiveSpeedY + loY); } else { float xt, yt; switch (layer.Description.SpeedModelX) { case LayerSpeedModel::AlwaysOnTop: xt = -HardcodedOffset; break; case LayerSpeedModel::FitLevel: { float progress = (float)viewCenter.X / (_layers[_sprLayerIndex].LayoutSize.X * TileSet::DefaultTileSize); xt = std::clamp(progress, 0.0f, 1.0f) * ((layer.LayoutSize.X * TileSet::DefaultTileSize) - cullingRect.W + HardcodedOffset) + loX; break; } case LayerSpeedModel::SpeedMultipliers: { float progress = (float)viewCenter.X / (_layers[_sprLayerIndex].LayoutSize.X * TileSet::DefaultTileSize); progress = (layer.Description.SpeedX < layer.Description.AutoSpeedX ? std::clamp(progress, layer.Description.SpeedX, layer.Description.AutoSpeedX) : (layer.Description.SpeedX + layer.Description.AutoSpeedX) * 0.5f); xt = progress * ((layer.LayoutSize.X * TileSet::DefaultTileSize) - HardcodedOffset) + loX; break; } default: xt = TranslateCoordinate(x1, layer.Description.SpeedX, loX, cullingRect.W, false); break; } switch (layer.Description.SpeedModelY) { case LayerSpeedModel::AlwaysOnTop: yt = -HardcodedOffset; break; case LayerSpeedModel::FitLevel: { float progress = (float)viewCenter.Y / (_layers[_sprLayerIndex].LayoutSize.Y * TileSet::DefaultTileSize); yt = std::clamp(progress, 0.0f, 1.0f) * ((layer.LayoutSize.Y * TileSet::DefaultTileSize) - cullingRect.H + HardcodedOffset) + loY; break; } case LayerSpeedModel::SpeedMultipliers: { float progress = (float)viewCenter.Y / (_layers[_sprLayerIndex].LayoutSize.Y * TileSet::DefaultTileSize); progress = (layer.Description.SpeedY < layer.Description.AutoSpeedY ? std::clamp(progress, layer.Description.SpeedY, layer.Description.AutoSpeedY) : (layer.Description.SpeedY + layer.Description.AutoSpeedY) * 0.5f); yt = progress * ((layer.LayoutSize.Y * TileSet::DefaultTileSize) - HardcodedOffset) + loY; break; } default: // TODO: Some levels looks better with these adjustments /*if (speedY < 1.0f) { speedY = powf(speedY, 1.06f); } else if (speedY > 1.0f) { speedY = powf(speedY, 0.996f); }*/ yt = TranslateCoordinate(y1, layer.Description.SpeedY, loY, cullingRect.H, true); break; } // Calculate the index (on the layer map) of the first tile that needs to be drawn to the position determined earlier std::int32_t tileX, tileY, tileAbsX, tileAbsY; // Get the actual tile coords on the layer layout if (xt > 0) { tileAbsX = (std::int32_t)std::floor(xt / (float)TileSet::DefaultTileSize); tileX = tileAbsX % tileCount.X; } else { tileAbsX = (std::int32_t)std::ceil(xt / (float)TileSet::DefaultTileSize); tileX = tileAbsX % tileCount.X; while (tileX < 0) { tileX += tileCount.X; } } if (yt > 0) { tileAbsY = (std::int32_t)std::floor(yt / (float)TileSet::DefaultTileSize); tileY = tileAbsY % tileCount.Y; } else { tileAbsY = (std::int32_t)std::ceil(yt / (float)TileSet::DefaultTileSize); tileY = tileAbsY % tileCount.Y; while (tileY < 0) { tileY += tileCount.Y; } } // Update x1 and y1 with the remainder, so that we start at the tile boundary // minus 1, because indices are updated in the beginning of the loops float remX = fmodf(xt, (float)TileSet::DefaultTileSize); float remY = fmodf(yt, (float)TileSet::DefaultTileSize); x1 -= remX - (float)TileSet::DefaultTileSize; y1 -= remY - (float)TileSet::DefaultTileSize; // Save the tile Y at the left border so that we can roll back to it at the start of every row iteration std::int32_t tileYs = tileY; // Calculate the last coordinates we want to draw to float x3 = x1 + (TileSet::DefaultTileSize * 2) + cullingRect.W; float y3 = y1 + (TileSet::DefaultTileSize * 2) + cullingRect.H; std::int32_t tile_xo = -1; for (float x2 = x1; x2 <= x3; x2 += TileSet::DefaultTileSize) { tileX = (tileX + 1) % tileCount.X; tile_xo++; if (!layer.Description.RepeatX) { // If the current tile isn't in the first iteration of the layer horizontally, don't draw this column if (tileAbsX + tile_xo + 1 < 0 || tileAbsX + tile_xo + 1 >= tileCount.X) { continue; } } tileY = tileYs; std::int32_t tile_yo = -1; for (float y2 = y1; y2 <= y3; y2 += TileSet::DefaultTileSize) { tileY = (tileY + 1) % tileCount.Y; tile_yo++; LayerTile tile = layer.Layout[tileX + tileY * layer.LayoutSize.X]; if (!layer.Description.RepeatY) { // If the current tile isn't in the first iteration of the layer vertically, don't draw it if (tileAbsY + tile_yo + 1 < 0 || tileAbsY + tile_yo + 1 >= tileCount.Y) { continue; } } std::int32_t tileId = ResolveTileID(tile); if (tileId == 0 || tile.Alpha == 0) { continue; } TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr) { continue; } auto command = RentRenderCommand(layer.Description.RendererType); command->SetType(RenderCommand::Type::TileMap); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); Vector2i texSize = tileSet->TextureDiffuse->GetSize(); float texScaleX = TileSet::DefaultTileSize / float(texSize.X); float texBiasX = ((tileId % tileSet->TilesPerRow) * (TileSet::DefaultTileSize + 2.0f) + 1.0f) / float(texSize.X); float texScaleY = TileSet::DefaultTileSize / float(texSize.Y); float texBiasY = ((tileId / tileSet->TilesPerRow) * (TileSet::DefaultTileSize + 2.0f) + 1.0f) / float(texSize.Y); // ToDo: Flip normal map somehow if ((tile.Flags & LayerTileFlags::FlipX) == LayerTileFlags::FlipX) { texBiasX += texScaleX; texScaleX *= -1; } if ((tile.Flags & LayerTileFlags::FlipY) == LayerTileFlags::FlipY) { texBiasY += texScaleY; texScaleY *= -1; } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(TileSet::DefaultTileSize, TileSet::DefaultTileSize); Vector4f color = layer.Description.Color; color.W *= tile.Alpha / 255.0f; instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(color.Data()); float x2r = x2, y2r = y2; if (!PreferencesCache::UnalignedViewport) { x2r = std::floor(x2r); y2r = std::floor(y2r); } command->SetTransformation(Matrix4x4f::Translation(x2r, y2r, 0.0f)); command->SetLayer(layer.Description.Depth); command->GetMaterial().SetTexture(*tileSet->TextureDiffuse); renderQueue.AddCommand(command); } } } } float TileMap::TranslateCoordinate(float coordinate, float speed, float offset, std::int32_t viewSize, bool isY) { std::int32_t alignment = ((isY ? (viewSize - 200) : (viewSize - 320)) / 2) + HardcodedOffset; return (coordinate * speed + offset + alignment * (speed - 1.0f)); } RenderCommand* TileMap::RentRenderCommand(LayerRendererType type) { RenderCommand* command; if (_renderCommandsCount < _renderCommands.size()) { command = _renderCommands[_renderCommandsCount].get(); _renderCommandsCount++; } else { command = _renderCommands.emplace_back(std::make_unique()).get(); _renderCommandsCount++; command->GetMaterial().SetBlendingEnabled(true); } bool shaderChanged; switch (type) { case LayerRendererType::Solid: shaderChanged = command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::SpriteNoTexture); break; case LayerRendererType::Tinted: shaderChanged = command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::Tinted)); break; case LayerRendererType::Sky: shaderChanged = command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PreferencesCache::BackgroundDithering ? PrecompiledShader::TexturedBackgroundDither : PrecompiledShader::TexturedBackground)); break; case LayerRendererType::Circle: shaderChanged = command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PreferencesCache::BackgroundDithering ? PrecompiledShader::TexturedBackgroundCircleDither : PrecompiledShader::TexturedBackgroundCircle)); break; default: shaderChanged = command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); break; } if (shaderChanged) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } return command; } void TileMap::AddTileSet(StringView tileSetPath, std::uint16_t offset, std::uint16_t count, const std::uint8_t* paletteRemapping) { auto& tileSetPart = _tileSets.emplace_back(); tileSetPart.Data = ContentResolver::Get().RequestTileSet(tileSetPath, 0, false, paletteRemapping); tileSetPart.Offset = offset; tileSetPart.Count = count; if (tileSetPart.Data == nullptr) { LOGE("Cannot load extra tileset \"{}\"", tileSetPath); } } void TileMap::ReadLayerConfiguration(Stream& s) { LayerType layerType = (LayerType)s.ReadValue(); std::uint16_t layerFlags = s.ReadValueAsLE(); if (layerType == LayerType::Sprite) { _sprLayerIndex = (std::int32_t)_layers.size(); } TileMapLayer& newLayer = _layers.emplace_back(); std::int32_t width = s.ReadValueAsLE(); std::int32_t height = s.ReadValueAsLE(); newLayer.LayoutSize = Vector2i(width, height); newLayer.Visible = ((layerFlags & 0x08) == 0x08); if (layerType != LayerType::Sprite) { std::uint8_t combinedSpeedModels = s.ReadValue(); newLayer.Description.SpeedModelX = (LayerSpeedModel)(combinedSpeedModels & 0x0f); newLayer.Description.SpeedModelY = (LayerSpeedModel)((combinedSpeedModels >> 4) & 0x0f); newLayer.Description.OffsetX = s.ReadValueAsLE(); newLayer.Description.OffsetY = s.ReadValueAsLE(); newLayer.Description.SpeedX = s.ReadValueAsLE(); newLayer.Description.SpeedY = s.ReadValueAsLE(); newLayer.Description.AutoSpeedX = s.ReadValueAsLE(); newLayer.Description.AutoSpeedY = s.ReadValueAsLE(); newLayer.Description.RepeatX = ((layerFlags & 0x01) == 0x01); newLayer.Description.RepeatY = ((layerFlags & 0x02) == 0x02); std::int16_t depth = s.ReadValueAsLE(); newLayer.Description.Depth = (std::uint16_t)(ILevelHandler::MainPlaneZ - depth); newLayer.Description.UseInherentOffset = ((layerFlags & 0x04) == 0x04); newLayer.Description.RendererType = (LayerRendererType)s.ReadValue(); std::uint8_t r = s.ReadValue(); std::uint8_t g = s.ReadValue(); std::uint8_t b = s.ReadValue(); std::uint8_t a = s.ReadValue(); if (newLayer.Description.RendererType == LayerRendererType::Tinted) { // TODO: Tinted color is precomputed from palette here const std::uint32_t* palettes = ContentResolver::Get().GetPalettes(); std::uint32_t color = palettes[r]; newLayer.Description.Color = Vector4f((color & 0x000000ff) / 255.0f, ((color >> 8) & 0x000000ff) / 255.0f, ((color >> 16) & 0x000000ff) / 255.0f, a * ((color >> 24) & 0x000000ff) / (255.0f * 255.0f)); } else { newLayer.Description.Color = Vector4f(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); if (newLayer.Description.RendererType >= LayerRendererType::Sky) { _texturedBackgroundLayer = (std::int32_t)_layers.size() - 1; } } } else { newLayer.Description.OffsetX = 0.0f; newLayer.Description.OffsetY = 0.0f; newLayer.Description.SpeedX = 1.0f; newLayer.Description.SpeedY = 1.0f; newLayer.Description.AutoSpeedX = 0.0f; newLayer.Description.AutoSpeedY = 0.0f; newLayer.Description.RepeatX = false; newLayer.Description.RepeatY = false; newLayer.Description.Depth = (std::uint16_t)(ILevelHandler::MainPlaneZ - 50); newLayer.Description.UseInherentOffset = false; newLayer.Description.SpeedModelX = LayerSpeedModel::Default; newLayer.Description.SpeedModelY = LayerSpeedModel::Default; newLayer.Description.RendererType = LayerRendererType::Default; newLayer.Description.Color = Vector4f(1.0f, 1.0f, 1.0f, 1.0f); } newLayer.Layout = std::make_unique(width * height); for (std::int32_t i = 0; i < (width * height); i++) { std::uint8_t tileFlags = s.ReadValue(); std::uint16_t tileIdx = s.ReadValueAsLE(); std::uint8_t tileModifier = (std::uint8_t)(tileFlags >> 4); LayerTile& tile = newLayer.Layout[i]; tile.TileID = tileIdx; tile.DestructAnimation = -1; tile.Flags = (LayerTileFlags)(tileFlags & 0x0f); if (tileModifier == 1 /*Translucent*/) { tile.Alpha = 192; } else if (tileModifier == 2 /*Invisible*/) { tile.Alpha = 0; } else { tile.Alpha = 255; } } } void TileMap::ReadAnimatedTiles(Stream& s) { _animatedTilesOffset = s.ReadValueAsLE(); std::int32_t count = s.ReadValueAsLE(); _animatedTiles.reserve(count); for (std::int32_t i = 0; i < count; i++) { std::uint8_t frameCount = s.ReadValue(); if (frameCount == 0) { continue; } AnimatedTile& animTile = _animatedTiles.emplace_back(); // FrameDuration is multiplied by 16 before saving, so divide it here back animTile.FrameDuration = s.ReadValueAsLE() / 16.0f; animTile.Delay = s.ReadValueAsLE(); animTile.DelayJitter = s.ReadValueAsLE(); animTile.IsPingPong = s.ReadValue(); animTile.PingPongDelay = s.ReadValueAsLE(); for (std::int32_t j = 0; j < frameCount; j++) { auto& frame = animTile.Tiles.emplace_back(); // TODO: flags /*std::uint8_t flag =*/ s.ReadValue(); frame.TileID = s.ReadValueAsLE(); } } } void TileMap::SetTileEventFlags(std::int32_t x, std::int32_t y, EventType tileEvent, std::uint8_t* tileParams) { auto& tile = _layers[_sprLayerIndex].Layout[x + y * _layers[_sprLayerIndex].LayoutSize.X]; switch (tileEvent) { case EventType::ModifierOneWay: tile.Flags |= LayerTileFlags::OneWay; break; case EventType::ModifierVine: tile.HasSuspendType = SuspendType::Vine; break; case EventType::ModifierHook: tile.HasSuspendType = SuspendType::Hook; break; case EventType::SceneryDestruct: SetTileDestructibleEventParams(tile, TileDestructType::Weapon, tileParams[0] | (tileParams[1] << 8)); break; case EventType::SceneryDestructButtstomp: SetTileDestructibleEventParams(tile, TileDestructType::Special, tileParams[0]); break; case EventType::TriggerArea: SetTileDestructibleEventParams(tile, TileDestructType::Trigger, tileParams[0]); break; case EventType::SceneryDestructSpeed: SetTileDestructibleEventParams(tile, TileDestructType::Speed, tileParams[0]); break; case EventType::SceneryCollapse: // TODO: Framerate (tileParams[1]) not used SetTileDestructibleEventParams(tile, TileDestructType::Collapse, tileParams[0]); break; } } /** @brief Overrides the diffuse texture of the specified tile */ bool TileMap::OverrideTileDiffuse(std::int32_t tileId, StaticArrayView<(TileSet::DefaultTileSize + 2) * (TileSet::DefaultTileSize + 2), std::uint32_t> tileDiffuse) { TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr) { return false; } return tileSet->OverrideTileDiffuse(tileId, tileDiffuse); } /** @brief Overrides the collision mask of the specified tile */ bool TileMap::OverrideTileMask(std::int32_t tileId, StaticArrayView tileMask) { TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr) { return false; } return tileSet->OverrideTileMask(tileId, tileMask); } void TileMap::SetTileDestructibleEventParams(LayerTile& tile, TileDestructType type, std::uint16_t tileParams) { tile.DestructType = type; tile.DestructAnimation = tile.TileID; if (tile.TileID >= _animatedTilesOffset) { tile.TileID = _animatedTiles[tile.DestructAnimation - _animatedTilesOffset].Tiles[0].TileID; } tile.TileParams = tileParams; tile.DestructFrameIndex = 0; } std::int32_t TileMap::GetTileDestructibleFrameCount(const LayerTile& tile) { if (tile.DestructAnimation >= _animatedTilesOffset) { return (std::int32_t)_animatedTiles[tile.DestructAnimation - _animatedTilesOffset].Tiles.size() - 2; } return 1; } Array TileMap::GetUsedTileSetPaths() const { Array result; arrayReserve(result, _tileSets.size()); for (const auto& tileSetPart : _tileSets) { if (tileSetPart.Data != nullptr && !tileSetPart.Data->FilePath.empty()) { arrayAppend(result, tileSetPart.Data->FilePath); } } return result; } void TileMap::CreateDebris(const DestructibleDebris& debris) { auto& spriteLayer = _layers[_sprLayerIndex]; if ((debris.Flags & DebrisFlags::Disappear) == DebrisFlags::Disappear && debris.Depth <= spriteLayer.Description.Depth) { std::int32_t x = (std::int32_t)debris.Pos.X / TileSet::DefaultTileSize; std::int32_t y = (std::int32_t)debris.Pos.Y / TileSet::DefaultTileSize; if (x < 0 || y < 0 || x >= spriteLayer.LayoutSize.X || y >= spriteLayer.LayoutSize.Y) { return; } std::int32_t tileId = ResolveTileID(spriteLayer.Layout[x + y * spriteLayer.LayoutSize.X]); TileSet* tileSet = ResolveTileSet(tileId); if (tileSet != nullptr) { if (tileSet->IsTileFilled(tileId)) { return; } if (_sprLayerIndex + 1 < _layers.size() && _layers[_sprLayerIndex + 1].Description.SpeedX == 1.0f && _layers[_sprLayerIndex + 1].Description.SpeedY == 1.0f) { tileId = ResolveTileID(_layers[_sprLayerIndex + 1].Layout[x + y * spriteLayer.LayoutSize.X]); if (tileSet->IsTileFilled(tileId)) { return; } } } } _debrisList.push_back(debris); } void TileMap::CreateTileDebris(std::int32_t tileId, std::int32_t x, std::int32_t y) { static const float SpeedMultiplier[] = { -2, 2, -1, 1 }; constexpr std::int32_t QuarterSize = TileSet::DefaultTileSize / 2; // Tile #0 is always empty if (tileId == 0) { return; } TileSet* tileSet = ResolveTileSet(tileId); if (tileSet == nullptr || tileSet->TextureDiffuse == nullptr) { return; } std::uint16_t z = _layers[_sprLayerIndex].Description.Depth + 80; Vector2i texSize = tileSet->TextureDiffuse->GetSize(); float texScaleX = float(QuarterSize) / float(texSize.X); float texBiasX = ((tileId % tileSet->TilesPerRow) * (TileSet::DefaultTileSize + 2.0f) + 1.0f) / float(texSize.X); float texScaleY = float(QuarterSize) / float(texSize.Y); float texBiasY = ((tileId / tileSet->TilesPerRow) * (TileSet::DefaultTileSize + 2.0f) + 1.0f) / float(texSize.Y); // TODO: Implement flip here /*if (isFlippedX) { texBiasX += texScaleX; texScaleX *= -1; } if (isFlippedY) { texBiasY += texScaleY; texScaleY *= -1; }*/ for (std::int32_t i = 0; i < 4; i++) { DestructibleDebris& debris = _debrisList.emplace_back(); debris.Pos = Vector2f(x * TileSet::DefaultTileSize + (i % 2) * QuarterSize, y * TileSet::DefaultTileSize + (i / 2) * QuarterSize); debris.Depth = z; debris.Size = Vector2f(QuarterSize, QuarterSize); debris.Speed = Vector2f(SpeedMultiplier[i] * Random().FastFloat(0.8f, 1.2f), -4.0f * Random().FastFloat(0.8f, 1.2f)); debris.Acceleration = Vector2f(0.0f, 0.3f); debris.Scale = 1.0f; debris.ScaleSpeed = Random().FastFloat(-0.01f, -0.002f); debris.Angle = 0.0f; debris.AngleSpeed = SpeedMultiplier[i] * Random().FastFloat(0.0f, 0.014f); debris.Alpha = 1.0f; debris.AlphaSpeed = -0.01f; debris.Time = 120.0f; debris.TexScaleX = texScaleX; debris.TexBiasX = texBiasX + ((i % 2) * QuarterSize / float(texSize.X)); debris.TexScaleY = texScaleY; debris.TexBiasY = texBiasY + ((i / 2) * QuarterSize / float(texSize.Y)); debris.DiffuseTexture = tileSet->TextureDiffuse.get(); debris.Flags = DebrisFlags::None; } } void TileMap::CreateParticleDebris(const GraphicResource* res, Vector3f pos, Vector2f force, std::int32_t currentFrame, bool isFacingLeft) { constexpr std::int32_t DebrisSize = 3; if (res->Base->TextureDiffuse == nullptr) { return; } float x = pos.X - res->Base->Hotspot.X; float y = pos.Y - res->Base->Hotspot.Y; Vector2i texSize = res->Base->TextureDiffuse->GetSize(); for (std::int32_t fy = 0; fy < res->Base->FrameDimensions.Y; fy += DebrisSize + 1) { for (std::int32_t fx = 0; fx < res->Base->FrameDimensions.X; fx += DebrisSize + 1) { float currentSize = DebrisSize * Random().FastFloat(0.2f, 1.1f); DestructibleDebris& debris = _debrisList.emplace_back(); debris.Pos = Vector2f(x + (isFacingLeft ? res->Base->FrameDimensions.X - fx : fx), y + fy); debris.Depth = (std::uint16_t)pos.Z; debris.Size = Vector2f(currentSize, currentSize); debris.Speed = Vector2f(force.X + ((fx - res->Base->FrameDimensions.X / 2) + Random().FastFloat(-2.0f, 2.0f)) * (isFacingLeft ? -1.0f : 1.0f) * Random().FastFloat(2.0f, 8.0f) / res->Base->FrameDimensions.X, force.Y - 1.0f * Random().FastFloat(2.2f, 4.0f)); debris.Acceleration = Vector2f(0.0f, 0.2f); debris.Scale = 1.0f; debris.ScaleSpeed = 0.0f; debris.Angle = 0.0f; debris.AngleSpeed = 0.0f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.002f; debris.Time = 320.0f; debris.TexScaleX = (currentSize / float(texSize.X)); debris.TexBiasX = (((float)(currentFrame % res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.X) + ((float)fx / float(texSize.X))); debris.TexScaleY = (currentSize / float(texSize.Y)); debris.TexBiasY = (((float)(currentFrame / res->Base->FrameConfiguration.X) / res->Base->FrameConfiguration.Y) + ((float)fy / float(texSize.Y))); debris.DiffuseTexture = res->Base->TextureDiffuse.get(); debris.Flags = DebrisFlags::Bounce; } } } void TileMap::CreateSpriteDebris(const GraphicResource* res, Vector3f pos, std::int32_t count) { if (res->Base->TextureDiffuse == nullptr) { return; } float x = pos.X - res->Base->Hotspot.X; float y = pos.Y - res->Base->Hotspot.Y; Vector2i texSize = res->Base->TextureDiffuse->GetSize(); for (std::int32_t i = 0; i < count; i++) { float speedX = Random().FastFloat(-1.0f, 1.0f) * Random().FastFloat(0.2f, 0.8f) * count; DestructibleDebris& debris = _debrisList.emplace_back(); debris.Pos = Vector2f(x, y); debris.Depth = (std::uint16_t)pos.Z; debris.Size = Vector2f((float)res->Base->FrameDimensions.X, (float)res->Base->FrameDimensions.Y); debris.Speed = Vector2f(speedX, -1.0f * Random().FastFloat(2.2f, 4.0f)); debris.Acceleration = Vector2f(0.0f, 0.2f); debris.Scale = 1.0f; debris.ScaleSpeed = -0.002f; debris.Angle = Random().FastFloat(0.0f, fTwoPi); debris.AngleSpeed = speedX * 0.02f; debris.Alpha = 1.0f; debris.AlphaSpeed = -0.002f; debris.Time = 560.0f; std::int32_t curAnimFrame = res->FrameOffset + Random().Next(0, res->FrameCount); std::int32_t col = curAnimFrame % res->Base->FrameConfiguration.X; std::int32_t row = curAnimFrame / res->Base->FrameConfiguration.X; debris.TexScaleX = (float(res->Base->FrameDimensions.X) / float(texSize.X)); debris.TexBiasX = (float(res->Base->FrameDimensions.X * col) / float(texSize.X)); debris.TexScaleY = (float(res->Base->FrameDimensions.Y) / float(texSize.Y)); debris.TexBiasY = (float(res->Base->FrameDimensions.Y * row) / float(texSize.Y)); debris.DiffuseTexture = res->Base->TextureDiffuse.get(); debris.Flags = DebrisFlags::Bounce; } } void TileMap::UpdateDebris(float timeMult) { ZoneScopedC(0xA09359); std::int32_t size = (std::int32_t)_debrisList.size(); for (std::int32_t i = 0; i < size; i++) { DestructibleDebris& debris = _debrisList[i]; if (debris.Scale <= 0.0f || debris.Alpha <= 0.0f) { std::swap(debris, _debrisList[size - 1]); _debrisList.pop_back(); i--; size--; continue; } debris.Time -= timeMult; if (debris.Time <= 0.0f) { debris.Alpha = -std::min(0.02f, debris.Alpha); } if ((debris.Flags & (DebrisFlags::Disappear | DebrisFlags::Bounce)) != DebrisFlags::None) { // Debris should collide with tilemap float nx = debris.Pos.X + debris.Speed.X * timeMult; float ny = debris.Pos.Y + debris.Speed.Y * timeMult; AABB aabb = AABBf(nx - 1, ny - 1, nx + 1, ny + 1); TileCollisionParams params = { TileDestructType::None, true }; if (IsTileEmpty(aabb, params)) { // Nothing... } else if ((debris.Flags & DebrisFlags::Disappear) == DebrisFlags::Disappear) { debris.ScaleSpeed = -0.02f; debris.AlphaSpeed = -0.006f; debris.Speed = Vector2f::Zero; debris.Acceleration = Vector2f::Zero; } else { // Place us to the ground only if no horizontal movement was // involved (this prevents speeds resetting if the actor // collides with a wall from the side while in the air) aabb.T = debris.Pos.Y - 1; aabb.B = debris.Pos.Y + 1; if (IsTileEmpty(aabb, params)) { if (debris.Speed.Y > 0.0f) { debris.Speed.Y = -(0.8f/*elasticity*/ * debris.Speed.Y); //OnHitFloorHook(); } else { debris.Speed.Y = 0; //OnHitCeilingHook(); } } // If the actor didn't move all the way horizontally, // it hit a wall (or was already touching it) aabb = AABBf(debris.Pos.X - 1, ny - 1, debris.Pos.X + 1, ny + 1); if (IsTileEmpty(aabb, params)) { debris.Speed.X = -(0.8f/*elasticity*/ * debris.Speed.X); debris.AngleSpeed = -(0.8f/*elasticity*/ * debris.AngleSpeed); //OnHitWallHook(); } } } debris.Pos.X += debris.Speed.X * timeMult + 0.5f * debris.Acceleration.X * timeMult * timeMult; debris.Pos.Y += debris.Speed.Y * timeMult + 0.5f * debris.Acceleration.Y * timeMult * timeMult; if (debris.Acceleration.X != 0.0f) { debris.Speed.X = std::min(debris.Speed.X + debris.Acceleration.X * timeMult, 10.0f); } if (debris.Acceleration.Y != 0.0f) { debris.Speed.Y = std::min(debris.Speed.Y + debris.Acceleration.Y * timeMult, 10.0f); } debris.Scale += debris.ScaleSpeed * timeMult; debris.Angle += debris.AngleSpeed * timeMult; debris.Alpha += debris.AlphaSpeed * timeMult; } } void TileMap::DrawDebris(RenderQueue& renderQueue) { ZoneScopedNC("Debris", 0xA09359); constexpr float MaxDebrisSize = 128.0f; Rectf viewportRect = RenderResources::GetCurrentViewport()->GetCullingRect(); viewportRect.X -= MaxDebrisSize; viewportRect.Y -= MaxDebrisSize; viewportRect.W += MaxDebrisSize * 2.0f; viewportRect.H += MaxDebrisSize * 2.0f; for (const auto& debris : _debrisList) { if (!viewportRect.Contains(debris.Pos)) { continue; } auto command = RentRenderCommand(LayerRendererType::Default); command->SetType(RenderCommand::Type::Particle); if ((debris.Flags & DebrisFlags::AdditiveBlending) == DebrisFlags::AdditiveBlending) { command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE); } else { command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(debris.TexScaleX, debris.TexBiasX, debris.TexScaleY, debris.TexBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(debris.Size.X, debris.Size.Y); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf(1.0f, 1.0f, 1.0f, debris.Alpha).Data()); Matrix4x4f worldMatrix = Matrix4x4f::Translation(debris.Pos.X, debris.Pos.Y, 0.0f); worldMatrix.RotateZ(debris.Angle); worldMatrix.Scale(debris.Scale, debris.Scale, 1.0f); worldMatrix.Translate(debris.Size.X * -0.5f, debris.Size.Y * -0.5f, 0.0f); command->SetTransformation(worldMatrix); command->SetLayer(debris.Depth); command->GetMaterial().SetTexture(*debris.DiffuseTexture); renderQueue.AddCommand(command); } } bool TileMap::GetTrigger(std::uint8_t triggerId) { return _triggerState[triggerId]; } void TileMap::SetTrigger(std::uint8_t triggerId, bool newState) { if (_triggerState[triggerId] == newState) { return; } _triggerState.set(triggerId, newState); // Go through all tiles and update any that are influenced by this trigger Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; std::int32_t n = layoutSize.X * layoutSize.Y; for (std::int32_t i = 0; i < n; i++) { LayerTile& tile = _layers[_sprLayerIndex].Layout[i]; if (tile.DestructType == TileDestructType::Trigger && tile.TileParams == triggerId) { if (tile.DestructAnimation >= _animatedTilesOffset) { if (_animatedTiles[tile.DestructAnimation - _animatedTilesOffset].Tiles.size() > 1) { tile.DestructFrameIndex = (newState ? 1 : 0); tile.TileID = _animatedTiles[tile.DestructAnimation - _animatedTilesOffset].Tiles[tile.DestructFrameIndex].TileID; } } else { tile.DestructFrameIndex = (newState ? 1 : 0); tile.TileID = (newState ? 0 /*Empty*/ : tile.DestructAnimation); } } } } void TileMap::CreateCheckpointForRollback() { Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; if (_sprLayerForRollback == nullptr) { _sprLayerForRollback = std::make_unique(layoutSize.X * layoutSize.Y); } std::memcpy(_sprLayerForRollback.get(), _layers[_sprLayerIndex].Layout.get(), layoutSize.X * layoutSize.Y * sizeof(LayerTile)); std::memcpy(_triggerStateForRollback.data(), _triggerState.data(), _triggerState.sizeInBytes()); } void TileMap::RollbackToCheckpoint() { if (_sprLayerForRollback == nullptr) { return; } Vector2i layoutSize = _layers[_sprLayerIndex].LayoutSize; std::memcpy(_layers[_sprLayerIndex].Layout.get(), _sprLayerForRollback.get(), layoutSize.X * layoutSize.Y * sizeof(LayerTile)); std::memcpy(_triggerState.data(), _triggerStateForRollback.data(), _triggerState.sizeInBytes()); } void TileMap::InitializeFromStream(Stream& src) { std::int32_t layoutSize = src.ReadVariableInt32(); if (layoutSize == -1) { return; } DEATH_ASSERT(_sprLayerIndex != -1, "Sprite layer not defined", ); auto& spriteLayer = _layers[_sprLayerIndex]; std::int32_t realLayoutSize = spriteLayer.LayoutSize.X * spriteLayer.LayoutSize.Y; DEATH_ASSERT(layoutSize == realLayoutSize, "Layout size mismatch", ); for (std::int32_t i = 0; i < layoutSize; i++) { auto& tile = spriteLayer.Layout[i]; tile.DestructFrameIndex = src.ReadVariableInt32(); if (tile.DestructAnimation >= 0) { if (tile.DestructAnimation >= _animatedTilesOffset) { if (tile.DestructAnimation - _animatedTilesOffset < (std::int32_t)_animatedTiles.size()) { auto& anim = _animatedTiles[tile.DestructAnimation - _animatedTilesOffset]; std::int32_t max = (std::int32_t)anim.Tiles.size() - 2; if (tile.DestructFrameIndex > max) { LOGW("Serialized tile {} with animation frame {} is out of range", i, tile.DestructFrameIndex); tile.DestructFrameIndex = max; } if (tile.DestructFrameIndex < 0) { LOGW("Serialized tile {} with animation frame {} is out of range", i, tile.DestructFrameIndex); tile.DestructFrameIndex = 0; } tile.TileID = anim.Tiles[tile.DestructFrameIndex].TileID; } else { LOGW("Invalid animated tile ID {}", tile.DestructAnimation); } } else { if (tile.DestructFrameIndex >= 1) { tile.DestructFrameIndex = 1; tile.TileID = 0; // Empty tile } } } } src.Read(_triggerState.data(), _triggerState.sizeInBytes()); } void TileMap::SerializeResumableToStream(Stream& dest, bool fromCheckpoint) { if (_sprLayerIndex == -1) { dest.WriteValue(-1); return; } auto& spriteLayer = _layers[_sprLayerIndex]; std::int32_t layoutSize = spriteLayer.LayoutSize.X * spriteLayer.LayoutSize.Y; const LayerTile* source = (fromCheckpoint && _sprLayerForRollback != nullptr ? _sprLayerForRollback.get() : spriteLayer.Layout.get()); dest.WriteVariableInt32(layoutSize); for (std::int32_t i = 0; i < layoutSize; i++) { dest.WriteVariableInt32(source[i].DestructFrameIndex); } if (fromCheckpoint && _sprLayerForRollback != nullptr) { dest.Write(_triggerStateForRollback.data(), _triggerStateForRollback.sizeInBytes()); } else { dest.Write(_triggerState.data(), _triggerState.sizeInBytes()); } } void TileMap::RenderTexturedBackground(RenderQueue& renderQueue, const Rectf& cullingRect, Vector2f viewCenter, TileMapLayer& layer, float x, float y) { auto target = _texturedBackgroundPass._target.get(); if (target == nullptr) { return; } auto* command = RentRenderCommand(layer.Description.RendererType); auto* instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, 1.0f, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue((float)cullingRect.W, (float)cullingRect.H); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf(1.0f, 1.0f, 1.0f, 1.0f).Data()); command->GetMaterial().Uniform("uViewSize")->SetFloatValue((float)cullingRect.W, (float)cullingRect.H); command->GetMaterial().Uniform("uCameraPos")->SetFloatVector(viewCenter.Data()); command->GetMaterial().Uniform("uShift")->SetFloatValue(x, y); command->GetMaterial().Uniform("uHorizonColor")->SetFloatVector(layer.Description.Color.Data()); command->SetTransformation(Matrix4x4f::Translation(cullingRect.X, cullingRect.Y, 0.0f)); command->SetLayer(layer.Description.Depth); command->GetMaterial().SetTexture(*target); renderQueue.AddCommand(command); } void TileMap::OnInitializeViewport() { if (_texturedBackgroundLayer != -1) { _texturedBackgroundPass.Initialize(); } } TileSet* TileMap::ResolveTileSet(std::int32_t& tileId) { for (auto& tileSetPart : _tileSets) { if (tileId < tileSetPart.Count) { tileId += tileSetPart.Offset; return tileSetPart.Data.get(); } tileId -= tileSetPart.Count; } return nullptr; } std::int32_t TileMap::ResolveTileID(LayerTile& tile) { std::int32_t tileId = tile.TileID; if (tileId >= _animatedTilesOffset) { tileId -= _animatedTilesOffset; if (tileId >= (std::int32_t)_animatedTiles.size()) { return 0; } auto& animTile = _animatedTiles[tileId]; tileId = animTile.Tiles[animTile.CurrentTileIdx].TileID; } return tileId; } void TileMap::TexturedBackgroundPass::Initialize() { bool notInitialized = (_view == nullptr); if (notInitialized) { Vector2i layoutSize = _owner->_layers[_owner->_texturedBackgroundLayer].LayoutSize; std::int32_t width = layoutSize.X * TileSet::DefaultTileSize; std::int32_t height = layoutSize.Y * TileSet::DefaultTileSize; _camera = std::make_unique(); _camera->SetOrthoProjection(0.0f, (float)width, 0.0f, (float)height); _camera->SetView(0, 0, 0, 1); _target = std::make_unique(nullptr, Texture::Format::RGB8, width, height); _view = std::make_unique(_target.get(), Viewport::DepthStencilFormat::None); _view->SetRootNode(this); _view->SetCamera(_camera.get()); //_view->setClearMode(Viewport::ClearMode::Never); _target->SetMagFiltering(SamplerFilter::Linear); _target->SetWrap(SamplerWrapping::Repeat); // Prepare render commands std::int32_t renderCommandCount = (width * height) / (TileSet::DefaultTileSize * TileSet::DefaultTileSize); _renderCommands.reserve(renderCommandCount); for (std::int32_t i = 0; i < renderCommandCount; i++) { std::unique_ptr& command = _renderCommands.emplace_back(std::make_unique()); command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } } Viewport::GetChain().push_back(_view.get()); } bool TileMap::TexturedBackgroundPass::OnDraw(RenderQueue& renderQueue) { TileMapLayer& layer = _owner->_layers[_owner->_texturedBackgroundLayer]; Vector2i layoutSize = layer.LayoutSize; std::int32_t renderCommandIndex = 0; bool isAnimated = false; for (std::int32_t y = 0; y < layoutSize.Y; y++) { for (std::int32_t x = 0; x < layoutSize.X; x++) { LayerTile& tile = layer.Layout[x + y * layer.LayoutSize.X]; std::int32_t tileId = _owner->ResolveTileID(tile); if (tileId == 0) { continue; } TileSet* tileSet = _owner->ResolveTileSet(tileId); if (tileSet == nullptr) { continue; } auto command = _renderCommands[renderCommandIndex++].get(); Vector2i texSize = tileSet->TextureDiffuse->GetSize(); float texScaleX = TileSet::DefaultTileSize / float(texSize.X); float texBiasX = ((tileId % tileSet->TilesPerRow) * (TileSet::DefaultTileSize + 2.0f) + 1.0f) / float(texSize.X); float texScaleY = TileSet::DefaultTileSize / float(texSize.Y); float texBiasY = ((tileId / tileSet->TilesPerRow) * (TileSet::DefaultTileSize + 2.0f) + 1.0f) / float(texSize.Y); // TODO: Flip normal map somehow if ((tile.Flags & LayerTileFlags::FlipX) == LayerTileFlags::FlipX) { texBiasX += texScaleX; texScaleX *= -1; } if ((tile.Flags & LayerTileFlags::FlipY) == LayerTileFlags::FlipY) { texBiasY += texScaleY; texScaleY *= -1; } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(texScaleX, texBiasX, texScaleY, texBiasY); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(TileSet::DefaultTileSize, TileSet::DefaultTileSize); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); command->SetTransformation(Matrix4x4f::Translation(x * TileSet::DefaultTileSize, y * TileSet::DefaultTileSize, 0.0f)); command->GetMaterial().SetTexture(*tileSet->TextureDiffuse); renderQueue.AddCommand(command); } } if (!isAnimated && _alreadyRendered) { // If it's not animated, it can be rendered only once auto& chain = Viewport::GetChain(); for (std::int32_t i = std::int32_t(chain.size()) - 1; i >= 0; i--) { auto& item = chain[i]; if (item == _view.get()) { chain.erase(&item); break; } } } _alreadyRendered = true; return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/TileMap.h000066400000000000000000000314271512772601700243260ustar00rootroot00000000000000#pragma once #include "ITileMapOwner.h" #include "../ILevelHandler.h" #include "../PitType.h" #include "../SuspendType.h" #include "TileSet.h" #include "../../nCine/Graphics/Camera.h" #include "../../nCine/Graphics/Viewport.h" #include using namespace Death::IO; namespace Jazz2 { class LevelHandler; #if defined(WITH_ANGELSCRIPT) namespace Scripting { class LevelScriptLoader; } #endif } namespace Jazz2::Tiles { /** @brief Layer speed model */ enum class LayerSpeedModel { /** @brief Default model */ Default, /** @brief Ignores all speed and offset settings to be tied to the top/left side of the screen */ AlwaysOnTop, /** @brief Ignores the speed and auto speed properties, and instead ensures that the full extent of this layer will be visible and no blank space outside of it will be shown */ FitLevel, /** @brief Treats the layer's speed and auto speed properties on this axis as multipliers of the current camera size, rather than camera position */ SpeedMultipliers }; /** @brief Layer renderer type */ enum class LayerRendererType { Default, /**< Default rendering */ Solid, /**< Solid color rendering */ Tinted, /**< Color-tinted rendering */ Sky = 10, /**< Textured background --- Sky */ Circle /**< Textured background --- Circle */ }; /** @brief Description of a tile map layer */ struct LayerDescription { /** @brief Layer depth (Z position) */ std::uint16_t Depth; /** @brief Horizontal speed */ float SpeedX; /** @brief Vertical speed */ float SpeedY; /** @brief Horizontal auto speed */ float AutoSpeedX; /** @brief Vertical auto speed */ float AutoSpeedY; /** @brief Horizontal scroll offset */ float OffsetX; /** @brief Vertical scroll offset */ float OffsetY; /** @brief Whether layer should repeat horizontally */ bool RepeatX; /** @brief Whether layer should repeat vertically */ bool RepeatY; /** @brief Whether inherent offset should be used */ bool UseInherentOffset; /** @brief Horizontal speed model */ LayerSpeedModel SpeedModelX; /** @brief Vertical speed model */ LayerSpeedModel SpeedModelY; /** @brief Layer renderer type */ LayerRendererType RendererType; /** @brief Layer color parameter */ Vector4f Color; }; /** @brief Layer tile flags, supports a bitwise combination of its member values */ enum class LayerTileFlags : std::uint8_t { None = 0x00, /**< None */ FlipX = 0x01, /**< Flipped horizontally */ FlipY = 0x02, /**< Flipped vertically */ OneWay = 0x10 /**< One-way collision */ }; DEATH_ENUM_FLAGS(LayerTileFlags); /** @brief Represents a single tile in a tile map layer */ struct LayerTile { /** @brief Tile ID */ std::int32_t TileID; /** @brief Tile parameters */ std::uint16_t TileParams; /** @brief Tile flags */ LayerTileFlags Flags; /** @brief Tile transparency */ std::uint8_t Alpha; /** @brief Suspend type of tile */ SuspendType HasSuspendType; /** @brief Destruct type of tile */ TileDestructType DestructType; /** @brief Animation ID for destructible tile */ std::int32_t DestructAnimation; /** @brief Denotes the specific frame from the above animation that is currently active --- Collapsible: Delay ("wait" parameter); Trigger: Trigger ID */ std::int32_t DestructFrameIndex; }; /** @brief Represents a single tile map layer */ struct TileMapLayer { /** @brief Layer layout */ std::unique_ptr Layout; /** @brief Layer layout size */ Vector2i LayoutSize; /** @brief Layer description */ LayerDescription Description; /** @brief Layer visibility */ bool Visible; }; /** @brief Represents a single frame of an animated tile */ struct AnimatedTileFrame { /** @brief Tile ID */ std::int32_t TileID; }; /** @brief Represents an animated tile */ struct AnimatedTile { /** @brief Individual tiles (frames) */ SmallVector Tiles; /** @brief Fixed number of extra animation frames that will show the last frame */ std::int16_t Delay; /** @brief Maximum random number of extra animation frames that will show the last frame */ std::int16_t DelayJitter; /** @brief Fixed number of extra animation frames that will show the last frame before the animation should start to play backward (if @ref IsPingPong is enabled) */ std::int32_t PingPongDelay; /** @brief Current frame of the animation */ std::int32_t CurrentTileIdx; /** @brief Duration of animation frame */ float FrameDuration; /** @brief Frames left until animation advances */ float FramesLeft; /** @brief Whether animation should play forward and then backward */ bool IsPingPong; /** @brief Whether animation plays forward (if @ref IsPingPong is enabled) */ bool Forwards; }; /** @brief Represents a renderable tile map, consists of multiple layers */ class TileMap : public SceneNode // , public IResumable { #if defined(WITH_ANGELSCRIPT) friend class Scripting::LevelScriptLoader; #endif public: /** @{ @name Constants */ /** @brief Maximum number of triggers */ static constexpr std::int32_t TriggerCount = 32; /** @brief Hardcoded offset for layer positioning */ static constexpr std::int32_t HardcodedOffset = 70; /** @} */ /** @brief Flags that modify behaviour of @ref DestructibleDebris, supports a bitwise combination of its member values */ enum class DebrisFlags { None = 0x00, Disappear = 0x01, Bounce = 0x02, AdditiveBlending = 0x04 }; DEATH_PRIVATE_ENUM_FLAGS(DebrisFlags); /** @brief Describes a visual debris (particle effect) */ struct DestructibleDebris { /** @brief Position */ Vector2f Pos; /** @brief Depth (layer) */ std::uint16_t Depth; /** @brief Size */ Vector2f Size; /** @brief Speed */ Vector2f Speed; /** @brief Acceleration */ Vector2f Acceleration; /** @brief Scale */ float Scale; /** @brief Scale change speed */ float ScaleSpeed; /** @brief Angle */ float Angle; /** @brief Angle change speed */ float AngleSpeed; /** @brief Alpha */ float Alpha; /** @brief Alpha change speed */ float AlphaSpeed; /** @brief Time remaining until disposal */ float Time; /** @brief Texture horizontal scale */ float TexScaleX; /** @brief Texture horizontal bias */ float TexBiasX; /** @brief Texture vertical scale */ float TexScaleY; /** @brief Texture vertical bias */ float TexBiasY; /** @brief Diffuse texture */ Texture* DiffuseTexture; /** @brief Behavior flags */ DebrisFlags Flags; }; TileMap(StringView tileSetPath, std::uint16_t captionTileId, bool applyPalette); ~TileMap(); bool IsValid() const; /** @brief Sets an owner of tile map */ void SetOwner(ITileMapOwner* owner); /** @brief Returns size of tile map in tiles */ Vector2i GetSize() const; /** @brief Returns size of tile map in pixels */ Vector2i GetLevelBounds() const; /** @brief Returns pit type */ PitType GetPitType() const; /** @brief Sets pit type */ void SetPitType(PitType value); void OnUpdate(float timeMult) override; /** @brief Called at the end of each frame */ void OnEndFrame(); bool OnDraw(RenderQueue& renderQueue) override; /** @brief Returns `true` if the mask of a tile on the main (sprite) layer is completely empty */ bool IsTileEmpty(std::int32_t tx, std::int32_t ty); /** @brief Returns `true` if the mask of tiles on the main (sprite) layer intersecting a given AABB is empty */ bool IsTileEmpty(const AABBf& aabb, TileCollisionParams& params); /** @brief Returns `true` if tiles on the main (sprite) layer intersecting a given AABB can be destroyed */ bool CanBeDestroyed(const AABBf& aabb, TileCollisionParams& params); /** @brief Returns suspend state of a given position */ SuspendType GetTileSuspendState(float x, float y); /** @brief Advances descructible animation of a given tile */ bool AdvanceDestructibleTileAnimation(std::int32_t tx, std::int32_t ty, std::int32_t amount); /** @brief Adds an additional tile set as a continuation of the previous one */ void AddTileSet(StringView tileSetPath, std::uint16_t offset, std::uint16_t count, const std::uint8_t* paletteRemapping = nullptr); /** @brief Reads layer configuration from a stream */ void ReadLayerConfiguration(Stream& s); /** @brief Reads description of animated tiles from a stream */ void ReadAnimatedTiles(Stream& s); /** @brief Sets tile event flags */ void SetTileEventFlags(std::int32_t x, std::int32_t y, EventType tileEvent, std::uint8_t* tileParams); /** @brief Overrides the diffuse texture of the specified tile */ bool OverrideTileDiffuse(std::int32_t tileId, StaticArrayView<(TileSet::DefaultTileSize + 2) * (TileSet::DefaultTileSize + 2), std::uint32_t> tileDiffuse); /** @brief Overrides the collision mask of the specified tile */ bool OverrideTileMask(std::int32_t tileId, StaticArrayView tileMask); /** @brief Returns a caption tile */ StaticArrayView GetCaptionTile() const { return _tileSets[0].Data->GetCaptionTile(); } /** @brief Returns relative paths of all used tile sets */ Array GetUsedTileSetPaths() const; /** @brief Creates a generic debris */ void CreateDebris(const DestructibleDebris& debris); /** @brief Creates a tile debris */ void CreateTileDebris(std::int32_t tileId, std::int32_t x, std::int32_t y); /** @brief Creates a particle debris from a sprite */ void CreateParticleDebris(const GraphicResource* res, Vector3f pos, Vector2f force, std::int32_t currentFrame, bool isFacingLeft); /** @brief Creates a sprite debris */ void CreateSpriteDebris(const GraphicResource* res, Vector3f pos, std::int32_t count); /** @brief Returns state of a given trigger */ bool GetTrigger(std::uint8_t triggerId); /** @brief Sets state of a given trigger */ void SetTrigger(std::uint8_t triggerId, bool newState); /** @brief Creates a checkpoint for eventual rollback */ void CreateCheckpointForRollback(); /** @brief Rolls back to the last checkpoint */ void RollbackToCheckpoint(); /** @brief Initializes tile map state from a stream */ void InitializeFromStream(Stream& src); /** @brief Serializes tile map state to a stream */ void SerializeResumableToStream(Stream& dest, bool fromCheckpoint = false); /** @brief Called when the viewport needs to be initialized (e.g., when the resolution is changed) */ void OnInitializeViewport(); private: enum class LayerType { Other, Sky, Sprite }; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct TileSetPart { std::unique_ptr Data; std::int32_t Offset; std::int32_t Count; }; class TexturedBackgroundPass : public SceneNode { friend class TileMap; public: TexturedBackgroundPass(TileMap* owner) : _owner(owner), _alreadyRendered(false) { } void Initialize(); bool OnDraw(RenderQueue& renderQueue) override; private: TileMap* _owner; std::unique_ptr _target; std::unique_ptr _view; std::unique_ptr _camera; SmallVector, 0> _renderCommands; bool _alreadyRendered; }; #endif ITileMapOwner* _owner; std::int32_t _sprLayerIndex; PitType _pitType; SmallVector _tileSets; SmallVector _layers; std::unique_ptr _sprLayerForRollback; SmallVector _animatedTiles; SmallVector _activeCollapsingTiles; float _collapsingTimer; std::uint32_t _animatedTilesOffset; BitArray _triggerState; BitArray _triggerStateForRollback; SmallVector _debrisList; SmallVector, 0> _renderCommands; std::int32_t _renderCommandsCount; std::int32_t _texturedBackgroundLayer; TexturedBackgroundPass _texturedBackgroundPass; void DrawLayer(RenderQueue& renderQueue, TileMapLayer& layer, const Rectf& cullingRect, Vector2f viewCenter); static float TranslateCoordinate(float coordinate, float speed, float offset, std::int32_t viewSize, bool isY); RenderCommand* RentRenderCommand(LayerRendererType type); bool AdvanceDestructibleTileAnimation(LayerTile& tile, std::int32_t tx, std::int32_t ty, std::int32_t& amount, StringView soundName); void AdvanceCollapsingTileTimers(float timeMult); void SetTileDestructibleEventParams(LayerTile& tile, TileDestructType type, std::uint16_t tileParams); std::int32_t GetTileDestructibleFrameCount(const LayerTile& tile); void UpdateDebris(float timeMult); void DrawDebris(RenderQueue& renderQueue); void RenderTexturedBackground(RenderQueue& renderQueue, const Rectf& cullingRect, Vector2f viewCenter, TileMapLayer& layer, float x, float y); TileSet* ResolveTileSet(std::int32_t& tileId); std::int32_t ResolveTileID(LayerTile& tile); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/TileSet.cpp000066400000000000000000000050601512772601700246710ustar00rootroot00000000000000#include "TileSet.h" namespace Jazz2::Tiles { TileSet::TileSet(StringView path, std::uint16_t tileCount, std::unique_ptr textureDiffuse, std::unique_ptr mask, std::uint32_t maskSize, std::unique_ptr captionTile) : FilePath(path), TextureDiffuse(std::move(textureDiffuse)), _mask(std::move(mask)), _captionTile(std::move(captionTile)), _isMaskEmpty(), _isMaskFilled(), _isTileFilled() { // TilesPerRow is used only for rendering if (TextureDiffuse != nullptr) { Vector2i texSize = TextureDiffuse->GetSize(); TilesPerRow = (texSize.X / (DefaultTileSize + 2)); } else { TilesPerRow = 0; } TileCount = tileCount; _isMaskEmpty.resize(ValueInit, TileCount); _isMaskFilled.resize(ValueInit, TileCount); _isTileFilled.resize(ValueInit, TileCount); std::uint32_t maskMaxTiles = maskSize / (DefaultTileSize * DefaultTileSize); for (std::uint32_t i = 0; i < tileCount; i++) { bool maskEmpty = true; bool maskFilled = true; if (i < maskMaxTiles) { auto* maskOffset = &_mask[i * DefaultTileSize * DefaultTileSize]; for (std::int32_t j = 0; j < DefaultTileSize * DefaultTileSize; j++) { bool masked = (maskOffset[j] > 0); maskEmpty &= !masked; maskFilled &= masked; } } if (maskEmpty) { _isMaskEmpty.set(i); } if (maskFilled) { _isMaskFilled.set(i); } // TODO: _isTileFilled is not properly set if (/*tileFilled ||*/ !maskEmpty) { _isTileFilled.set(i); } } } bool TileSet::OverrideTileDiffuse(std::int32_t tileId, StaticArrayView<(DefaultTileSize + 2) * (DefaultTileSize + 2), std::uint32_t> tileDiffuse) { if (tileId >= TileCount) { return false; } std::int32_t x = (tileId % TilesPerRow) * (DefaultTileSize + 2); std::int32_t y = (tileId / TilesPerRow) * (DefaultTileSize + 2); // TODO: _isTileFilled is not properly set return TextureDiffuse->LoadFromTexels((std::uint8_t*)tileDiffuse.data(), x, y, DefaultTileSize + 2, DefaultTileSize + 2); } bool TileSet::OverrideTileMask(std::int32_t tileId, StaticArrayView tileMask) { if (tileId >= TileCount) { return false; } auto* maskOffset = &_mask[tileId * DefaultTileSize * DefaultTileSize]; bool maskEmpty = true; bool maskFilled = true; for (std::int32_t j = 0; j < DefaultTileSize * DefaultTileSize; j++) { maskOffset[j] = tileMask[j]; bool masked = (tileMask[j] > 0); maskEmpty &= !masked; maskFilled &= masked; } _isMaskEmpty.set(tileId, maskEmpty); _isMaskFilled.set(tileId, maskFilled); return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/Tiles/TileSet.h000066400000000000000000000052201512772601700243340ustar00rootroot00000000000000#pragma once #include "../../Main.h" #include "../../nCine/Base/BitArray.h" #include "../../nCine/Graphics/Texture.h" #include #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::Tiles { /** @brief Represents tile set used by tile map, consists of texture and collision mask */ class TileSet { public: /** @brief Size of a tile */ static constexpr std::int32_t DefaultTileSize = 32; TileSet(StringView path, std::uint16_t tileCount, std::unique_ptr textureDiffuse, std::unique_ptr mask, std::uint32_t maskSize, std::unique_ptr captionTile); /** @brief Relative path to source file */ String FilePath; /** @brief Main (diffuse) texture */ std::unique_ptr TextureDiffuse; /** @brief Total number of tiles */ std::int32_t TileCount; /** @brief Number of tiles per row */ std::int32_t TilesPerRow; /** @brief Returns mask for specified tile */ std::uint8_t* GetTileMask(std::int32_t tileId) const { if (tileId >= TileCount) { return nullptr; } return &_mask[tileId * DefaultTileSize * DefaultTileSize]; } /** @brief Returns `true` if the mask of a tile is completely empty */ bool IsTileMaskEmpty(std::int32_t tileId) const { if (tileId >= TileCount) { return true; } return _isMaskEmpty[tileId]; } /** @brief Returns `true` if the mask of a tile is completely filled (non-empty) */ bool IsTileMaskFilled(std::int32_t tileId) const { if (tileId >= TileCount) { return false; } return _isMaskFilled[tileId]; } /** @brief Returns `true` if the texture of a tile is completely opaque (non-transparent) */ bool IsTileFilled(std::int32_t tileId) const { if (tileId >= TileCount) { return false; } return _isTileFilled[tileId]; } /** @brief Returns a caption tile */ StaticArrayView GetCaptionTile() const { return staticArrayView(_captionTile.get()); } /** @brief Overrides the diffuse texture of the specified tile */ bool OverrideTileDiffuse(std::int32_t tileId, StaticArrayView<(DefaultTileSize + 2) * (DefaultTileSize + 2), std::uint32_t> tileDiffuse); /** @brief Overrides the collision mask of the specified tile */ bool OverrideTileMask(std::int32_t tileId, StaticArrayView tileMask); private: std::unique_ptr _mask; std::unique_ptr _captionTile; BitArray _isMaskEmpty; BitArray _isMaskFilled; BitArray _isTileFilled; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/000077500000000000000000000000001512772601700220505ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Alignment.h000066400000000000000000000013551512772601700241430ustar00rootroot00000000000000#pragma once #include "../../Main.h" namespace Jazz2::UI { /** @brief Alignment, supports a bitwise combination of its member values */ enum class Alignment { Center = 0x00, /**< Center */ Left = 0x01, /**< Left */ Right = 0x02, /**< Right */ Top = 0x04, /**< Top */ Bottom = 0x08, /**< Bottom */ TopLeft = Top | Left, /**< Top-left */ TopRight = Top | Right, /**< TopRight */ BottomLeft = Bottom | Left, /**< Bottom-left */ BottomRight = Bottom | Right, /**< Bottom-right */ HorizontalMask = Left | Center | Right, /**< Mask of horizontal alignment */ VerticalMask = Top | Center | Bottom /**< Mask of vertical alignment */ }; DEATH_ENUM_FLAGS(Alignment); }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Canvas.cpp000066400000000000000000000107341512772601700237740ustar00rootroot00000000000000#include "Canvas.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Base/Random.h" namespace Jazz2::UI { Canvas::Canvas() : AnimTime(0.0f), _renderCommandsCount(0), _currentRenderQueue(nullptr) { setVisitOrderState(SceneNode::VisitOrderState::Disabled); } void Canvas::OnUpdate(float timeMult) { SceneNode::OnUpdate(timeMult); AnimTime += timeMult * AnimTimeMultiplier; } bool Canvas::OnDraw(RenderQueue& renderQueue) { SceneNode::OnDraw(renderQueue); _renderCommandsCount = 0; _currentRenderQueue = &renderQueue; return false; } void Canvas::DrawRenderCommand(RenderCommand* command) { _currentRenderQueue->AddCommand(command); } void Canvas::DrawTexture(const Texture& texture, Vector2f pos, std::uint16_t z, Vector2f size, const Vector4f& texCoords, const Colorf& color, bool additiveBlending, float angle) { auto command = RentRenderCommand(); if (command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite)) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); // Required to reset render command properly //command->SetTransformation(command->transformation()); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } if (additiveBlending) { command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE); } else { command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatVector(texCoords.Data()); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatVector(size.Data()); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(color.Data()); Matrix4x4f worldMatrix = Matrix4x4f::Translation(pos.X, pos.Y, 0.0f); if (std::abs(angle) > 0.01f) { worldMatrix.Translate(size.X * 0.5f, size.Y * 0.5f, 0.0f); worldMatrix.RotateZ(angle); worldMatrix.Translate(size.X * -0.5f, size.Y * -0.5f, 0.0f); } command->SetTransformation(worldMatrix); command->SetLayer(z); command->GetMaterial().SetTexture(texture); _currentRenderQueue->AddCommand(command); } void Canvas::DrawSolid(Vector2f pos, std::uint16_t z, Vector2f size, const Colorf& color, bool additiveBlending) { auto command = RentRenderCommand(); if (command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::SpriteNoTexture)) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); // Required to reset render command properly //command->SetTransformation(command->transformation()); } if (additiveBlending) { command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE); } else { command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatVector(size.Data()); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(color.Data()); command->SetTransformation(Matrix4x4f::Translation(pos.X, pos.Y, 0.0f)); command->SetLayer(z); _currentRenderQueue->AddCommand(command); } Vector2f Canvas::ApplyAlignment(Alignment align, Vector2f vec, Vector2f size) { Vector2f result = vec; // Sprites are aligned to center by default switch (align & Alignment::HorizontalMask) { case Alignment::Center: result.X -= size.X * 0.5f; break; case Alignment::Right: result.X -= size.X; break; } switch (align & Alignment::VerticalMask) { case Alignment::Center: result.Y -= size.Y * 0.5f; break; case Alignment::Bottom: result.Y -= size.Y; break; } return result; } RenderCommand* Canvas::RentRenderCommand() { if (_renderCommandsCount < _renderCommands.size()) { RenderCommand* command = _renderCommands[_renderCommandsCount].get(); command->SetType(RenderCommand::Type::Sprite); _renderCommandsCount++; return command; } else { std::unique_ptr& command = _renderCommands.emplace_back(std::make_unique(RenderCommand::Type::Sprite)); command->GetMaterial().SetBlendingEnabled(true); _renderCommandsCount++; return command.get(); } } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Canvas.h000066400000000000000000000027621512772601700234430ustar00rootroot00000000000000#pragma once #include "Alignment.h" #include "../../nCine/Graphics/RenderCommand.h" #include "../../nCine/Graphics/SceneNode.h" using namespace nCine; namespace Jazz2::UI { /** @brief Canvas */ class Canvas : public SceneNode { friend class Font; public: Canvas(); /** @brief View size of the canvas */ Vector2i ViewSize; /** @brief Animation time of the canvas */ float AnimTime; void OnUpdate(float timeMult) override; bool OnDraw(RenderQueue& renderQueue) override; /** @brief Draws a textured rectangle */ void DrawTexture(const Texture& texture, Vector2f pos, std::uint16_t z, Vector2f size, const Vector4f& texCoords, const Colorf& color, bool additiveBlending = false, float angle = 0.0f); /** @brief Draws a solid rectangle */ void DrawSolid(Vector2f pos, std::uint16_t z, Vector2f size, const Colorf& color, bool additiveBlending = false); /** @brief Applies alignment settings to a given position vector */ static Vector2f ApplyAlignment(Alignment align, Vector2f vec, Vector2f size); /** @brief Rents a render command for rendering on the canvas */ RenderCommand* RentRenderCommand(); /** @brief Draws a raw render command */ void DrawRenderCommand(RenderCommand* command); protected: /** @brief Multiplier of game time for canvas rendering */ static constexpr float AnimTimeMultiplier = 0.014f; private: std::int32_t _renderCommandsCount; SmallVector, 0> _renderCommands; RenderQueue* _currentRenderQueue; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Cinematics.cpp000066400000000000000000000317701512772601700246430ustar00rootroot00000000000000#include "Cinematics.h" #include "../PreferencesCache.h" #include "../PlayerAction.h" #include "../Input/ControlScheme.h" #include "../../nCine/Application.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Input/JoyMapping.h" #include "../../nCine/Audio/AudioBufferPlayer.h" #include "../../nCine/Base/FrameTimer.h" #include #include #include using namespace Death::Memory; using namespace Jazz2::Input; namespace Jazz2::UI { Cinematics::Cinematics(IRootController* root, StringView path, Function&& callback) : _root(root), _callback(std::move(callback)), _frameDelay(0.0f), _frameProgress(0.0f), _framesLeft(0), _frameIndex(0), _pressedKeys(ValueInit, (std::size_t)Keys::Count), _pressedActions(0) { Initialize(path); } Cinematics::~Cinematics() { _canvas->setParent(nullptr); } Vector2i Cinematics::GetViewSize() const { return _upscalePass.GetViewSize(); } void Cinematics::OnBeginFrame() { float timeMult = theApplication().GetTimeMult(); if (_framesLeft <= 0) { if (_callback && _callback(_root, _frameDelay != 0.0f)) { _callback = nullptr; } return; } _frameProgress += timeMult; while (_frameProgress >= _frameDelay) { _frameProgress -= _frameDelay; _framesLeft--; PrepareNextFrame(); } UpdatePressedActions(); if ((_pressedActions & ((1 << (std::int32_t)PlayerAction::Fire) | (1 << (16 + (std::int32_t)PlayerAction::Fire)))) == (1 << (std::int32_t)PlayerAction::Fire)) { if (_callback && _callback(_root, false)) { _callback = nullptr; _framesLeft = 0; } } } void Cinematics::OnInitializeViewport(std::int32_t width, std::int32_t height) { constexpr float defaultRatio = (float)DefaultWidth / DefaultHeight; float currentRatio = (float)width / height; std::int32_t w, h; if (currentRatio > defaultRatio) { w = std::min(DefaultWidth, width); h = (std::int32_t)roundf(w / currentRatio); } else if (currentRatio < defaultRatio) { h = std::min(DefaultHeight, height); w = (std::int32_t)roundf(h * currentRatio); } else { w = std::min(DefaultWidth, width); h = std::min(DefaultHeight, height); } _upscalePass.Initialize(w, h, width, height); // Viewports must be registered in reverse order _upscalePass.Register(); _canvas->setParent(_upscalePass.GetNode()); } void Cinematics::OnKeyPressed(const KeyboardEvent& event) { _pressedKeys.set((std::size_t)event.sym); } void Cinematics::OnKeyReleased(const KeyboardEvent& event) { _pressedKeys.reset((std::size_t)event.sym); } void Cinematics::OnTouchEvent(const TouchEvent& event) { if (event.type == TouchEventType::Down) { if (_callback && _callback(_root, false)) { _callback = nullptr; _framesLeft = 0; } } } void Cinematics::Initialize(StringView path) { theApplication().GetGfxDevice().setWindowTitle("Jazz² Resurrection"_s); _canvas = std::make_unique(this); auto& resolver = ContentResolver::Get(); if (!LoadCinematicsFromFile(path)) { _framesLeft = 0; return; } #if defined(WITH_AUDIO) && defined(WITH_OPENMPT) _music = resolver.GetMusic(String(path + ".j2b"_s)); if (_music != nullptr) { _music->setGain(PreferencesCache::MasterVolume * PreferencesCache::MusicVolume); _music->setSourceRelative(true); _music->play(); } #endif // Mark Fire button as already pressed to avoid some issues _pressedActions = (1 << (std::int32_t)PlayerAction::Fire) | (1 << ((std::int32_t)PlayerAction::Fire + 16)); } bool Cinematics::LoadCinematicsFromFile(StringView path) { // Try "Content" directory first, then "Source" directory auto& resolver = ContentResolver::Get(); auto s = resolver.OpenContentFile(fs::CombinePath("Cinematics"_s, String(path + ".j2v"_s)), 64 * 1024); if (!s->IsValid()) { if (auto alternativePath = fs::FindPathCaseInsensitive(fs::CombinePath(resolver.GetSourcePath(), String(path + ".j2v"_s)))) { s = fs::Open(alternativePath, FileAccess::Read, 64 * 1024); if (!s->IsValid()) { return false; } } } DEATH_ASSERT(s->GetSize() > 32 && s->GetSize() < 64 * 1024 * 1024, ("Cannot load \"{}.j2v\" - unexpected file size", path), false); // "CineFeed" + file size (uint32_t) + CRC of lowercase filename (uint32_t) std::uint8_t internalBuffer[16]; s->Read(internalBuffer, 16); DEATH_ASSERT(strncmp((const char*)internalBuffer, "CineFeed", sizeof("CineFeed") - 1) == 0, ("Cannot load \"{}.j2v\" - invalid signature", path), false); _width = s->ReadValueAsLE(); _height = s->ReadValueAsLE(); s->Seek(2, SeekOrigin::Current); // Bits per pixel _frameDelay = s->ReadValueAsLE() / (FrameTimer::SecondsPerFrame * 1000); // Delay in milliseconds _framesLeft = s->ReadValueAsLE(); s->Seek(20, SeekOrigin::Current); _texture = std::make_unique("Cinematics", Texture::Format::RGBA8, _width, _height); _buffer = std::make_unique(_width * _height); _lastBuffer = std::make_unique(_width * _height); _currentFrame = std::make_unique(_width * _height); // Read all 4 compressed streams std::uint32_t totalOffset = s->GetPosition(); while (totalOffset < s->GetSize()) { for (std::int32_t i = 0; i < std::int32_t(arraySize(_decompressedStreams)); i++) { std::int32_t bytesLeft = s->ReadValueAsLE(); totalOffset += 4 + bytesLeft; _compressedStreams[i].FetchFromStream(*s, bytesLeft); } } for (std::int32_t i = 0; i < std::int32_t(arraySize(_decompressedStreams)); i++) { // Skip first two bytes (0x78 0xDA) _compressedStreams[i].Seek(2, SeekOrigin::Begin); _decompressedStreams[i].Open(_compressedStreams[i]); } LoadSfxList(path); return true; } bool Cinematics::LoadSfxList(StringView path) { #if defined(WITH_AUDIO) auto& resolver = ContentResolver::Get(); auto s = resolver.OpenContentFile(fs::CombinePath("Cinematics"_s, String(path + ".j2sfx"_s))); if (!s->IsValid()) { return false; } if (s->GetSize() <= 16 || s->GetSize() >= 64 * 1024 * 1024) { LOGE("Cannot load SFX playlist for \"{}.j2v\" - unexpected file size", path); return false; } std::uint64_t signature = s->ReadValueAsLE(); std::uint8_t fileType = s->ReadValue(); std::uint16_t version = s->ReadValueAsLE(); if (signature != 0x2095A59FF0BFBBEF || fileType != ContentResolver::SfxListFile || version > SfxListVersion) { LOGE("Cannot load SFX playlist for \"{}.j2v\" - invalid signature", path); return false; } std::uint32_t sampleCount = s->ReadValueAsLE(); for (std::uint32_t i = 0; i < sampleCount; i++) { std::uint8_t stringSize = s->ReadValue(); String samplePath = String(NoInit, stringSize); s->Read(samplePath.data(), stringSize); String samplePathNormalized = fs::ToNativeSeparators(samplePath); String fullPath = fs::CombinePath("Animations"_s, samplePathNormalized); auto sample = resolver.OpenContentFile(fullPath); if (sample->IsValid()) { _sfxSamples.emplace_back(std::move(sample), fullPath); } else { _sfxSamples.emplace_back(); // Sample not found } } std::uint32_t itemCount = s->ReadValueAsLE(); for (std::uint32_t i = 0; i < itemCount; i++) { auto& item = _sfxPlaylist.emplace_back(); item.Frame = s->ReadVariableUint32(); item.Sample = s->ReadValueAsLE(); item.Gain = s->ReadValue() / 255.0f; item.Panning = s->ReadValue() / 127.0f; } return true; #else return false; #endif } void Cinematics::PrepareNextFrame() { // Check if palette was changed if (ReadValue(0) == 0x01) { Read(3, _palette, sizeof(_palette)); } // Read pixels into the buffer for (std::int32_t y = 0; y < _height; y++) { std::uint8_t c; std::int32_t x = 0; while ((c = ReadValue(0)) != 0x80) { if (c < 0x80) { std::int32_t u; if (c == 0x00) { u = AsLE(ReadValue(0)); } else { u = c; } // Read specified number of pixels in row for (std::int32_t i = 0; i < u; i++) { DEATH_DEBUG_ASSERT(x < _width, "Frame decoding overrun"); _buffer[y * _width + x] = ReadValue(3); x++; } } else { std::int32_t u; if (c == 0x81) { u = AsLE(ReadValue(0)); } else { u = c - 0x6A; } // Copy specified number of pixels from previous frame std::int32_t n = AsLE(ReadValue(1)) + (ReadValue(2) + y - 127) * _width; for (std::int32_t i = 0; i < u; i++) { DEATH_DEBUG_ASSERT(x < _width, "Frame decoding overrun"); _buffer[y * _width + x] = _lastBuffer[n]; x++; n++; } } } } // Apply current palette to indices for (std::int32_t i = 0; i < _width * _height; i++) { _currentFrame[i] = _palette[_buffer[i]]; } // Upload new texture to GPU _texture->LoadFromTexels((std::uint8_t*)_currentFrame.get(), 0, 0, _width, _height); // Create copy of the buffer std::memcpy(_lastBuffer.get(), _buffer.get(), _width * _height); #if defined(WITH_AUDIO) for (std::size_t i = 0; i < _sfxPlaylist.size(); i++) { if (_sfxPlaylist[i].Frame == _frameIndex) { auto& item = _sfxPlaylist[i]; auto& sample = _sfxSamples[item.Sample]; if (sample.Buffer == nullptr) { continue; } item.CurrentPlayer = std::make_unique(sample.Buffer.get()); item.CurrentPlayer->setPosition(Vector3f(item.Panning, 0.0f, 0.0f)); item.CurrentPlayer->setAs2D(true); item.CurrentPlayer->setGain(_sfxPlaylist[i].Gain * PreferencesCache::MasterVolume * PreferencesCache::SfxVolume); item.CurrentPlayer->play(); } } #endif _frameIndex++; } void Cinematics::Read(std::int32_t streamIndex, void* buffer, std::uint32_t bytes) { _decompressedStreams[streamIndex].Read(buffer, bytes); } void Cinematics::UpdatePressedActions() { auto& input = theApplication().GetInputManager(); _pressedActions = ((_pressedActions & 0xFFFF) << 16); const JoyMappedState* joyStates[ControlScheme::MaxConnectedGamepads]; std::int32_t joyStatesCount = 0; for (std::int32_t i = 0; i < JoyMapping::MaxNumJoysticks && joyStatesCount < std::int32_t(arraySize(joyStates)); i++) { if (input.isJoyMapped(i)) { joyStates[joyStatesCount++] = &input.joyMappedState(i); } } _pressedActions |= ControlScheme::FetchNavigation(_pressedKeys, ArrayView(joyStates, joyStatesCount)); // Also allow Menu action as skip key if (_pressedActions & (1 << (std::uint32_t)PlayerAction::Menu)) { _pressedActions |= (1 << (std::uint32_t)PlayerAction::Fire); } } void Cinematics::CinematicsCanvas::Initialize() { // Prepare output render command _renderCommand.SetType(RenderCommand::Type::Sprite); _renderCommand.GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite); _renderCommand.GetMaterial().ReserveUniformsDataMemory(); _renderCommand.GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); auto* textureUniform = _renderCommand.GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } bool Cinematics::CinematicsCanvas::OnDraw(RenderQueue& renderQueue) { if (_owner->_frameDelay == 0.0f) { return false; } Vector2i viewSize = _owner->_upscalePass.GetViewSize(); float ratioTarget = (float)viewSize.Y / viewSize.X; float ratioSource = (float)_owner->_height / _owner->_width; Vector2f frameSize; if (PreferencesCache::KeepAspectRatioInCinematics) { if (ratioTarget < ratioSource) { frameSize = Vector2f(viewSize.Y / ratioSource, viewSize.Y); } else { frameSize = Vector2f(viewSize.X, viewSize.X * ratioSource); } } else { // Try to adjust ratio a bit, otherwise show black bars or zoom it in float ratio = std::clamp(ratioTarget, ratioSource - 0.16f, ratioSource); frameSize = Vector2f(viewSize.X, viewSize.X * ratio); } Vector2f frameOffset = (viewSize.As() - frameSize) * 0.5f; frameOffset.X = std::round(frameOffset.X); frameOffset.Y = std::round(frameOffset.Y); auto* instanceBlock = _renderCommand.GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, 1.0f, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatVector(frameSize.Data()); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf::White.Data()); _renderCommand.SetTransformation(Matrix4x4f::Translation(frameOffset.X, frameOffset.Y, 0.0f)); _renderCommand.GetMaterial().SetTexture(*_owner->_texture); renderQueue.AddCommand(&_renderCommand); return true; } #if defined(WITH_AUDIO) Cinematics::SfxItem::SfxItem() { } Cinematics::SfxItem::SfxItem(std::unique_ptr stream, StringView path) : Buffer(std::make_unique(std::move(stream), path)) { } #endif }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Cinematics.h000066400000000000000000000064031512772601700243030ustar00rootroot00000000000000#pragma once #include "../IStateHandler.h" #include "../IRootController.h" #include "../ContentResolver.h" #include "../Rendering/UpscaleRenderPass.h" #include "../../nCine/Base/BitArray.h" #include "../../nCine/Audio/AudioBufferPlayer.h" #include "../../nCine/Audio/AudioStreamPlayer.h" #include "../../nCine/Input/InputEvents.h" #include #include using namespace Death::IO; namespace Jazz2::UI { /** @brief Handler that plays a cinematic video */ class Cinematics : public IStateHandler { public: /** @{ @name Constants */ /** @brief Default width of viewport */ static constexpr std::int32_t DefaultWidth = 720; /** @brief Default height of viewport */ static constexpr std::int32_t DefaultHeight = 405; #ifndef DOXYGEN_GENERATING_OUTPUT static constexpr std::uint8_t SfxListVersion = 1; #endif /** @} */ Cinematics(IRootController* root, StringView path, Function&& callback); ~Cinematics() override; Vector2i GetViewSize() const override; void OnBeginFrame() override; void OnInitializeViewport(std::int32_t width, std::int32_t height) override; void OnKeyPressed(const KeyboardEvent& event) override; void OnKeyReleased(const KeyboardEvent& event) override; void OnTouchEvent(const TouchEvent& event) override; private: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class CinematicsCanvas : public SceneNode { public: CinematicsCanvas(Cinematics* owner) : _owner(owner) { Initialize(); } void Initialize(); bool OnDraw(RenderQueue& renderQueue) override; private: Cinematics* _owner; RenderCommand _renderCommand; }; #endif #if defined(WITH_AUDIO) struct SfxItem { std::unique_ptr Buffer; SfxItem(); SfxItem(std::unique_ptr stream, StringView path); }; struct SfxPlaylistItem { std::uint32_t Frame; std::uint16_t Sample; float Gain; float Panning; std::unique_ptr CurrentPlayer; }; #endif IRootController* _root; Rendering::UpscaleRenderPass _upscalePass; std::unique_ptr _canvas; #if defined(WITH_AUDIO) std::unique_ptr _music; SmallVector _sfxSamples; SmallVector _sfxPlaylist; #endif Function _callback; std::uint32_t _width, _height; float _frameDelay, _frameProgress; std::int32_t _frameIndex; std::int32_t _framesLeft; std::unique_ptr _texture; std::unique_ptr _buffer; std::unique_ptr _lastBuffer; std::unique_ptr _currentFrame; std::uint32_t _palette[256]; MemoryStream _compressedStreams[4]; Compression::DeflateStream _decompressedStreams[4]; BitArray _pressedKeys; std::uint32_t _pressedActions; void Initialize(StringView path); bool LoadCinematicsFromFile(StringView path); bool LoadSfxList(StringView path); void PrepareNextFrame(); void Read(std::int32_t streamIndex, void* buffer, std::uint32_t bytes); void UpdatePressedActions(); template inline T ReadValue(std::int32_t streamIndex) { T buffer; Read(streamIndex, &buffer, sizeof(T)); return buffer; } }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/DiscordRpcClient.cpp000066400000000000000000000272021512772601700257520ustar00rootroot00000000000000#include "DiscordRpcClient.h" #if (defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT)) || defined(DEATH_TARGET_UNIX) #include "../../nCine/Base/Algorithms.h" #include "../../jsoncpp/json.h" #include #if defined(DEATH_TARGET_UNIX) # include # include # include #endif using namespace Death::Containers::Literals; using namespace std::string_view_literals; namespace Jazz2::UI { DiscordRpcClient& DiscordRpcClient::Get() { static DiscordRpcClient current; return current; } DiscordRpcClient::DiscordRpcClient() : #if defined(DEATH_TARGET_WINDOWS) _hPipe(INVALID_HANDLE_VALUE), _hEventRead(NULL), _hEventWrite(NULL), #else _sockFd(-1), #endif _nonce(0), _userId(0) { } DiscordRpcClient::~DiscordRpcClient() { Disconnect(); } bool DiscordRpcClient::Connect(StringView clientId) { if (clientId.empty()) { return false; } #if defined(DEATH_TARGET_WINDOWS) if (_hPipe != INVALID_HANDLE_VALUE) { return true; } wchar_t pipeName[32]; for (std::int32_t i = 0; i < 10; i++) { swprintf_s(pipeName, L"\\\\.\\pipe\\discord-ipc-%i", i); _hPipe = ::CreateFile(pipeName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); if (_hPipe != INVALID_HANDLE_VALUE) { break; } } if (_hPipe == INVALID_HANDLE_VALUE) { return false; } _clientId = clientId; _nonce = 0; _hEventRead = ::CreateEvent(NULL, FALSE, TRUE, NULL); _hEventWrite = ::CreateEvent(NULL, FALSE, FALSE, NULL); _thread = Thread(DiscordRpcClient::OnBackgroundThread, this); #else if (_sockFd >= 0) { return true; } static const StringView RpcPaths[] = { "%s/discord-ipc-%i"_s, "%s/app/com.discordapp.Discord/discord-ipc-%i"_s, "%s/snap.discord-canary/discord-ipc-%i"_s, "%s/snap.discord/discord-ipc-%i"_s }; _sockFd = ::socket(AF_UNIX, SOCK_STREAM, 0); if (_sockFd < 0) { LOGE("Failed to create socket"); return false; } # if defined(SO_NOSIGPIPE) std::int32_t optval = 1; ::setsockopt(_sockFd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); # endif struct sockaddr_un addr; addr.sun_family = AF_UNIX; StringView tempPath = ::getenv("XDG_RUNTIME_DIR"); if (tempPath.empty()) { tempPath = ::getenv("TMPDIR"); if (tempPath.empty()) { tempPath = ::getenv("TMP"); if (tempPath.empty()) { tempPath = ::getenv("TEMP"); if (tempPath.empty()) { tempPath = "/tmp"_s; } } } } bool isConnected = false; for (std::int32_t j = 0; j < std::int32_t(arraySize(RpcPaths)); j++) { for (std::int32_t i = 0; i < 10; i++) { std::size_t length = formatInto({ addr.sun_path, sizeof(addr.sun_path) - 1 }, RpcPaths[j].data(), tempPath.data(), i); addr.sun_path[length] = '\0'; if (::connect(_sockFd, (struct sockaddr*)&addr, sizeof(addr)) >= 0) { isConnected = true; break; } } } if (!isConnected) { ::close(_sockFd); _sockFd = -1; return false; } _clientId = clientId; _nonce = 0; _thread = Thread(DiscordRpcClient::OnBackgroundThread, this); #endif return true; } void DiscordRpcClient::Disconnect() { #if defined(DEATH_TARGET_WINDOWS) HANDLE pipe = _hPipe.exchange(INVALID_HANDLE_VALUE); if (pipe != INVALID_HANDLE_VALUE) { ::CancelIoEx(pipe, NULL); ::CloseHandle(pipe); _thread.Join(); } if (_hEventRead != NULL) { ::CloseHandle(_hEventRead); _hEventRead = NULL; } if (_hEventWrite != NULL) { ::CloseHandle(_hEventWrite); _hEventWrite = NULL; } #else std::int32_t sockFd = _sockFd.exchange(-1); if (sockFd >= 0) { _thread.Abort(); ::close(sockFd); } #endif } bool DiscordRpcClient::IsSupported() const { #if defined(DEATH_TARGET_WINDOWS) return (_hPipe != INVALID_HANDLE_VALUE); #else return (_sockFd >= 0); #endif } std::uint64_t DiscordRpcClient::GetUserId() const { return _userId; } StringView DiscordRpcClient::GetUserDisplayName() const { return _userDisplayName; } bool DiscordRpcClient::SetRichPresence(const RichPresence& richPresence) { #if defined(DEATH_TARGET_WINDOWS) if (!_pendingFrame.empty() || !IsSupported()) { return false; } DWORD processId = ::GetCurrentProcessId(); #else if (!IsSupported()) { return false; } pid_t processId = ::getpid(); #endif char buffer[1024]; std::int32_t bufferOffset = formatInto(buffer, "{{\"cmd\":\"SET_ACTIVITY\",\"nonce\":{},\"args\":{{\"pid\":{},\"activity\":{{", ++_nonce, processId); if (!richPresence.State.empty()) { bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"state\":\"{}\",", richPresence.State); } if (!richPresence.Details.empty()) { bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"details\":\"{}\",", richPresence.Details); } bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"assets\":{{"); bool isFirst = true; if (!richPresence.LargeImage.empty()) { isFirst = false; bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"large_image\":\"{}\"", richPresence.LargeImage); } if (!richPresence.LargeImageTooltip.empty()) { if (!isFirst) { buffer[bufferOffset++] = ','; } isFirst = false; bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"large_text\":\"{}\"", richPresence.LargeImageTooltip); } if (!richPresence.SmallImage.empty()) { if (!isFirst) { buffer[bufferOffset++] = ','; } isFirst = false; bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"small_image\":\"{}\"", richPresence.SmallImage); } if (!richPresence.SmallImageTooltip.empty()) { if (!isFirst) { buffer[bufferOffset++] = ','; } isFirst = false; bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "\"small_text\":\"{}\"", richPresence.SmallImageTooltip); } bufferOffset += formatInto({ buffer + bufferOffset, sizeof(buffer) - bufferOffset }, "}}}}}}}}"); #if defined(DEATH_TARGET_WINDOWS) _pendingFrame = String(buffer, bufferOffset); ::SetEvent(_hEventWrite); #else WriteFrame(Opcodes::Frame, buffer, bufferOffset); #endif return true; } void DiscordRpcClient::ProcessInboundFrame(const char* json, std::size_t length, std::size_t allocated) { LOGD("{}", StringView(json, length)); Json::CharReaderBuilder builder; auto reader = std::unique_ptr(builder.newCharReader()); Json::Value doc; std::string errors; if (reader->parse(json, json + length, &doc, &errors)) { std::string_view cmd; if (doc["cmd"].get(cmd) == Json::SUCCESS && cmd == "DISPATCH"sv) { const auto& data = doc["data"]; if (data.isObject()) { const auto& user = data["user"]; // {"id":"123456789","username":"nick.name","discriminator":"0","global_name":"Display Name","avatar":"123456789abcdef","avatar_decoration_data":null,"bot":false,"flags":32,"premium_type":0} std::string_view userId, userGlobalName; if (user.isObject() && user["id"].get(userId) == Json::SUCCESS && user["global_name"].get(userGlobalName) == Json::SUCCESS) { _userId = stou64(userId.data(), userId.size()); _userDisplayName = userGlobalName; LOGD("Connected to Discord as user \"{}\" ({})", _userDisplayName, _userId); } } } } } bool DiscordRpcClient::WriteFrame(Opcodes opcode, const char* buffer, std::uint32_t bufferSize) { char frameHeader[8]; *(std::uint32_t*)&frameHeader[0] = (std::uint32_t)opcode; *(std::uint32_t*)&frameHeader[4] = bufferSize; #if defined(DEATH_TARGET_WINDOWS) DWORD bytesWritten = 0; return ::WriteFile(_hPipe, frameHeader, sizeof(frameHeader), &bytesWritten, NULL) && ::WriteFile(_hPipe, buffer, bufferSize, &bytesWritten, NULL); #else if (::write(_sockFd, frameHeader, sizeof(frameHeader)) < 0) { return false; } std::int32_t bytesTotal = 0; while (bytesTotal < bufferSize) { std::int32_t bytesWritten = ::write(_sockFd, buffer + bytesTotal, bufferSize - bytesTotal); if (bytesWritten < 0) { return false; } bytesTotal += bytesWritten; } return true; #endif } void DiscordRpcClient::OnBackgroundThread(void* args) { DiscordRpcClient* _this = static_cast(args); // Handshake char buffer[2048]; std::size_t bufferSize = formatInto(buffer, "{{\"v\":1,\"client_id\":\"{}\"}}", _this->_clientId); _this->WriteFrame(Opcodes::Handshake, buffer, bufferSize); #if defined(DEATH_TARGET_WINDOWS) HANDLE hPipe = _this->_hPipe; OVERLAPPED ov = {}; ov.hEvent = _this->_hEventRead; if (!::ReadFile(hPipe, buffer, sizeof(buffer), NULL, &ov)) { DWORD error = ::GetLastError(); if (error == ERROR_BROKEN_PIPE) { _this->_hPipe = INVALID_HANDLE_VALUE; if (hPipe != INVALID_HANDLE_VALUE) { ::CloseHandle(hPipe); } return; } } HANDLE waitHandles[] = { _this->_hEventRead, _this->_hEventWrite }; while (_this->_hPipe != INVALID_HANDLE_VALUE) { DWORD dwEvent = ::WaitForMultipleObjects(static_cast(arraySize(waitHandles)), waitHandles, FALSE, INFINITE); switch (dwEvent) { case WAIT_OBJECT_0: { DWORD bytesRead; if (::GetOverlappedResult(hPipe, &ov, &bytesRead, FALSE) && bytesRead > 0) { Opcodes opcode = (Opcodes)*(std::uint32_t*)&buffer[0]; std::uint32_t frameSize = *(std::uint32_t*)&buffer[4]; if (frameSize >= sizeof(buffer)) { continue; } switch (opcode) { case Opcodes::Handshake: // Invalid response opcode case Opcodes::Close: { _this->_hPipe = INVALID_HANDLE_VALUE; if (hPipe != INVALID_HANDLE_VALUE) { ::CloseHandle(hPipe); } return; } case Opcodes::Ping: { _this->WriteFrame(Opcodes::Pong, &buffer[8], frameSize); break; } case Opcodes::Frame: { _this->ProcessInboundFrame(&buffer[8], frameSize, sizeof(buffer) - 8); break; } } //if (bytesRead > frameSize + 8) { // LOGW("Partial read ({} bytes left)", bytesRead - (frameSize + 8)); //} } if (!::ReadFile(hPipe, buffer, sizeof(buffer), NULL, &ov)) { DWORD error = ::GetLastError(); if (error == ERROR_BROKEN_PIPE) { _this->_hPipe = INVALID_HANDLE_VALUE; if (hPipe != INVALID_HANDLE_VALUE) { ::CloseHandle(hPipe); } return; } } break; } case WAIT_OBJECT_0 + 1: { if (!_this->_pendingFrame.empty()) { _this->WriteFrame(Opcodes::Frame, _this->_pendingFrame.data(), _this->_pendingFrame.size()); _this->_pendingFrame = {}; } break; } } } #else while (_this->_sockFd >= 0) { std::int32_t bytesRead = ::read(_this->_sockFd, buffer, sizeof(buffer)); if (bytesRead <= 0) { LOGE("Failed to read from socket: {}", bytesRead); std::int32_t sockFd = _this->_sockFd.exchange(-1); if (sockFd >= 0) { ::close(sockFd); } break; } Opcodes opcode = (Opcodes)*(std::uint32_t*)&buffer[0]; std::uint32_t frameSize = *(std::uint32_t*)&buffer[4]; if (frameSize >= sizeof(buffer)) { continue; } switch (opcode) { case Opcodes::Handshake: case Opcodes::Close: { std::int32_t sockFd = _this->_sockFd.exchange(-1); if (sockFd >= 0) { ::close(sockFd); } return; } case Opcodes::Ping: { _this->WriteFrame(Opcodes::Pong, &buffer[8], frameSize); break; } case Opcodes::Frame: { _this->ProcessInboundFrame(&buffer[8], frameSize, sizeof(buffer) - 8); break; } } //if (bytesRead > frameSize + 8) { // LOGW("Partial read ({} bytes left)", bytesRead - (frameSize + 8)); //} } #endif } } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/DiscordRpcClient.h000066400000000000000000000045441512772601700254230ustar00rootroot00000000000000#pragma once #include "../../Main.h" #if (defined(DEATH_TARGET_WINDOWS) && !defined(DEATH_TARGET_WINDOWS_RT)) || defined(DEATH_TARGET_UNIX) || defined(DOXYGEN_GENERATING_OUTPUT) #include "../../nCine/Threading/Thread.h" #include #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::UI { /** @brief Allows interactions with running Discord client */ class DiscordRpcClient { public: /** @brief Rich presence description */ struct RichPresence { /** @brief State text */ String State; /** @brief Details description text */ String Details; /** @brief Large image ID */ String LargeImage; /** @brief Large image tooltip text */ String LargeImageTooltip; /** @brief Small image ID */ String SmallImage; /** @brief Small image tooltip text */ String SmallImageTooltip; }; DiscordRpcClient(); ~DiscordRpcClient(); /** @brief Connects to a local Discord client */ bool Connect(StringView clientId); /** @brief Disconnects from a local Discord client */ void Disconnect(); /** @brief Returns `true` if Discord is running and connection is active */ bool IsSupported() const; /** @brief Returns a user ID of the logged-in user */ std::uint64_t GetUserId() const; /** @brief Returns a display name of the logged-in user */ StringView GetUserDisplayName() const; /** @brief Sets rich presence */ bool SetRichPresence(const RichPresence& richPresence); /** @brief Returns static instance of @ref DiscordRpcClient */ static DiscordRpcClient& Get(); private: DiscordRpcClient(const DiscordRpcClient&) = delete; DiscordRpcClient& operator=(const DiscordRpcClient&) = delete; enum class Opcodes : std::uint32_t { Handshake, Frame, Close, Ping, Pong }; # if defined(DEATH_TARGET_WINDOWS) std::atomic _hPipe; HANDLE _hEventRead; HANDLE _hEventWrite; String _pendingFrame; # else std::atomic_int32_t _sockFd; # endif Thread _thread; std::int32_t _nonce; String _clientId; std::uint64_t _userId; String _userDisplayName; void ProcessInboundFrame(const char* json, std::size_t length, std::size_t allocated); bool WriteFrame(Opcodes opcode, const char* buffer, std::uint32_t bufferSize); static void OnBackgroundThread(void* args); }; } #endifdeathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Font.cpp000066400000000000000000000427221512772601700234710ustar00rootroot00000000000000#include "Font.h" #include "../ContentResolver.h" #include "../../nCine/Graphics/ITextureLoader.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Base/Random.h" #include #include using namespace Death; namespace Jazz2::UI { Font::Font(StringView path, const std::uint32_t* palette) : _baseSpacing(0) { auto s = fs::Open(String(path + ".font"_s), FileAccess::Read); auto fileSize = s->GetSize(); if (fileSize < 4 || fileSize > 8 * 1024 * 1024) { // 8 MB file size limit return; } std::unique_ptr texLoader = ITextureLoader::createFromFile(path); if (texLoader->hasLoaded()) { auto texFormat = texLoader->texFormat().internalFormat(); if (texFormat != GL_RGBA8 && texFormat != GL_RGB8) { return; } std::int32_t w = texLoader->width(); std::int32_t h = texLoader->height(); auto* pixels = (std::uint8_t*)texLoader->pixels(); /*std::uint8_t flags =*/ s->ReadValue(); std::uint16_t width = s->ReadValueAsLE(); std::uint16_t height = s->ReadValueAsLE(); std::uint8_t cols = s->ReadValue(); std::int32_t rows = h / height; std::int16_t spacing = s->ReadValueAsLE(); std::uint8_t asciiFirst = s->ReadValue(); std::uint8_t asciiCount = s->ReadValue(); std::uint8_t widths[128]; s->Read(widths, asciiCount); std::int32_t i = 0; for (; i < asciiCount; i++) { _asciiChars[i + asciiFirst] = Rectf( (float)(i % cols) / cols, (float)(i / cols) / rows, widths[i], height ); } std::int32_t unicodeCharCount = asciiCount + s->ReadValueAsLE(); for (; i < unicodeCharCount; i++) { char c[5] {}; s->Read(c, 1); std::int32_t remainingBytes = ((c[0] & 240) == 240) ? 3 : ( ((c[0] & 224) == 224) ? 2 : ( ((c[0] & 192) == 192) ? 1 : 0 )); if (remainingBytes == 0) { // Placeholder for unknown characters std::uint8_t charWidth = s->ReadValue(); if (c[0] == 0) { _asciiChars[0] = Rectf( (float)(i % cols) / cols, (float)(i / cols) / rows, charWidth, height ); } continue; } s->Read(c + 1, remainingBytes); std::uint8_t charWidth = s->ReadValue(); Pair cursor = Utf8::NextChar(c, 0); _unicodeChars[cursor.first()] = Rectf( (float)(i % cols) / cols, (float)(i / cols) / rows, charWidth, height ); } _charSize = Vector2i(width, height); _baseSpacing = spacing; for (std::uint32_t i = 0; i < w * h; i++) { std::uint32_t srcIdx = i * ContentResolver::PixelSize; std::uint32_t color = palette[pixels[srcIdx]]; std::uint8_t alpha = pixels[srcIdx + 3]; std::uint8_t r = (color >> 0) & 0xFF; std::uint8_t g = (color >> 8) & 0xFF; std::uint8_t b = (color >> 16) & 0xFF; std::uint8_t a = ((color >> 24) & 0xFF) * alpha / 255; pixels[srcIdx + 0] = r; pixels[srcIdx + 1] = g; pixels[srcIdx + 2] = b; pixels[srcIdx + 3] = a; } _texture = std::make_unique(path.data(), Texture::Format::RGBA8, w, h); _texture->LoadFromTexels(pixels, 0, 0, w, h); _texture->SetMinFiltering(SamplerFilter::Linear); _texture->SetMagFiltering(SamplerFilter::Linear); } } std::int32_t Font::GetSizeInPixels() const { // TODO return _charSize.Y; } std::int32_t Font::GetAscentInPixels() const { // TODO return (_charSize.Y * 4 / 5); } Vector2f Font::MeasureChar(char32_t c) const { Rectf uvRect; if (c < 128) { uvRect = _asciiChars[c]; } else { auto it = _unicodeChars.find(c); if (it != _unicodeChars.end()) { uvRect = it->second; } else { uvRect = _asciiChars[0]; } } return Vector2f(uvRect.W, uvRect.H); } Vector2f Font::MeasureString(StringView text, float scale, float charSpacing, float lineSpacing) { std::size_t textLength = text.size(); if (textLength == 0 || _charSize.Y <= 0) { return Vector2f::Zero; } float totalWidth = 0.0f, lastWidth = 0.0f, totalHeight = 0.0f; float charSpacingPre = charSpacing; float scalePre = scale; std::int32_t idx = 0; do { Pair cursor = Utf8::NextChar(text, idx); if (cursor.first() == '\n') { // New line if (totalWidth < lastWidth) { totalWidth = lastWidth; } lastWidth = 0.0f; totalHeight += (_charSize.Y * scale * lineSpacing); } else if (cursor.first() == '\f') { // Formatting cursor = Utf8::NextChar(text, cursor.second()); if (cursor.first() == '[') { idx = std::int32_t(cursor.second()); do { cursor = Utf8::NextChar(text, idx); if (cursor.first() == ']') { break; } idx = std::int32_t(cursor.second()); } while (idx < textLength); } } else { Vector2f charSize = MeasureChar(cursor.first()); if (charSize.X > 0 && charSize.Y > 0) { lastWidth += (charSize.X + _baseSpacing) * charSpacingPre * scalePre; } } idx = std::int32_t(cursor.second()); } while (idx < textLength); if (totalWidth < lastWidth) { totalWidth = lastWidth; } totalHeight += (_charSize.Y * scale * lineSpacing); return Vector2f(ceilf(totalWidth), ceilf(totalHeight)); } Vector2f Font::MeasureStringEx(StringView text, float scale, float charSpacing, float maxWidth, std::int32_t* charFit, float* charFitWidths) { if (charFit != nullptr) { *charFit = 0; } std::size_t textLength = text.size(); if (textLength == 0 || _charSize.Y <= 0) { return Vector2f::Zero; } float totalWidth = 0.0f, totalHeight = 0.0f; std::size_t idx = 0;; std::size_t lastCharFit = 0; do { auto [c, next] = Utf8::NextChar(text, idx); Vector2f charSize = MeasureChar(c); if (charSize.X > 0 && charSize.Y > 0) { float totalWidthNew = totalWidth + (charSize.X + _baseSpacing) * charSpacing * scale; if (charFitWidths != nullptr) { do { charFitWidths[lastCharFit++] = totalWidthNew; } while (lastCharFit < next); } if (totalWidthNew > maxWidth) { break; } totalWidth = totalWidthNew; } idx = next; } while (idx < textLength); if (charFit != nullptr) { *charFit = static_cast(idx); } totalHeight += (_charSize.Y * scale); return Vector2f(ceilf(totalWidth), ceilf(totalHeight)); } void Font::DrawString(Canvas* canvas, StringView text, std::int32_t& charOffset, float x, float y, std::uint16_t z, Alignment align, Colorf color, float scale, float angleOffset, float varianceX, float varianceY, float speed, float charSpacing, float lineSpacing) { std::size_t textLength = text.size(); if (textLength == 0 || _charSize.Y <= 0) { return; } // TODO: Revise this float phase = canvas->AnimTime * speed * 16.0f; // Maximum number of lines - center and right alignment starts to glitch if text has more lines, but it should be enough in most cases constexpr std::int32_t MaxLines = 16; // Preprocessing float totalWidth = 0.0f, lastWidth = 0.0f, totalHeight = 0.0f; float lineWidths[MaxLines]; float charSpacingPre = charSpacing; float scalePre = scale; std::int32_t idx = 0; std::int32_t line = 0; do { Pair cursor = Utf8::NextChar(text, idx); if (cursor.first() == '\n') { // New line if (totalWidth < lastWidth) { totalWidth = lastWidth; } lineWidths[line & (MaxLines - 1)] = lastWidth; line++; lastWidth = 0.0f; totalHeight += (_charSize.Y * scale * lineSpacing); } else if (cursor.first() == '\f') { // Formatting cursor = Utf8::NextChar(text, cursor.second()); if (cursor.first() == '[') { idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == 'w') { idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == ':') { idx = std::int32_t(cursor.second()); std::int32_t paramLength = 0; char param[9]; do { cursor = Utf8::NextChar(text, idx); if (cursor.first() == ']') { break; } if (paramLength < std::int32_t(arraySize(param)) - 1) { param[paramLength++] = (char)cursor.first(); } idx = std::int32_t(cursor.second()); } while (idx < textLength); if (paramLength > 0) { param[paramLength] = '\0'; char* end = ¶m[paramLength]; unsigned long paramValue = strtoul(param, &end, 10); if (param != end) { charSpacing = paramValue * 0.01f; } } } else if (cursor.first() == ']') { // Reset char spacing charSpacing = charSpacingPre; } } else { do { if (cursor.first() == ']') { break; } idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); } while (idx < textLength); } } } else { Rectf uvRect; if (cursor.first() < 128) { uvRect = _asciiChars[cursor.first()]; } else { auto it = _unicodeChars.find(cursor.first()); if (it != _unicodeChars.end()) { uvRect = it->second; } else { uvRect = _asciiChars[0]; } } if (uvRect.W > 0 && uvRect.H > 0) { lastWidth += (uvRect.W + _baseSpacing) * charSpacing * scalePre; } } idx = std::int32_t(cursor.second()); } while (idx < textLength); if (totalWidth < lastWidth) { totalWidth = lastWidth; } lineWidths[line & (MaxLines - 1)] = lastWidth; totalHeight += (_charSize.Y * scale * lineSpacing); charSpacing = charSpacingPre; // Rendering Vector2f originPos = Vector2f(x, y); switch (align & Alignment::HorizontalMask) { case Alignment::Center: originPos.X -= totalWidth * 0.5f; break; case Alignment::Right: originPos.X -= totalWidth; break; } switch (align & Alignment::VerticalMask) { case Alignment::Center: originPos.Y -= totalHeight * 0.5f; break; case Alignment::Bottom: originPos.Y -= totalHeight; break; } float lineStart = originPos.X; switch (align & Alignment::HorizontalMask) { case Alignment::Center: originPos.X += (totalWidth - lineWidths[0]) * 0.5f; break; case Alignment::Right: originPos.X += (totalWidth - lineWidths[0]); break; } Vector2i texSize = _texture->GetSize(); Shader* colorizeShader; bool useRandomColor, isShadow; float alpha; if (color.R == DefaultColor.R && color.G == DefaultColor.G && color.B == DefaultColor.B) { colorizeShader = nullptr; useRandomColor = false; isShadow = false; alpha = color.A; color = Colorf(1.0f, 1.0f, 1.0f, alpha); } else { colorizeShader = ContentResolver::Get().GetShader(PrecompiledShader::Colorized); useRandomColor = (color.R == RandomColor.R && color.G == RandomColor.G && color.B == RandomColor.B); isShadow = (color.R == 0.0f && color.G == 0.0f && color.B == 0.0f); alpha = std::min(color.A * 2.0f, 1.0f); } idx = 0; line = 0; do { Pair cursor = Utf8::NextChar(text, idx); if (cursor.first() == '\n') { // New line line++; originPos.X = lineStart; switch (align & Alignment::HorizontalMask) { case Alignment::Center: originPos.X += (totalWidth - lineWidths[line & (MaxLines - 1)]) * 0.5f; break; case Alignment::Right: originPos.X += (totalWidth - lineWidths[line & (MaxLines - 1)]); break; } originPos.Y += (_charSize.Y * scale * lineSpacing); } else if (cursor.first() == '\f') { // Formatting cursor = Utf8::NextChar(text, cursor.second()); if (cursor.first() == '[') { idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == 'c') { idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == ':') { // Set custom color idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == '#') { idx = std::int32_t(cursor.second()); std::int32_t paramLength = 0; char param[9]; do { cursor = Utf8::NextChar(text, idx); if (cursor.first() == ']') { break; } if (paramLength < std::int32_t(arraySize(param)) - 1) { param[paramLength++] = (char)cursor.first(); } idx = std::int32_t(cursor.second()); } while (idx < textLength); if (paramLength > 0 && !useRandomColor && !isShadow) { param[paramLength] = '\0'; char* end = ¶m[paramLength]; unsigned long paramValue = strtoul(param, &end, 16); if (param != end) { color = Color(paramValue); color.SetAlpha(0.5f * alpha); if (colorizeShader == nullptr) { colorizeShader = ContentResolver::Get().GetShader(PrecompiledShader::Colorized); } } } } } } else if (cursor.first() == 'w') { idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == ':') { idx = std::int32_t(cursor.second()); std::int32_t paramLength = 0; char param[9]; do { cursor = Utf8::NextChar(text, idx); if (cursor.first() == ']') { break; } if (paramLength < std::int32_t(arraySize(param)) - 1) { param[paramLength++] = (char)cursor.first(); } idx = std::int32_t(cursor.second()); } while (idx < textLength); if (paramLength > 0) { param[paramLength] = '\0'; char* end = ¶m[paramLength]; unsigned long paramValue = strtoul(param, &end, 10); if (param != end) { charSpacing = paramValue * 0.01f; } } } } else if (cursor.first() == '/') { idx = std::int32_t(cursor.second()); cursor = Utf8::NextChar(text, idx); if (cursor.first() == 'c') { // Reset color if (!useRandomColor && !isShadow) { color = Colorf(1.0f, 1.0f, 1.0f, alpha); colorizeShader = nullptr; } } else if (cursor.first() == 'w') { // Reset char spacing charSpacing = charSpacingPre; } } } while (cursor.first() != ']' && cursor.second() < text.size()) { cursor = Utf8::NextChar(text, cursor.second()); } } else { Rectf uvRect; if (cursor.first() < 128) { uvRect = _asciiChars[cursor.first()]; } else { auto it = _unicodeChars.find(cursor.first()); if (it != _unicodeChars.end()) { uvRect = it->second; } else { uvRect = _asciiChars[0]; } } if (uvRect.W > 0 && uvRect.H > 0) { if (useRandomColor) { const Colorf& newColor = RandomColors[charOffset % std::int32_t(arraySize(RandomColors))]; color = Colorf(newColor.R, newColor.G, newColor.B, color.A); } Vector2f pos = Vector2f(originPos); if (angleOffset > 0.0f) { float currentPhase = (phase + charOffset) * angleOffset * fPi; if (speed > 0.0f && (charOffset % 2) == 1) { currentPhase = -currentPhase; } pos.X += cosf(currentPhase) * varianceX * scale; pos.Y += sinf(currentPhase) * varianceY * scale; } pos.X = std::round(pos.X); pos.Y = std::round(pos.Y); std::int32_t charWidth = _charSize.X; if (charWidth > uvRect.W) { charWidth--; } Vector4f texCoords = Vector4f( charWidth / float(texSize.X), uvRect.X, uvRect.H / float(texSize.Y), uvRect.Y ); auto command = canvas->RentRenderCommand(); command->SetType(RenderCommand::Type::Text); bool shaderChanged = (colorizeShader ? command->GetMaterial().SetShader(colorizeShader) : command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::Sprite)); if (shaderChanged) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); // Required to reset render command properly //command->SetTransformation(command->transformation()); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); auto* instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatVector(texCoords.Data()); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(charWidth * scale, uvRect.H * scale); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(color.Data()); command->SetTransformation(Matrix4x4f::Translation(pos.X, pos.Y, 0.0f)); command->SetLayer(z - (charOffset & 1)); command->GetMaterial().SetTexture(*_texture.get()); canvas->_currentRenderQueue->AddCommand(command); originPos.X += ((uvRect.W + _baseSpacing) * scale * charSpacing); charOffset++; } } idx = std::int32_t(cursor.second()); } while (idx < textLength); charOffset++; } String Font::StripFormatting(StringView text) { if (text.empty()) { return {}; } SmallVector tempBuffer; for (std::size_t i = 0; i < text.size(); i++) { while (i + 1 < text.size() && text[i] == '\f' && text[i + 1] == '[') { i += 2; while (text[i] != L']' && text[i] != L'\0') { i++; } i++; } if (text[i] != L'\0') { tempBuffer.push_back(text[i]); } } return String(tempBuffer.data(), tempBuffer.size()); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Font.h000066400000000000000000000067041512772601700231360ustar00rootroot00000000000000#pragma once #include "Canvas.h" #include "../../nCine/Primitives/Colorf.h" #include "../../nCine/Primitives/Rect.h" #include "../../nCine/Base/HashMap.h" #include "../../nCine/Graphics/Texture.h" using namespace nCine; namespace Jazz2::UI { /** @brief Bitmap font renderer The renderer loads a bitmap font from a custom file format with full Unicode support and renders it to a canvas. It can also measure the size of a string without rendering it. @section Jazz2-UI-Font-format Text formatting The renderer supports inline text formatting using the @cpp "\f[…]" @ce notation. Unknown attributes are ignored. Following attributes are supported: - @cpp "\f[c:#RRGGBB]" @ce --- Sets the font color that can be specified in a hex value in RGB format - The game usually renders text using a colorization shader that uses a different color space to be able to change the text color more precisely, so the specified color must be adjusted accordingly - @cpp "\f[/c]" @ce --- Resets the font color - @cpp "\f[w:XX]" @ce --- Sets the character spacing as a percentage, recommended range is 80% to 120% - @cpp "\f[/w]" @ce --- Resets the character spacing */ class Font { public: /** @{ @name Constants */ /** @brief Default (yellow) font color */ static constexpr Colorf DefaultColor = Colorf(333.0f, 333.0f, 333.0f, 1.0f); /** @brief Default (yellow) font color with 60% transparency */ static constexpr Colorf TransparentDefaultColor = Colorf(333.0f, 333.0f, 333.0f, 0.6f); /** @brief Random (rainbow) font color */ static constexpr Colorf RandomColor = Colorf(444.0f, 444.0f, 444.0f, 0.5f); /** @brief Random (rainbow) font color with 60% transparency */ static constexpr Colorf TransparentRandomColor = Colorf(444.0f, 444.0f, 444.0f, 0.36f); /** @} */ Font(StringView path, const std::uint32_t* palette); /** @brief Returns font size in pixels */ std::int32_t GetSizeInPixels() const; /** @brief Returns font ascent in pixels */ std::int32_t GetAscentInPixels() const; /** @brief Returns size of a single character */ Vector2f MeasureChar(char32_t c) const; /** @brief Returns size of a string */ Vector2f MeasureString(StringView text, float scale = 1.0f, float charSpacing = 1.0f, float lineSpacing = 1.0f); /** @brief Returns size of a string and its cumulative widths */ Vector2f MeasureStringEx(StringView text, float scale, float charSpacing, float maxWidth, std::int32_t* charFit, float* charFitWidths); /** @brief Draws a string */ void DrawString(Canvas* canvas, StringView text, std::int32_t& charOffset, float x, float y, std::uint16_t z, Alignment align, Colorf color, float scale = 1.0f, float angleOffset = 0.0f, float varianceX = 4.0f, float varianceY = 4.0f, float speed = 0.4f, float charSpacing = 1.0f, float lineSpacing = 1.0f); /** @brief Strips formatting from the specified text */ static String StripFormatting(StringView text); private: static constexpr Colorf RandomColors[] = { Colorf(0.4f, 0.55f, 0.85f, 0.5f), Colorf(0.7f, 0.45f, 0.42f, 0.5f), Colorf(0.58f, 0.48f, 0.38f, 0.5f), Colorf(0.25f, 0.45f, 0.3f, 0.5f), Colorf(0.7f, 0.42f, 0.7f, 0.5f), Colorf(0.44f, 0.44f, 0.8f, 0.5f), Colorf(0.54f, 0.54f, 0.54f, 0.5f), Colorf(0.62f, 0.44f, 0.34f, 0.5f), Colorf(0.56f, 0.50f, 0.42f, 0.5f), }; Rectf _asciiChars[128]; HashMap _unicodeChars; Vector2i _charSize; std::int32_t _baseSpacing; std::unique_ptr _texture; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/FormattedTextBlock.cpp000066400000000000000000000750711512772601700263330ustar00rootroot00000000000000#include "FormattedTextBlock.h" using namespace Death::Containers; using namespace Death::Containers::Literals; namespace Jazz2::UI { FormattedTextBlock::Part::Part(std::uint32_t begin, std::uint32_t length, Vector2f location, float height, Colorf color, float scale, float charSpacing, bool allowVariance) noexcept : Begin(begin), Length(length), Location(location), Height(height), CurrentColor(color), Scale(scale), CharSpacing(charSpacing), AllowVariance(allowVariance) { } FormattedTextBlock::Part::Part(const Part& other) noexcept { Begin = other.Begin; Length = other.Length; Location = other.Location; Height = other.Height; CurrentColor = other.CurrentColor; Scale = other.Scale; CharSpacing = other.CharSpacing; AllowVariance = other.AllowVariance; } FormattedTextBlock::Part::Part(Part&& other) noexcept { Begin = other.Begin; Length = other.Length; Location = other.Location; Height = other.Height; CurrentColor = std::move(other.CurrentColor); Scale = other.Scale; CharSpacing = other.CharSpacing; AllowVariance = other.AllowVariance; } FormattedTextBlock::Part& FormattedTextBlock::Part::operator=(const Part& other) noexcept { Begin = other.Begin; Length = other.Length; Location = other.Location; Height = other.Height; CurrentColor = other.CurrentColor; Scale = other.Scale; CharSpacing = other.CharSpacing; AllowVariance = other.AllowVariance; return *this; } FormattedTextBlock::Part& FormattedTextBlock::Part::operator=(Part&& other) noexcept { Begin = other.Begin; Length = other.Length; Location = other.Location; Height = other.Height; CurrentColor = std::move(other.CurrentColor); Scale = other.Scale; CharSpacing = other.CharSpacing; AllowVariance = other.AllowVariance; return *this; } FormattedTextBlock::BackgroundPart::BackgroundPart(Colorf color) : CurrentColor(color) { } FormattedTextBlock::FormattedTextBlock() : _font(nullptr), _flags(FormattedTextBlockFlags::None), _defaultColor(Font::DefaultColor), _proposedWidth(4096), _cachedWidth(0), _defaultScale(1.0f), _defaultCharSpacing(1.0f), _defaultLineSpacing(1.0f), _alignment(Alignment::Left) { } FormattedTextBlock::FormattedTextBlock(const FormattedTextBlockParams& params) : _font(params.TextFont), _text(params.Text), _flags(FormattedTextBlockFlags::None), _defaultColor(params.Color), _proposedWidth(4096), _cachedWidth(0), _defaultScale(params.Scale), _defaultCharSpacing(params.CharSpacing), _defaultLineSpacing(params.LineSpacing), _alignment(params.Align) { } FormattedTextBlock::FormattedTextBlock(FormattedTextBlock&& other) noexcept { _parts = std::move(other._parts); _background = std::move(other._background); _font = std::move(other._font); _text = std::move(other._text); _flags = other._flags; _proposedWidth = other._proposedWidth; _cachedWidth = other._cachedWidth; _defaultColor = other._defaultColor; _defaultScale = other._defaultScale; _defaultCharSpacing = other._defaultCharSpacing; _defaultLineSpacing = other._defaultLineSpacing; _alignment = other._alignment; } FormattedTextBlock& FormattedTextBlock::operator=(FormattedTextBlock&& other) noexcept { _parts = std::move(other._parts); _background = std::move(other._background); _font = std::move(other._font); _text = std::move(other._text); _flags = other._flags; _proposedWidth = other._proposedWidth; _cachedWidth = other._cachedWidth; _defaultColor = other._defaultColor; _defaultScale = other._defaultScale; _defaultCharSpacing = other._defaultCharSpacing; _defaultLineSpacing = other._defaultLineSpacing; _alignment = other._alignment; return *this; } FormattedTextBlock FormattedTextBlock::From(const FormattedTextBlock& source) { FormattedTextBlock dest; dest._flags = source._flags; dest._font = source._font; dest._text = source._text; dest._defaultColor = source._defaultColor; dest._defaultScale = source._defaultScale; dest._defaultCharSpacing = source._defaultCharSpacing; dest._defaultLineSpacing = source._defaultLineSpacing; dest._alignment = source._alignment; return dest; } void FormattedTextBlock::Draw(Canvas* canvas, Rectf bounds, std::uint16_t depth, std::int32_t& charOffset, float angleOffset, float varianceX, float varianceY, float speed) { if (_parts.empty() || _proposedWidth != bounds.W) { _proposedWidth = bounds.W; RecreateCache(); } /*for (auto& part : _background) { if (part.Bounds.H > 5) { // TODO // Rounded rectangle Color outlineColor = (part.CurrentColor.GetR() + part.CurrentColor.GetG() + part.CurrentColor.GetB() < 3 * 128 ? Color::FromArgb(180, part.CurrentColor.GetR() + 60, part.CurrentColor.GetG() + 60, part.CurrentColor.GetB() + 60) : Color::FromArgb(110, part.CurrentColor.GetR() - 120, part.CurrentColor.GetG() - 120, part.CurrentColor.GetB() - 120)); g.FillRoundedRectangle(part.CurrentColor, outlineColor, part.Bounds.Offset(Point(bounds.X, bounds.Y))); } else { // Dotted line for (std::int32_t y = 0; y < part.Bounds.H; y++) { for (std::int32_t x = 1; x < part.Bounds.W; x += part.Bounds.H * 2) { g.FillPixelsUnsafe(part.CurrentColor, part.Bounds.X + bounds.X + x, part.Bounds.Y + bounds.Y + y, part.Bounds.H); } } } }*/ std::int32_t charOffsetShadow = charOffset; auto it = _parts.begin(); while (it != _parts.end()) { if (it->Location.Y + it->Height > bounds.H) { break; } Vector2f p = it->Location; p.X += bounds.X; p.Y += bounds.Y; String textPart = (it->Begin == Ellipsis ? "..."_s : StringView(_text.data() + it->Begin, it->Length)); _font->DrawString(canvas, textPart, charOffsetShadow, p.X, p.Y + 2.8f * _defaultScale, depth - 80, Alignment::Left, Colorf(0.0f, 0.0f, 0.0f, 0.29f), it->Scale, it->AllowVariance ? angleOffset : 0.0f, varianceX, varianceY, speed, it->CharSpacing); ++it; } it = _parts.begin(); while (it != _parts.end()) { if (it->Location.Y + it->Height > bounds.H) { break; } Vector2f p = it->Location; p.X += bounds.X; p.Y += bounds.Y; String textPart = (it->Begin == Ellipsis ? "..."_s : StringView(_text.data() + it->Begin, it->Length)); _font->DrawString(canvas, textPart, charOffset, p.X, p.Y, depth, Alignment::Left, it->CurrentColor, it->Scale, it->AllowVariance ? angleOffset : 0.0f, varianceX, varianceY, speed, it->CharSpacing); ++it; } } Vector2f FormattedTextBlock::MeasureSize(Vector2f proposedSize) { if (_parts.empty() || _proposedWidth != proposedSize.X) { _proposedWidth = proposedSize.X; RecreateCache(); } std::int32_t cachedHeight = GetCachedHeight(); if (cachedHeight <= 0) { return {}; } return Vector2f(_cachedWidth, cachedHeight); } float FormattedTextBlock::GetCachedWidth() const { return _cachedWidth; } float FormattedTextBlock::GetCachedHeight() const { if (_parts.empty()) { return 0.0f; } const Part& lastPart = _parts[_parts.size() - 1]; return lastPart.Location.Y + lastPart.Height; } void FormattedTextBlock::RecreateCache() { _flags &= ~FormattedTextBlockFlags::Ellipsized; _cachedWidth = 0; if (_text.empty()) { return; } _parts.clear(); _background.clear(); char* unprocessedText = _text.data(); std::int32_t unprocessedLength = (std::int32_t)_text.size(); SmallVector charFitWidths(DefaultInit, unprocessedLength + 1); Colorf currentColor = _defaultColor; float scale = _defaultScale; float charSpacing = _defaultCharSpacing; float lineSpacing = _defaultLineSpacing; enum class SkipTill { None, EndOfLine, EndOfHighlight }; enum class StyleIndex { Bold, Italic, Underline, DottedUnderline, Strikeout, AllowVariance, Count }; Vector2f currentLocation; std::int32_t firstPartOfLine = 0; std::int32_t lineBeginIndex = 0; std::int32_t lineAlignIndex = -1; std::int32_t backgroundIndex = -1; SkipTill skipTill = SkipTill::None; std::int32_t styleCount[(std::int32_t)StyleIndex::Count] {}; while (unprocessedLength > 0) { char* nextPtr = FindFirstControlSequence(unprocessedText, unprocessedLength); if (nextPtr == unprocessedText) { if (nextPtr[0] == '\f') { char* formatDescPtr = nextPtr + 2; char* formatEndPtr = strchr(formatDescPtr, ']'); if (formatEndPtr == nullptr) { LOGD("Missing closing format bracket in formatted string"); goto End; } std::int32_t formatLength = (std::int32_t)(formatEndPtr - formatDescPtr); if (formatLength > 0) { switch (formatDescPtr[0]) { case 'a': // Dotted underline if (formatLength == 1) { ++styleCount[(std::int32_t)StyleIndex::DottedUnderline]; } break; case 'c': // Color if ((formatLength == 9 || formatLength == 11) && formatDescPtr[1] == ':' && formatDescPtr[2] == '#') { formatDescPtr += 3; formatLength -= 3; wchar_t number[9]; std::int32_t i = 0; for (; i < formatLength; i++) { number[i] = *formatDescPtr; formatDescPtr++; } number[i] = '\0'; uint32_t color = wcstoul(number, nullptr, 16); if (formatLength == 6) { color |= 0xff000000; } // Swap red and blue channel, because Color stores it in 0xAABBGGRR format internally currentColor = Uint32ToColorf(color); } break; case 'u': // Underline if (formatLength == 1) { ++styleCount[(std::int32_t)StyleIndex::Underline]; } break; case 's': // Strikeout if (formatLength == 1) { ++styleCount[(std::int32_t)StyleIndex::Strikeout]; } break; case 'r': // Background color (highlight rectangle) if ((formatLength == 9 || formatLength == 11) && formatDescPtr[1] == ':' && formatDescPtr[2] == '#') { // TODO: Combination with align to right is not supported yet if (skipTill != SkipTill::EndOfLine && lineAlignIndex == -1) { formatDescPtr += 3; formatLength -= 3; wchar_t number[9]; std::int32_t i = 0; for (; i < formatLength; i++) { number[i] = *formatDescPtr; formatDescPtr++; } number[i] = '\0'; uint32_t color = wcstoul(number, nullptr, 16); if (formatLength == 6) { color |= 0xff000000; } if (backgroundIndex != -1) { // Finish previous unclosed highlight FinalizeBackgroundPart(currentLocation, _parts, _background); } backgroundIndex = (std::int32_t)_background.size(); // Swap red and blue channel, because Color stores it in 0xAABBGGRR format internally auto& part = _background.emplace_back(Uint32ToColorf(color)); part.Bounds.X = currentLocation.X - 3; part.Bounds.Y = currentLocation.Y; } } break; case '-': // Align to right if (formatLength == 1 && lineAlignIndex == -1) { lineAlignIndex = (std::int32_t)_parts.size(); } break; case '^': // Show rest only when multiline is disabled if (formatLength == 1 && IsMultiline()) { skipTill = SkipTill::EndOfLine; } break; case 'h': // Scale if (formatLength >= 3 && formatDescPtr[1] == ':') { char* end = nullptr; std::uint32_t paramValue = strtoul(&formatDescPtr[2], &end, 10); if (formatDescPtr + formatLength == end) { scale = _defaultScale * paramValue * 0.01f; } } break; case 'w': // Character spacing if (formatLength >= 3 && formatDescPtr[1] == ':') { char* end = nullptr; std::uint32_t paramValue = strtoul(&formatDescPtr[2], &end, 10); if (formatDescPtr + formatLength == end) { charSpacing = _defaultCharSpacing * paramValue * 0.01f; } } break; case 'j': // Allow variance if (formatLength == 1) { ++styleCount[(std::int32_t)StyleIndex::AllowVariance]; } break; case '/': { // End tag if (formatLength == 2) { switch (formatDescPtr[1]) { case 'a': { if (styleCount[(std::int32_t)StyleIndex::DottedUnderline] > 0) { --styleCount[(std::int32_t)StyleIndex::DottedUnderline]; } break; } case 'u': { if (styleCount[(std::int32_t)StyleIndex::Underline] > 0) { --styleCount[(std::int32_t)StyleIndex::Underline]; } break; } case 's': { if (styleCount[(std::int32_t)StyleIndex::Strikeout] > 0) { --styleCount[(std::int32_t)StyleIndex::Strikeout]; } break; } case 'c': { currentColor = _defaultColor; break; } case 'r': { if (backgroundIndex != -1) { FinalizeBackgroundPart(currentLocation, _parts, _background); backgroundIndex = -1; if (skipTill == SkipTill::EndOfHighlight) { skipTill = SkipTill::None; } } break; } case 'h': { scale = _defaultScale; break; } case 'w': { charSpacing = _defaultCharSpacing; break; } case 'j': { if (styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0) { --styleCount[(std::int32_t)StyleIndex::AllowVariance]; } break; } } } break; } } } formatEndPtr++; unprocessedLength -= (std::int32_t)(formatEndPtr - unprocessedText); unprocessedText = formatEndPtr; continue; } else if (nextPtr[0] == L'\n') { // New line HandleEndOfLine(currentLocation, lineBeginIndex, lineAlignIndex, backgroundIndex); float lineHeight = PerformVerticalAlignment(_parts, firstPartOfLine); firstPartOfLine = (std::int32_t)_parts.size(); if (lineHeight == -1) { Vector2f size = _font->MeasureStringEx("\n"_s, scale, charSpacing, _proposedWidth, nullptr, nullptr); lineHeight = size.Y; } if (!IsMultiline()) { goto End; } if (_cachedWidth < currentLocation.X) { _cachedWidth = currentLocation.X; } currentLocation.X = 0; currentLocation.Y += lineHeight; unprocessedLength--; unprocessedText++; skipTill = SkipTill::None; continue; } else if (nextPtr[0] == L'\t') { // Tabs std::int32_t tabCount = 0; do { unprocessedLength--; unprocessedText++; tabCount++; } while (unprocessedLength > 0 && unprocessedText[0] == L'\t'); // This has to be adjusted for bitmap font //std::int32_t tabWidth = _font->GetSizeInPixels() * 5 / 2; std::int32_t tabWidth = scale * _font->GetSizeInPixels() * 13 / 7; float remainder = fmodf(currentLocation.X, tabWidth); currentLocation.X += (tabWidth * tabCount) - remainder; if (currentLocation.X >= _proposedWidth) { currentLocation.X = _proposedWidth; if (!IsMultiline()) { goto End; } lineAlignIndex = -1; if ((_flags & (FormattedTextBlockFlags::Multiline | FormattedTextBlockFlags::Wrapping)) == (FormattedTextBlockFlags::Multiline | FormattedTextBlockFlags::Wrapping)) { std::int32_t lineHeight = PerformVerticalAlignment(_parts, firstPartOfLine); firstPartOfLine = (std::int32_t)_parts.size(); if (_cachedWidth < currentLocation.X) { _cachedWidth = currentLocation.X; } currentLocation.X = 0; currentLocation.Y += lineHeight; skipTill = SkipTill::None; } else { skipTill = SkipTill::EndOfLine; } } continue; } else if (nextPtr[0] == '\0') { // Skip NULL terminators - they shouldn't be inside strings anyway unprocessedLength--; unprocessedText++; continue; } } if (skipTill != SkipTill::None) { // Wrapping is not enabled, so skip everything till end of line, but preserve formatting unprocessedLength -= (std::int32_t)(nextPtr - unprocessedText); unprocessedText = nextPtr; continue; } std::int32_t lengthToMeasure = (std::int32_t)(nextPtr - unprocessedText); float maxWidth = _proposedWidth - currentLocation.X; std::int32_t charFit; Vector2f size = _font->MeasureStringEx(StringView(unprocessedText, lengthToMeasure), scale, charSpacing, maxWidth, &charFit, charFitWidths.data()); float charFitWidth = (charFit > 0 ? charFitWidths[charFit - 1] : 0.0f); if (charFit >= lengthToMeasure) { // All the characters fit char* toPtr = (nextPtr[0] == L'\n' && nextPtr[-1] == L'\r' ? nextPtr - 1 : nextPtr); std::int32_t partLength = (std::int32_t)(toPtr - unprocessedText); Part& part = _parts.emplace_back((std::uint32_t)(unprocessedText - _text.data()), partLength, currentLocation, size.Y * lineSpacing, currentColor, scale, charSpacing, styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0); if (nextPtr[0] == L'\n') { if (partLength > 0) { if (styleCount[(std::int32_t)StyleIndex::DottedUnderline] > 0) { InsertDottedUnderline(part, charFitWidths[partLength - 1]); } currentLocation.X += charFitWidths[partLength - 1]; } HandleEndOfLine(currentLocation, lineBeginIndex, lineAlignIndex, backgroundIndex); float lineHeight = PerformVerticalAlignment(_parts, firstPartOfLine); firstPartOfLine = (std::int32_t)_parts.size(); if (!IsMultiline()) { goto End; } if (_cachedWidth < currentLocation.X) { _cachedWidth = currentLocation.X; } currentLocation.X = 0; currentLocation.Y += lineHeight; // Skip the new line and also all spaces that follow do { nextPtr++; } while (nextPtr[0] == L' '); skipTill = SkipTill::None; } else { if (styleCount[(std::int32_t)StyleIndex::DottedUnderline] > 0 && charFitWidth > 0) { InsertDottedUnderline(part, charFitWidth); } currentLocation.X += charFitWidth; } unprocessedLength -= (std::int32_t)(nextPtr - unprocessedText); unprocessedText = nextPtr; } else { // There's not enough room for all the characters if ((_flags & (FormattedTextBlockFlags::Multiline | FormattedTextBlockFlags::Wrapping)) == (FormattedTextBlockFlags::Multiline | FormattedTextBlockFlags::Wrapping)) { // The smallest part doesn't fit (and clipping is not supported), so it can't continue if (charFit == 0 && firstPartOfLine == _parts.size()) { goto End; } if (backgroundIndex != -1) { // If highlighting is active, trim it only, don't wrap it if (charFit > 2) { charFit -= 2; Part& part = _parts.emplace_back((std::uint32_t)(unprocessedText - _text.data()), charFit, currentLocation, size.Y * lineSpacing, currentColor, scale, charSpacing, styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0); if (styleCount[(std::int32_t)StyleIndex::DottedUnderline] > 0) { InsertDottedUnderline(part, charFitWidths[charFit - 1]); } currentLocation.X += charFitWidths[charFit - 1]; maxWidth -= charFitWidths[charFit - 1]; } InsertEllipsis(currentLocation, currentColor, scale, charSpacing, lineSpacing, styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0, maxWidth, charFitWidths.data()); skipTill = SkipTill::EndOfHighlight; continue; } else { char* lastWhitespacePtr; bool wasWhitespace = true; if (charFit > 0) { // Go backwards and try to find whitespace (or splittable character if it's not the first one) lastWhitespacePtr = unprocessedText + charFit; if (lastWhitespacePtr[0] != L' ' && lastWhitespacePtr[0] != L'\t') { lastWhitespacePtr--; } while (unprocessedText <= lastWhitespacePtr) { if (lastWhitespacePtr[0] == L' ' || lastWhitespacePtr[0] == L'\t' || lastWhitespacePtr[0] == L'/' || lastWhitespacePtr[0] == L'\\') { break; } lastWhitespacePtr--; } // Check if there is only whitespace in the current part, then don't split it here if (unprocessedText < lastWhitespacePtr) { bool onlyWhitespace = true; char* ptr = unprocessedText; do { if (ptr[0] != L' ' && ptr[0] != L'\t') { onlyWhitespace = false; break; } ptr++; } while (ptr < lastWhitespacePtr); if (onlyWhitespace) { lastWhitespacePtr = nullptr; } } else { lastWhitespacePtr = nullptr; } // If split point was not found yet, or there is a lot of space left, or it's the first path on current line, create largest possible part instead if (lastWhitespacePtr == nullptr && (maxWidth > 40 || firstPartOfLine == _parts.size())) { lastWhitespacePtr = unprocessedText + charFit; wasWhitespace = false; } else if (lastWhitespacePtr != nullptr && (lastWhitespacePtr[0] == L'/' || lastWhitespacePtr[0] == L'\\')) { // If it's splittable character, keep it on the current line lastWhitespacePtr++; wasWhitespace = false; } } else { // No character can fit lastWhitespacePtr = nullptr; } if (lastWhitespacePtr != nullptr) { std::int32_t partLength = (std::int32_t)(lastWhitespacePtr - unprocessedText); Part& part = _parts.emplace_back((std::uint32_t)(unprocessedText - _text.data()), partLength, currentLocation, size.Y * lineSpacing, currentColor, scale, charSpacing, styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0); if (styleCount[(std::int32_t)StyleIndex::DottedUnderline] > 0) { InsertDottedUnderline(part, charFitWidths[partLength - 1]); } currentLocation.X += charFitWidths[partLength - 1]; if (wasWhitespace) { lastWhitespacePtr++; partLength++; } unprocessedLength -= partLength; unprocessedText = lastWhitespacePtr; } else { // If no characters can fit, at least trim all leading whitespace from the next line while (unprocessedLength > 0 && unprocessedText[0] == ' ') { unprocessedLength--; unprocessedText++; } } } // Split alignment is not allowed on wrapped line lineAlignIndex = -1; HandleEndOfLine(currentLocation, lineBeginIndex, lineAlignIndex, backgroundIndex); float lineHeight = PerformVerticalAlignment(_parts, firstPartOfLine); if (_cachedWidth < currentLocation.X) { _cachedWidth = currentLocation.X; } currentLocation.X = 0; currentLocation.Y += lineHeight; } else { if (charFit > 2) { charFit -= 2; Part& part = _parts.emplace_back((std::uint32_t)(unprocessedText - _text.data()), charFit, currentLocation, size.Y * lineSpacing, currentColor, scale, charSpacing, styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0); if (styleCount[(std::int32_t)StyleIndex::DottedUnderline] > 0) { InsertDottedUnderline(part, charFitWidths[charFit - 1]); } currentLocation.X += charFitWidths[charFit - 1]; maxWidth -= charFitWidths[charFit - 1]; } InsertEllipsis(currentLocation, currentColor, scale, charSpacing, lineSpacing, styleCount[(std::int32_t)StyleIndex::AllowVariance] > 0, maxWidth, charFitWidths.data()); _flags |= FormattedTextBlockFlags::Ellipsized; if (!IsMultiline()) { goto End; } if (nextPtr[0] == L'\n') { nextPtr++; unprocessedLength -= (std::int32_t)(nextPtr - unprocessedText); unprocessedText = nextPtr; float lineHeight = PerformVerticalAlignment(_parts, firstPartOfLine); if (_cachedWidth < currentLocation.X) { _cachedWidth = currentLocation.X; } currentLocation.X = 0; currentLocation.Y += lineHeight; } else { unprocessedLength -= (std::int32_t)(nextPtr - unprocessedText); unprocessedText = nextPtr; lineAlignIndex = -1; skipTill = SkipTill::EndOfLine; continue; } } lineAlignIndex = -1; firstPartOfLine = (std::int32_t)_parts.size(); skipTill = SkipTill::None; } } End: HandleEndOfLine(currentLocation, lineBeginIndex, lineAlignIndex, backgroundIndex); PerformVerticalAlignment(_parts, firstPartOfLine); if (_cachedWidth < currentLocation.X) { _cachedWidth = currentLocation.X; } } void FormattedTextBlock::HandleEndOfLine(Vector2f currentLocation, std::int32_t& lineBeginIndex, std::int32_t& lineAlignIndex, std::int32_t& backgroundIndex) { if (backgroundIndex != -1) { auto& part = _background[backgroundIndex]; if (part.Bounds.X == currentLocation.X - 3) { _background.pop_back(); } else { part.Bounds.W = currentLocation.X - part.Bounds.X + 3; auto& lastPart = _parts[_parts.size() - 1]; part.Bounds.H = lastPart.Height + 1; } backgroundIndex = -1; } if (lineAlignIndex != -1) { // Perform split alignment float offset = (_proposedWidth - currentLocation.X); for (std::int32_t i = lineAlignIndex; i < (std::int32_t)_parts.size(); i++) { Part& partRef = _parts[i]; partRef.Location.X += offset; } lineAlignIndex = -1; } else { // Perform standard alignment Alignment horizontalAlign = (_alignment & Alignment::HorizontalMask); if (horizontalAlign == Alignment::Center || horizontalAlign == Alignment::Right) { float offset = (_proposedWidth - currentLocation.X); if (horizontalAlign == Alignment::Center) { offset *= 0.5f; } for (std::int32_t i = lineBeginIndex; i < (std::int32_t)_parts.size(); i++) { Part& partRef = _parts[i]; partRef.Location.X += offset; } } } lineBeginIndex = (std::int32_t)_parts.size(); } void FormattedTextBlock::InsertEllipsis(Vector2f& currentLocation, Colorf currentColor, float scale, float charSpacing, float lineSpacing, bool allowVariance, float maxWidth, float* charFitWidths) { std::int32_t charFit; Vector2f size = _font->MeasureStringEx("..."_s, scale, charSpacing, maxWidth, &charFit, charFitWidths); if (charFit > 0) { _parts.emplace_back(Ellipsis, charFit, currentLocation, size.Y * lineSpacing, currentColor, scale, charSpacing, allowVariance); currentLocation.X += charFitWidths[charFit - 1]; } } void FormattedTextBlock::InsertDottedUnderline(Part& part, float width) { std::int32_t ascent = _font->GetAscentInPixels(); // Max. thickness is 5px, because it's shared also with rounded rectangles (with height >= 6px) std::int32_t thickness = std::clamp(ascent / 16, 1, 5); BackgroundPart& underline = _background.emplace_back(part.CurrentColor); underline.Bounds.X = part.Location.X; underline.Bounds.Y = part.Location.Y + ((part.Height + ascent + 1) / 2) - thickness; // Perfectly aligned with Segoe UI font underline.Bounds.W = width; underline.Bounds.H = thickness; } float FormattedTextBlock::PerformVerticalAlignment(SmallVectorImpl& processedParts, std::int32_t firstPartOfLine) { float maxHeight = -1.0f; for (std::size_t i = firstPartOfLine; i < processedParts.size(); i++) { maxHeight = std::max(maxHeight, processedParts[i].Height); } for (std::size_t i = firstPartOfLine; i < processedParts.size(); i++) { Part& lastPart = processedParts[i]; if (lastPart.Height != maxHeight) { float diff = floorf((maxHeight - lastPart.Height) * 0.2f); lastPart.Location.Y += diff; lastPart.Height = maxHeight - diff; } } return maxHeight; } void FormattedTextBlock::FinalizeBackgroundPart(Vector2f currentLocation, SmallVectorImpl& parts, SmallVectorImpl& backgroundParts) { auto& background = backgroundParts[backgroundParts.size() - 1]; if (background.Bounds.X == currentLocation.X - 3) { backgroundParts.erase(&background); } else { background.Bounds.W = currentLocation.X - background.Bounds.X + 3; auto* lastPart = &parts[parts.size() - 1]; background.Bounds.H = lastPart->Height + 1; } } char* FormattedTextBlock::FindFirstControlSequence(char* string, std::int32_t length) { for (std::int32_t i = 0; i < length; i++) { if (string[i] == '\0' || string[i] == '\n' || string[i] == '\t' || (string[i] == '\f' && string[i + 1] == '[')) { return &string[i]; } } return string + length; } void FormattedTextBlock::SetAlignment(Alignment value) { if (_alignment == value) { return; } _alignment = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetDefaultColor(Colorf color) { if (_defaultColor == color) { return; } _defaultColor = color; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetFont(Font* value) { if (_font == value) { return; } _font = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetScale(float value) { if (_defaultScale == value) { return; } _defaultScale = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetCharSpacing(float value) { if (_defaultCharSpacing == value) { return; } _defaultCharSpacing = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetLineSpacing(float value) { if (_defaultLineSpacing == value) { return; } _defaultLineSpacing = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetProposedWidth(float value) { if (_proposedWidth == value) { return; } _proposedWidth = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetText(StringView value) { if (_text == value) { return; } _text = value; _parts.clear(); _background.clear(); } void FormattedTextBlock::SetText(String&& value) { if (_text == value) { return; } _text = std::move(value); _parts.clear(); _background.clear(); } void FormattedTextBlock::SetMultiline(bool value) { if (((_flags & FormattedTextBlockFlags::Multiline) == FormattedTextBlockFlags::Multiline) == value) { return; } if (value) { _flags |= FormattedTextBlockFlags::Multiline; } else { _flags &= ~FormattedTextBlockFlags::Multiline; } _parts.clear(); _background.clear(); } void FormattedTextBlock::SetWrapping(bool value) { if (((_flags & FormattedTextBlockFlags::Wrapping) == FormattedTextBlockFlags::Wrapping) == value) { return; } if (value) { _flags |= FormattedTextBlockFlags::Wrapping; } else { _flags &= ~FormattedTextBlockFlags::Wrapping; } _parts.clear(); _background.clear(); } Colorf FormattedTextBlock::Uint32ToColorf(std::uint32_t value) { return Colorf((float)((value & 0x00ff0000) >> 16) / 255.0f, (float)((value & 0x0000ff00) >> 8) / 255.0f, (float)(value & 0x000000ff) / 255.0f, (float)((value & 0xff000000) >> 24) / 255.0f); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/FormattedTextBlock.h000066400000000000000000000113301512772601700257640ustar00rootroot00000000000000#pragma once #include "Canvas.h" #include "Font.h" #include "../../nCine/Primitives/Rect.h" #include "../../nCine/Primitives/Vector2.h" #include #include #include using namespace Death::Containers; using namespace nCine; namespace Jazz2::UI { /** @brief Initialization parameters for formatted text block */ struct FormattedTextBlockParams { StringView Text; Font* TextFont = nullptr; Alignment Align = Alignment::Left; Colorf Color = Font::DefaultColor; float Scale = 1.0f; float CharSpacing = 1.0f; float LineSpacing = 1.0f; }; /** @brief Formatted text block */ class FormattedTextBlock { public: FormattedTextBlock(); FormattedTextBlock(const FormattedTextBlockParams& params); FormattedTextBlock(const FormattedTextBlock&) = delete; FormattedTextBlock(FormattedTextBlock&& other) noexcept; FormattedTextBlock& operator=(const FormattedTextBlock&) = delete; FormattedTextBlock& operator=(FormattedTextBlock&& other) noexcept; static FormattedTextBlock From(const FormattedTextBlock& source); void Draw(Canvas* canvas, Rectf bounds, std::uint16_t depth, std::int32_t& charOffset, float angleOffset = 0.0f, float varianceX = 4.0f, float varianceY = 4.0f, float speed = 0.4f); Vector2f MeasureSize(Vector2f proposedSize); float GetCachedWidth() const; float GetCachedHeight() const; Alignment GetAlignment() const { return _alignment; } void SetAlignment(Alignment value); Colorf GetDefaultColor() const { return _defaultColor; } void SetDefaultColor(Colorf color); Font* GetFont() { return _font; } void SetFont(Font* value); float GetScale() const { return _defaultScale; } void SetScale(float value); float GetCharSpacing() const { return _defaultCharSpacing; } void SetCharSpacing(float value); float GetLineSpacing() const { return _defaultLineSpacing; } void SetLineSpacing(float value); constexpr float GetProposedWidth() const { return _proposedWidth; } void SetProposedWidth(float value); StringView GetText() const { return _text; } void SetText(StringView value); void SetText(String&& value); constexpr bool IsMultiline() const { return (_flags & FormattedTextBlockFlags::Multiline) == FormattedTextBlockFlags::Multiline; } void SetMultiline(bool value); constexpr bool GetWrapping() const { return (_flags & FormattedTextBlockFlags::Wrapping) == FormattedTextBlockFlags::Wrapping; } void SetWrapping(bool value); constexpr bool IsEllipsized() const { return (_flags & FormattedTextBlockFlags::Ellipsized) == FormattedTextBlockFlags::Ellipsized; } private: static constexpr std::uint32_t Ellipsis = UINT32_MAX; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct Part { std::uint32_t Begin; std::uint32_t Length; Vector2f Location; float Height; Colorf CurrentColor; float Scale; float CharSpacing; bool AllowVariance; Part(std::uint32_t begin, std::uint32_t length, Vector2f location, float height, Colorf color, float scale, float charSpacing, bool allowVariance) noexcept; Part(const Part& other) noexcept; Part(Part&& other) noexcept; Part& operator=(const Part& other) noexcept; Part& operator=(Part&& other) noexcept; }; struct BackgroundPart { Rectf Bounds; Colorf CurrentColor; BackgroundPart(Colorf color); }; #endif enum class FormattedTextBlockFlags : std::uint16_t { None = 0, Multiline = 0x01, Wrapping = 0x02, Ellipsized = 0x10 }; DEATH_PRIVATE_ENUM_FLAGS(FormattedTextBlockFlags); SmallVector _parts; SmallVector _background; Font* _font; String _text; FormattedTextBlockFlags _flags; float _proposedWidth; float _cachedWidth; Colorf _defaultColor; float _defaultScale; float _defaultCharSpacing; float _defaultLineSpacing; Alignment _alignment; void RecreateCache(); void HandleEndOfLine(Vector2f currentLocation, std::int32_t& lineBeginIndex, std::int32_t& lineAlignIndex, std::int32_t& backgroundIndex); void InsertEllipsis(Vector2f& currentLocation, Colorf currentColor, float scale, float charSpacing, float lineSpacing, bool allowVariance, float maxWidth, float* charFitWidths); void InsertDottedUnderline(Part& part, float width); static float PerformVerticalAlignment(SmallVectorImpl& processedParts, std::int32_t firstPartOfLine); static void FinalizeBackgroundPart(Vector2f currentLocation, SmallVectorImpl& parts, SmallVectorImpl& backgroundParts); static char* FindFirstControlSequence(char* string, std::int32_t length); static Colorf Uint32ToColorf(std::uint32_t value); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/HUD.cpp000066400000000000000000002034011512772601700231740ustar00rootroot00000000000000#include "HUD.h" #include "InGameConsole.h" #include "Menu/IMenuContainer.h" #include "../ContentResolver.h" #include "../PreferencesCache.h" #include "../Actors/Enemies/Bosses/BossBase.h" #if defined(WITH_ANGELSCRIPT) # include "../Scripting/LevelScriptLoader.h" #endif #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Application.h" #if defined(DEATH_TARGET_ANDROID) # include "../../nCine/Backends/Android/AndroidApplication.h" #endif // Position of key in 22x6 grid static const std::uint8_t KeyLayout[] = { 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 84, 85, 86, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 101, 104, 106, 107, 108, 109, 110, 111, 112, 116, 120, 121, 122, 123, 125, 126, 127, 128, 130 }; namespace Jazz2::UI { namespace Resources { static constexpr AnimState WeaponBlasterJazz = (AnimState)0; static constexpr AnimState WeaponBlasterSpaz = (AnimState)1; static constexpr AnimState WeaponBlasterLori = (AnimState)2; static constexpr AnimState WeaponBouncer = (AnimState)3; static constexpr AnimState WeaponFreezer = (AnimState)4; static constexpr AnimState WeaponSeeker = (AnimState)5; static constexpr AnimState WeaponRF = (AnimState)6; static constexpr AnimState WeaponToaster = (AnimState)7; static constexpr AnimState WeaponTNT = (AnimState)8; static constexpr AnimState WeaponPepper = (AnimState)9; static constexpr AnimState WeaponElectro = (AnimState)10; static constexpr AnimState WeaponThunderbolt = (AnimState)11; static constexpr AnimState WeaponPowerUpBlasterJazz = (AnimState)20; static constexpr AnimState WeaponPowerUpBlasterSpaz = (AnimState)21; static constexpr AnimState WeaponPowerUpBlasterLori = (AnimState)22; static constexpr AnimState WeaponPowerUpBouncer = (AnimState)23; static constexpr AnimState WeaponPowerUpFreezer = (AnimState)24; static constexpr AnimState WeaponPowerUpSeeker = (AnimState)25; static constexpr AnimState WeaponPowerUpRF = (AnimState)26; static constexpr AnimState WeaponPowerUpToaster = (AnimState)27; static constexpr AnimState WeaponPowerUpTNT = (AnimState)28; static constexpr AnimState WeaponPowerUpPepper = (AnimState)29; static constexpr AnimState WeaponPowerUpElectro = (AnimState)30; static constexpr AnimState WeaponPowerUpThunderbolt = (AnimState)31; static constexpr AnimState WeaponToasterDisabled = (AnimState)47; static constexpr AnimState CharacterJazz = (AnimState)60; static constexpr AnimState CharacterSpaz = (AnimState)61; static constexpr AnimState CharacterLori = (AnimState)62; static constexpr AnimState CharacterFrog = (AnimState)63; static constexpr AnimState Heart = (AnimState)70; static constexpr AnimState PickupGemRed = (AnimState)71; static constexpr AnimState PickupGemGreen = (AnimState)72; static constexpr AnimState PickupGemBlue = (AnimState)73; static constexpr AnimState PickupGemPurple = (AnimState)74; static constexpr AnimState PickupCoin = (AnimState)75; static constexpr AnimState PickupFood = (AnimState)76; static constexpr AnimState PickupCarrot = (AnimState)77; static constexpr AnimState PickupStopwatch = (AnimState)78; static constexpr AnimState BossHealthBar = (AnimState)79; static constexpr AnimState WeaponWheel = (AnimState)80; static constexpr AnimState WeaponWheelInner = (AnimState)81; static constexpr AnimState WeaponWheelDim = (AnimState)82; static constexpr AnimState TouchDpad = (AnimState)100; static constexpr AnimState TouchFire = (AnimState)101; static constexpr AnimState TouchJump = (AnimState)102; static constexpr AnimState TouchRun = (AnimState)103; static constexpr AnimState TouchChange = (AnimState)104; static constexpr AnimState TouchPause = (AnimState)105; static constexpr AnimState TouchClose = (AnimState)106; } using namespace Jazz2::UI::Resources; HUD::HUD(LevelHandler* levelHandler) : _levelHandler(levelHandler), _metadata(nullptr), _levelTextTime(-1.0f), _coins(0), _gems(0), _coinsTime(-1.0f), _gemsTime(-1.0f), _activeBossTime(0.0f), _touchButtonsTimer(0.0f), _rgbAmbientLight(0.0f), _rgbHealthLast(0.0f), _rgbLightsAnim(0.0f), _rgbLightsTime(0.0f), _transitionState(TransitionState::WaitingForFadeIn), _transitionTime(1.0f) { auto& resolver = ContentResolver::Get(); _metadata = resolver.RequestMetadata("UI/HUD"_s); _smallFont = resolver.GetFont(FontType::Small); _touchButtons[0] = CreateTouchButton(PlayerAction::None, TouchDpad, Alignment::BottomLeft, DpadLeft, DpadBottom, DpadSize, DpadSize); // D-pad subsections _touchButtons[1] = CreateTouchButton(PlayerAction::Up, AnimState::Default, Alignment::BottomLeft, DpadLeft, DpadBottom + (DpadSize * 2 / 3), DpadSize, (DpadSize / 3) + DpadThreshold); _touchButtons[2] = CreateTouchButton(PlayerAction::Down, AnimState::Default, Alignment::BottomLeft, DpadLeft, DpadBottom - DpadThreshold, DpadSize, (DpadSize / 3) + DpadThreshold); _touchButtons[3] = CreateTouchButton(PlayerAction::Left, AnimState::Default, Alignment::BottomLeft | AllowRollover, DpadLeft - DpadThreshold, DpadBottom, (DpadSize / 3) + DpadThreshold, DpadSize); _touchButtons[4] = CreateTouchButton(PlayerAction::Right, AnimState::Default, Alignment::BottomLeft | AllowRollover, DpadLeft + (DpadSize * 2 / 3), DpadBottom, (DpadSize / 3) + DpadThreshold, DpadSize); // Action buttons _touchButtons[5] = CreateTouchButton(PlayerAction::Fire, TouchFire, Alignment::BottomRight, (ButtonSize + 0.02f) * 2, 0.04f, ButtonSize, ButtonSize); _touchButtons[6] = CreateTouchButton(PlayerAction::Jump, TouchJump, Alignment::BottomRight, (ButtonSize + 0.02f), 0.04f + 0.08f, ButtonSize, ButtonSize); _touchButtons[7] = CreateTouchButton(PlayerAction::Run, TouchRun, Alignment::BottomRight, 0.001f, 0.01f + 0.15f, ButtonSize, ButtonSize); _touchButtons[8] = CreateTouchButton(PlayerAction::ChangeWeapon, TouchChange, Alignment::BottomRight, ButtonSize + 0.01f, 0.04f + 0.28f, SmallButtonSize, SmallButtonSize); #if defined(DEATH_TARGET_ANDROID) if (static_cast(theApplication()).IsScreenRound()) { _touchButtons[9] = CreateTouchButton(PlayerAction::Menu, TouchPause, Alignment::Top | Fixed, 0.0f, 0.02f, SmallButtonSize, SmallButtonSize); _touchButtons[10] = {}; } else #endif { _touchButtons[9] = CreateTouchButton(PlayerAction::Menu, TouchPause, Alignment::TopRight | Fixed, 0.02f, 0.02f, SmallButtonSize, SmallButtonSize); _touchButtons[10] = CreateTouchButton(PlayerAction::Console, AnimState::Default, Alignment::TopLeft | Fixed, 0.02f, 0.02f, SmallButtonSize, SmallButtonSize); } } HUD::~HUD() { #if !defined(DEATH_TARGET_ANDROID) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_WINDOWS_RT) if (PreferencesCache::EnableRgbLights) { RgbLights::Get().Clear(); } #endif } void HUD::OnUpdate(float timeMult) { Canvas::OnUpdate(timeMult); if (_levelTextTime >= 0.0f) { _levelTextTime += timeMult; } if (_touchButtonsTimer > 0.0f) { _touchButtonsTimer -= timeMult; } switch (_transitionState) { case TransitionState::FadeIn: _transitionTime += 0.025f * timeMult; if (_transitionTime >= 1.0f) { _transitionState = TransitionState::None; } break; case TransitionState::FadeOut: if (_transitionTime > 0.0f) { _transitionTime -= 0.025f * timeMult; if (_transitionTime < 0.0f) { _transitionTime = 0.0f; } } break; case TransitionState::WaitingForFadeOut: _transitionTime -= timeMult; if (_transitionTime <= 0.0f) { _transitionState = TransitionState::FadeOut; _transitionTime = 1.0f; } break; } if (!_levelHandler->_assignedViewports.empty()) { if (_coinsTime >= 0.0f) { _coinsTime += timeMult; } if (_gemsTime >= 0.0f) { _gemsTime += timeMult; } if (_levelHandler->_activeBoss != nullptr) { _activeBossTime += timeMult; constexpr float TransitionTime = 60.0f; if (_activeBossTime > TransitionTime) { _activeBossTime = TransitionTime; } } else { _activeBossTime = 0.0f; } UpdateWeaponWheel(timeMult); UpdateRgbLights(timeMult, _levelHandler->_assignedViewports[0].get()); } } bool HUD::OnDraw(RenderQueue& renderQueue) { Canvas::OnDraw(renderQueue); if (_metadata == nullptr) { return false; } ViewSize = _levelHandler->GetViewSize(); Rectf view = Rectf(0.0f, 0.0f, static_cast(ViewSize.X), static_cast(ViewSize.Y)); Rectf adjustedView = view; #if defined(DEATH_TARGET_ANDROID) if (static_cast(theApplication()).IsScreenRound()) { float newWidth = roundf(adjustedView.W * 0.5f); adjustedView.X += roundf((adjustedView.W - newWidth) * 0.5f); adjustedView.W = newWidth; adjustedView.H = roundf(adjustedView.H * 0.95f); } else #endif if (_touchButtonsTimer > 0.0f) { adjustedView.X = 140.0f + PreferencesCache::TouchLeftPadding.X; adjustedView.W = adjustedView.W - adjustedView.X - (195.0f + PreferencesCache::TouchRightPadding.X); } std::int32_t charOffset = 0; char stringBuffer[32]; auto players = _levelHandler->GetPlayers(); for (std::size_t i = 0; i < _levelHandler->_assignedViewports.size(); i++) { auto& viewport = _levelHandler->_assignedViewports[i]; if (auto* player = runtime_cast(viewport->GetTargetActor())) { Rectf scopedView = viewport->GetBounds(); Rectf adjustedScopedView = scopedView; float left = std::max(adjustedScopedView.X, adjustedView.X); float right = std::min(adjustedScopedView.X + adjustedScopedView.W, adjustedView.X + adjustedView.W); adjustedScopedView.X = left; adjustedScopedView.W = right - left; #if defined(DEATH_TARGET_ANDROID) if (static_cast(theApplication()).IsScreenRound() && _levelHandler->_assignedViewports.size() == 1) { adjustedScopedView.H = roundf(adjustedScopedView.H * 0.95f); } #endif OnDrawHealth(scopedView, adjustedScopedView, player); OnDrawScore(scopedView, player); OnDrawWeaponAmmo(adjustedScopedView, player); DrawWeaponWheel(scopedView, player); } } DrawViewportSeparators(); OnDrawCoins(view, charOffset); OnDrawGems(view, charOffset); OnDrawActiveBoss(adjustedView); OnDrawLevelText(charOffset); #if defined(DEATH_DEBUG) /*if (PreferencesCache::ShowPerformanceMetrics && !_levelHandler->_assignedViewports.empty()) { auto& viewport = _levelHandler->_assignedViewports[0]; for (const auto& actor : _levelHandler->_actors) { DrawSolid(actor->GetPos() - actor->AABB.GetExtents() - viewport->_cameraPos + viewport->GetBounds().GetSize() * 0.5f, 10, actor->AABB.GetExtents() * 2.0f, Colorf(0.0f, 0.0f, 0.0f, 0.3f), false); DrawSolid(actor->GetPos() - actor->AABBInner.GetExtents() - viewport->_cameraPos + viewport->GetBounds().GetSize() * 0.5f, 12, actor->AABBInner.GetExtents() * 2.0f, Colorf(1.0f, 1.0f, 1.0f, 0.3f), true); } }*/ #endif // Touch Controls if (_touchButtonsTimer > 0.0f) { auto* controlledPlayer = (!players.empty() ? players[0] : nullptr); OnDrawTouchButtons(controlledPlayer); } // Performance Metrics if (PreferencesCache::ShowPerformanceMetrics) { i32tos((std::int32_t)std::round(theApplication().GetFrameTimer().GetAverageFps()), stringBuffer); #if defined(DEATH_TARGET_ANDROID) if (static_cast(theApplication()).IsScreenRound()) { _smallFont->DrawString(this, stringBuffer, charOffset, view.W / 2 + 40.0f, view.Y + 6.0f, FontLayer, Alignment::TopRight, Font::DefaultColor, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.96f); } else #endif _smallFont->DrawString(this, stringBuffer, charOffset, view.W - 4.0f, view.Y + 1.0f, FontLayer, Alignment::TopRight, Font::DefaultColor, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.96f); } if (_transitionState >= TransitionState::WaitingForFadeIn && _transitionState <= TransitionState::FadeOut) { auto command = RentRenderCommand(); if (command->GetMaterial().SetShader(ContentResolver::Get().GetShader(PrecompiledShader::Transition))) { command->GetMaterial().ReserveUniformsDataMemory(); command->GetGeometry().SetDrawParameters(GL_TRIANGLE_STRIP, 0, 4); } command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatVector(Vector4f(1.0f, 0.0f, 1.0f, 0.0f).Data()); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatVector(Vector2f(static_cast(ViewSize.X), static_cast(ViewSize.Y)).Data()); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(Colorf(0.0f, 0.0f, 0.0f, _transitionTime).Data()); command->SetTransformation(Matrix4x4f::Identity); command->SetLayer(600); renderQueue.AddCommand(command); } return true; } void HUD::OnTouchEvent(const TouchEvent& event, uint32_t& overrideActions) { _touchButtonsTimer = 1200.0f; if (event.type == TouchEventType::Down || event.type == TouchEventType::PointerDown) { int32_t pointerIndex = event.findPointerIndex(event.actionIndex); if (pointerIndex != -1) { float x = event.pointers[pointerIndex].x * (float)ViewSize.X; float y = event.pointers[pointerIndex].y * (float)ViewSize.Y; for (std::uint32_t i = 0; i < TouchButtonsCount; i++) { auto& button = _touchButtons[i]; if (button.Action != PlayerAction::None) { if (button.CurrentPointerId == -1 && IsOnButton(button, x, y)) { button.CurrentPointerId = event.actionIndex; overrideActions |= (1 << (std::int32_t)button.Action); if (button.Action == PlayerAction::Down) { // Buttstomp action is not separate for Touch controls yet overrideActions |= (1 << (std::int32_t)PlayerAction::Buttstomp); } } } } } } else if (event.type == TouchEventType::Move) { for (std::uint32_t i = 0; i < TouchButtonsCount; i++) { auto& button = _touchButtons[i]; if (button.Action != PlayerAction::None) { if (button.CurrentPointerId != -1) { bool isPressed = false; std::int32_t pointerIndex = event.findPointerIndex(button.CurrentPointerId); if (pointerIndex != -1) { float x = event.pointers[pointerIndex].x * (float)ViewSize.X; float y = event.pointers[pointerIndex].y * (float)ViewSize.Y; isPressed = IsOnButton(button, x, y); } if (!isPressed) { button.CurrentPointerId = -1; overrideActions &= ~(1 << (std::int32_t)button.Action); if (button.Action == PlayerAction::Down) { // Buttstomp action is not separate for Touch controls yet overrideActions &= ~(1 << (std::int32_t)PlayerAction::Buttstomp); } } } else { // Only some buttons should allow roll-over (only when the player's on foot) auto players = _levelHandler->GetPlayers(); bool canPlayerMoveVertically = (!players.empty() && players[0]->CanMoveVertically()); if ((button.Align & AllowRollover) != AllowRollover && !canPlayerMoveVertically) continue; for (std::uint32_t j = 0; j < event.count; j++) { float x = event.pointers[j].x * (float)ViewSize.X; float y = event.pointers[j].y * (float)ViewSize.Y; if (IsOnButton(button, x, y)) { button.CurrentPointerId = event.pointers[j].id; overrideActions |= (1 << (std::int32_t)button.Action); if (button.Action == PlayerAction::Down) { // Buttstomp action is not separate for Touch controls yet overrideActions |= (1 << (std::int32_t)PlayerAction::Buttstomp); } break; } } } } } } else if (event.type == TouchEventType::Up) { for (std::uint32_t i = 0; i < TouchButtonsCount; i++) { auto& button = _touchButtons[i]; if (button.CurrentPointerId != -1) { button.CurrentPointerId = -1; overrideActions &= ~(1 << (std::int32_t)button.Action); if (button.Action == PlayerAction::Down) { // Buttstomp action is not separate for Touch controls yet overrideActions &= ~(1 << (std::int32_t)PlayerAction::Buttstomp); } } } } else if (event.type == TouchEventType::PointerUp) { for (std::uint32_t i = 0; i < TouchButtonsCount; i++) { auto& button = _touchButtons[i]; if (button.CurrentPointerId == event.actionIndex) { button.CurrentPointerId = -1; overrideActions &= ~(1 << (std::int32_t)button.Action); if (button.Action == PlayerAction::Down) { // Buttstomp action is not separate for Touch controls yet overrideActions &= ~(1 << (std::int32_t)PlayerAction::Buttstomp); } } } } } void HUD::ShowLevelText(StringView text) { if (_levelText == text || text.empty()) { return; } _levelText = text; _levelTextTime = 0.0f; } void HUD::ShowCoins(std::int32_t count) { constexpr float StillTime = 120.0f; constexpr float TransitionTime = 60.0f; _coins = count; if (_coinsTime < 0.0f) { _coinsTime = 0.0f; } else if (_coinsTime > TransitionTime) { _coinsTime = TransitionTime; } if (_gemsTime >= 0.0f) { if (_gemsTime <= TransitionTime + StillTime) { _gemsTime = TransitionTime + StillTime; } else { _gemsTime = -1.0f; } } } void HUD::ShowGems(std::uint8_t gemType, std::int32_t count) { constexpr float StillTime = 120.0f; constexpr float TransitionTime = 60.0f; _gems = count; _gemsLastType = gemType; if (_gemsTime < 0.0f) { _gemsTime = 0.0f; } else if (_gemsTime > TransitionTime) { _gemsTime = TransitionTime; } if (_coinsTime >= 0.0f) { if (_coinsTime <= TransitionTime + StillTime) { _coinsTime = TransitionTime + StillTime; } else { _coinsTime = -1.0f; } } } void HUD::BeginFadeIn(bool skip) { _transitionState = TransitionState::FadeIn; _transitionTime = 0.0f; } void HUD::BeginFadeOut(float delay) { if (delay <= 0.0f) { _transitionState = TransitionState::FadeOut; _transitionTime = 1.0f; } else { _transitionState = TransitionState::WaitingForFadeOut; _transitionTime = delay; } } bool HUD::IsWeaponWheelVisible(std::int32_t playerIndex) const { return (_weaponWheel[playerIndex].Anim > 0.0f); } void HUD::OnDrawHealth(const Rectf& view, const Rectf& adjustedView, Actors::Player* player) { float bottom = adjustedView.Y + adjustedView.H; AnimState playerIcon; switch (player->_playerType) { default: case PlayerType::Jazz: playerIcon = CharacterJazz; break; case PlayerType::Spaz: playerIcon = CharacterSpaz; break; case PlayerType::Lori: playerIcon = CharacterLori; break; case PlayerType::Frog: playerIcon = CharacterFrog; break; } std::int32_t health = player->GetHealth(); std::int32_t lives = player->GetLives(); #if defined(WITH_ANGELSCRIPT) bool shouldDrawHealth = (health < 32 && (_levelHandler->_scripts == nullptr || !_levelHandler->_scripts->OnDraw(this, player, view, Scripting::DrawType::Health))); bool shouldDrawLives = (_levelHandler->_scripts == nullptr || !_levelHandler->_scripts->OnDraw(this, player, view, Scripting::DrawType::Lives)); #else bool shouldDrawHealth = (health < 32); constexpr bool shouldDrawLives = true; #endif if (shouldDrawLives) { DrawElement(playerIcon, -1, adjustedView.X + 38.0f, bottom - 1.0f + 1.6f, ShadowLayer, Alignment::BottomRight, Colorf(0.0f, 0.0f, 0.0f, 0.4f)); DrawElement(playerIcon, -1, adjustedView.X + 38.0f, bottom - 1.0f, MainLayer, Alignment::BottomRight, Colorf::White); } char stringBuffer[32]; std::int32_t charOffset = 0; std::int32_t charOffsetShadow = 0; // If drawing of lives if overriden, fallback to legacy HUD if (PreferencesCache::EnableReforgedHUD && shouldDrawLives) { if (lives > 0) { if (shouldDrawHealth) { DrawHealthCarrots(adjustedView.X + 24.0f, bottom - 30.0f, health); } if (shouldDrawLives) { if (lives < UINT8_MAX) { stringBuffer[0] = 'x'; i32tos(lives, stringBuffer + 1); _smallFont->DrawString(this, stringBuffer, charOffsetShadow, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f + 1.0f, FontShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f)); _smallFont->DrawString(this, stringBuffer, charOffset, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f, FontLayer, Alignment::BottomLeft, Font::DefaultColor); } else { _smallFont->DrawString(this, "x\u221E", charOffsetShadow, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f + 1.0f, FontShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f)); _smallFont->DrawString(this, "x\u221E", charOffset, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f, FontLayer, Alignment::BottomLeft, Font::DefaultColor); } } } else { if (shouldDrawHealth) { DrawHealthCarrots(adjustedView.X + 24.0f, bottom - 20.0f, health); } } if (player->_activeShield != ShieldType::None) { i32tos((std::int32_t)ceilf(player->_activeShieldTime * FrameTimer::SecondsPerFrame), stringBuffer); DrawElement(PickupStopwatch, -1, adjustedView.X + 68.0f, bottom - 8.0f + 1.6f, ShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 0.6f, 0.6f); DrawElement(PickupStopwatch, -1, adjustedView.X + 68.0f, bottom - 8.0f, MainLayer, Alignment::BottomLeft, Colorf::White, 0.6f, 0.6f); _smallFont->DrawString(this, stringBuffer, charOffsetShadow, adjustedView.X + 84.0f, bottom - 8.0f + 1.0f, FontShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f), 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.92f); _smallFont->DrawString(this, stringBuffer, charOffset, adjustedView.X + 84.0f, bottom - 8.0f, FontLayer, Alignment::BottomLeft, Font::DefaultColor, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.92f); } } else { if (shouldDrawHealth) { for (std::int32_t i = 0; i < health; i++) { DrawElement(Heart, -1, view.X + view.W - 4.0f - (i * 16.0f), view.Y + 4.0f + 1.6f, ShadowLayer, Alignment::TopRight, Colorf(0.0f, 0.0f, 0.0f, 0.3f)); DrawElement(Heart, -1, view.X + view.W - 4.0f - (i * 16.0f), view.Y + 4.0f, MainLayer, Alignment::TopRight, Colorf::White); } } if (shouldDrawLives) { if (lives > 0) { if (lives < UINT8_MAX) { stringBuffer[0] = 'x'; i32tos(lives, stringBuffer + 1); _smallFont->DrawString(this, stringBuffer, charOffsetShadow, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f + 1.0f, FontShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f)); _smallFont->DrawString(this, stringBuffer, charOffset, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f, FontLayer, Alignment::BottomLeft, Font::DefaultColor); } else { _smallFont->DrawString(this, "x\u221E", charOffsetShadow, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f + 1.0f, FontShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f)); _smallFont->DrawString(this, "x\u221E", charOffset, adjustedView.X + 36.0f - 4.0f, bottom - 1.0f, FontLayer, Alignment::BottomLeft, Font::DefaultColor); } } } if (player->_activeShield != ShieldType::None) { DrawElement(PickupStopwatch, -1, view.X + view.W * 0.5f - 30.0f, view.Y + 1.0f + 1.6f, ShadowLayer, Alignment::TopLeft, Colorf(0.0f, 0.0f, 0.0f, 0.3f)); DrawElement(PickupStopwatch, -1, view.X + view.W * 0.5f - 30.0f, view.Y + 1.0f, MainLayer, Alignment::TopLeft, Colorf::White); i32tos((std::int32_t)ceilf(player->_activeShieldTime * FrameTimer::SecondsPerFrame), stringBuffer); _smallFont->DrawString(this, stringBuffer, charOffsetShadow, view.X + view.W * 0.5f - 6.0f, view.Y + 6.0f + 1.0f, FontShadowLayer, Alignment::TopLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f)); _smallFont->DrawString(this, stringBuffer, charOffset, view.X + view.W * 0.5f - 6.0f, view.Y + 6.0f, FontLayer, Alignment::TopLeft, Font::DefaultColor); } } } void HUD::OnDrawScore(const Rectf& view, Actors::Player* player) { #if defined(WITH_ANGELSCRIPT) if (_levelHandler->_scripts != nullptr && _levelHandler->_scripts->OnDraw(this, player, view, Scripting::DrawType::Score)) { return; } #endif char stringBuffer[32]; std::int32_t charOffset = 0; std::int32_t charOffsetShadow = 0; if (PreferencesCache::EnableReforgedHUD) { DrawElement(PickupFood, -1, view.X + 3.0f, view.Y + 3.0f + 1.6f, ShadowLayer, Alignment::TopLeft, Colorf(0.0f, 0.0f, 0.0f, 0.4f)); DrawElement(PickupFood, -1, view.X + 3.0f, view.Y + 3.0f, MainLayer, Alignment::TopLeft, Colorf::White); std::size_t length = formatInto(stringBuffer, "{:.8}", player->GetScore()); _smallFont->DrawString(this, { stringBuffer, length }, charOffsetShadow, view.X + 14.0f, view.Y + 5.0f + 1.0f, FontShadowLayer, Alignment::TopLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.88f); _smallFont->DrawString(this, { stringBuffer, length }, charOffset, view.X + 14.0f, view.Y + 5.0f, FontLayer, Alignment::TopLeft, Font::DefaultColor, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.88f); } else { std::size_t length = formatInto(stringBuffer, "{:.8}", player->GetScore()); _smallFont->DrawString(this, { stringBuffer, length }, charOffsetShadow, view.X + 4.0f, view.Y + 1.0f + 1.0f, FontShadowLayer, Alignment::TopLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f), 1.2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.88f); _smallFont->DrawString(this, { stringBuffer, length }, charOffset, view.X + 4.0f, view.Y + 1.0f, FontLayer, Alignment::TopLeft, Font::DefaultColor, 1.2f, 0.0f, 0.0f, 0.0f, 0.0f, 0.88f); } } void HUD::OnDrawWeaponAmmo(const Rectf& adjustedView, Actors::Player* player) { #if defined(WITH_ANGELSCRIPT) if (_levelHandler->_scripts != nullptr && _levelHandler->_scripts->OnDraw(this, player, adjustedView, Scripting::DrawType::WeaponAmmo)) { return; } #endif if (!player->_weaponAllowed || player->_playerType == PlayerType::Frog) { return; } float right = adjustedView.X + adjustedView.W; float bottom = adjustedView.Y + adjustedView.H; WeaponType weapon = player->_currentWeapon; Vector2f pos = Vector2f(right - 40.0f, bottom - 2.0f); AnimState currentWeaponAnim = GetCurrentWeapon(player, weapon, pos); char stringBuffer[32]; StringView ammoCount; if (player->_weaponAmmo[(int32_t)weapon] == UINT16_MAX) { ammoCount = "x\u221E"_s; } else { stringBuffer[0] = 'x'; i32tos(player->_weaponAmmo[(int32_t)weapon] / 256, stringBuffer + 1); ammoCount = stringBuffer; } std::int32_t charOffset = 0; std::int32_t charOffsetShadow = 0; _smallFont->DrawString(this, ammoCount, charOffsetShadow, right - 40.0f, bottom - 2.0f + 1.0f, FontShadowLayer, Alignment::BottomLeft, Colorf(0.0f, 0.0f, 0.0f, 0.32f), 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.96f); _smallFont->DrawString(this, ammoCount, charOffset, right - 40.0f, bottom - 2.0f, FontLayer, Alignment::BottomLeft, Font::DefaultColor, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.96f); auto* res = _metadata->FindAnimation(currentWeaponAnim); if (res != nullptr) { if (res->Base->FrameDimensions.Y < 20) { pos.Y -= std::round((20 - res->Base->FrameDimensions.Y) * 0.5f); } DrawElement(currentWeaponAnim, -1, pos.X, pos.Y + 1.6f, ShadowLayer, Alignment::BottomRight, Colorf(0.0f, 0.0f, 0.0f, 0.4f)); DrawElement(currentWeaponAnim, -1, pos.X, pos.Y, MainLayer, Alignment::BottomRight, Colorf::White); } } void HUD::OnDrawActiveBoss(const Rectf& adjustedView) { if (_levelHandler->_activeBoss == nullptr || _levelHandler->_activeBoss->GetMaxHealth() == INT32_MAX) { return; } float bottom = adjustedView.Y + adjustedView.H; constexpr float TransitionTime = 60.0f; float y, alpha; if (_activeBossTime < TransitionTime) { y = (TransitionTime - _activeBossTime) / 8.0f; y = bottom * 0.1f - (y * y); alpha = std::max(_activeBossTime / TransitionTime, 0.0f); } else { y = bottom * 0.1f; alpha = 1.0f; } float perc = 0.08f + 0.84f * _levelHandler->_activeBoss->GetHealth() / _levelHandler->_activeBoss->GetMaxHealth(); DrawElement(BossHealthBar, 0, ViewSize.X * 0.5f, y + 2.0f, ShadowLayer, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, 0.1f * alpha)); DrawElement(BossHealthBar, 0, ViewSize.X * 0.5f, y + 1.0f, ShadowLayer, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, 0.2f * alpha)); DrawElement(BossHealthBar, 0, ViewSize.X * 0.5f, y, MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, alpha)); DrawElementClipped(BossHealthBar, 1, ViewSize.X * 0.5f, y, MainLayer + 2, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, alpha), perc, 1.0f); } void HUD::OnDrawLevelText(int32_t& charOffset) { constexpr float StillTime = 350.0f; constexpr float TransitionTime = 100.0f; constexpr float TotalTime = StillTime + TransitionTime * 2.0f; if (_levelTextTime < 0.0f) { return; } float offset; if (_levelTextTime < TransitionTime) { offset = powf((TransitionTime - _levelTextTime) / 12.0f, 3); } else if (_levelTextTime > TransitionTime + StillTime) { offset = -powf((_levelTextTime - TransitionTime - StillTime) / 12.0f, 3); } else { offset = 0; } float textScale = (ViewSize.X >= 360 ? 1.0f : 0.8f); std::int32_t charOffsetShadow = charOffset; _smallFont->DrawString(this, _levelText, charOffsetShadow, ViewSize.X * 0.5f + offset, ViewSize.Y * 0.04f + 2.5f, 50, Alignment::Top, Colorf(0.0f, 0.0f, 0.0f, 0.3f), textScale, 0.72f, 0.8f, 0.8f); _smallFont->DrawString(this, _levelText, charOffset, ViewSize.X * 0.5f + offset, ViewSize.Y * 0.04f, 60, Alignment::Top, Font::DefaultColor, textScale, 0.72f, 0.8f, 0.8f); if (_levelTextTime > TotalTime) { _levelTextTime = -1.0f; _levelText = {}; } } void HUD::OnDrawCoins(const Rectf& view, std::int32_t& charOffset) { constexpr float StillTime = 120.0f; constexpr float TransitionTime = 60.0f; constexpr float TotalTime = StillTime + TransitionTime * 2.0f; if (_coinsTime < 0.0f) { return; } float offset, alpha; if (_coinsTime < TransitionTime) { offset = (TransitionTime - _coinsTime) / 10.0f; offset = -(offset * offset); alpha = std::max(_coinsTime / TransitionTime, 0.1f); } else if (_coinsTime > TransitionTime + StillTime) { offset = (_coinsTime - TransitionTime - StillTime) / 10.0f; offset = (offset * offset); alpha = (TotalTime - _coinsTime) / TransitionTime; } else { offset = 0.0f; alpha = 1.0f; } float alpha2 = alpha * alpha; DrawElement(PickupCoin, -1, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + 2.5f + offset, ShadowLayer, Alignment::Right, Colorf(0.0f, 0.0f, 0.0f, 0.2f * alpha), 0.8f, 0.8f); DrawElement(PickupCoin, -1, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + offset, MainLayer, Alignment::Right, Colorf(1.0f, 1.0f, 1.0f, alpha2), 0.8f, 0.8f); char stringBuffer[32]; std::size_t length = formatInto(stringBuffer, "x{}", _coins); std::int32_t charOffsetShadow = charOffset; _smallFont->DrawString(this, { stringBuffer, length }, charOffsetShadow, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + 2.5f + offset, FontShadowLayer, Alignment::Left, Colorf(0.0f, 0.0f, 0.0f, 0.3f * alpha), 1.0f, 0.0f, 0.0f, 0.0f); Colorf fontColor = Font::DefaultColor; fontColor.SetAlpha(alpha2); _smallFont->DrawString(this, { stringBuffer, length }, charOffset, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + offset, FontLayer, Alignment::Left, fontColor, 1.0f, 0.0f, 0.0f, 0.0f); if (_coinsTime > TotalTime) { _coinsTime = -1.0f; } } void HUD::OnDrawGems(const Rectf& view, std::int32_t& charOffset) { constexpr float StillTime = 120.0f; constexpr float TransitionTime = 60.0f; constexpr float TotalTime = StillTime + TransitionTime * 2.0f; if (_gemsTime < 0.0f) { return; } float offset, alpha; if (_gemsTime < TransitionTime) { offset = (TransitionTime - _gemsTime) / 10.0f; offset = -(offset * offset); alpha = std::max(_gemsTime / TransitionTime, 0.1f); } else if (_gemsTime > TransitionTime + StillTime) { offset = (_gemsTime - TransitionTime - StillTime) / 10.0f; offset = (offset * offset); alpha = (TotalTime - _gemsTime) / TransitionTime; } else { offset = 0.0f; alpha = 1.0f; } AnimState animState = (AnimState)((std::uint32_t)PickupGemRed + _gemsLastType); float alpha2 = alpha * alpha; DrawElement(animState, -1, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + 2.5f + offset, ShadowLayer, Alignment::Right, Colorf(0.0f, 0.0f, 0.0f, 0.4f * alpha2), 0.8f, 0.8f); DrawElement(animState, -1, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + offset, MainLayer, Alignment::Right, Colorf(1.0f, 1.0f, 1.0f, 0.8f * alpha2), 0.8f, 0.8f); char stringBuffer[32]; std::size_t length = formatInto(stringBuffer, "x{}", _gems); std::int32_t charOffsetShadow = charOffset; _smallFont->DrawString(this, { stringBuffer, length }, charOffsetShadow, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + 2.5f + offset, FontShadowLayer, Alignment::Left, Colorf(0.0f, 0.0f, 0.0f, 0.3f * alpha), 1.0f, 0.0f, 0.0f, 0.0f); Colorf fontColor = Font::DefaultColor; fontColor.SetAlpha(alpha2); _smallFont->DrawString(this, { stringBuffer, length }, charOffset, view.X + view.W * 0.5f, view.Y + view.H * 0.92f + offset, FontLayer, Alignment::Left, fontColor, 1.0f, 0.0f, 0.0f, 0.0f); if (_gemsTime > TotalTime) { _gemsTime = -1.0f; } } void HUD::OnDrawTouchButtons(Actors::Player* player) { bool isConsoleVisible = _levelHandler->_console->IsVisible(); for (auto& button : _touchButtons) { if (player == nullptr && button.Action != PlayerAction::Menu && button.Action != PlayerAction::Console) { continue; } Texture* texture = nullptr; if (isConsoleVisible) { // Show only Close button if console is open if (button.Action != PlayerAction::Menu) { continue; } if (auto anim = _metadata->FindAnimation(TouchClose)) { texture = anim->Base->TextureDiffuse.get(); } } else { #if defined(NCINE_HAS_NATIVE_BACK_BUTTON) if (button.Action == PlayerAction::Menu && PreferencesCache::UseNativeBackButton) { continue; } #endif if (button.Graphics != nullptr) { texture = button.Graphics->Base->TextureDiffuse.get(); } } if (texture == nullptr) { continue; } float x = button.Left; float y = button.Top; if ((button.Align & Alignment::Right) == Alignment::Right) { x = ViewSize.X - button.Width * 0.5f - x; } else if ((button.Align & Alignment::Left) == Alignment::Left) { x = x + button.Width * 0.5f; } else { x = ViewSize.X / 2; } if ((button.Align & Alignment::Bottom) == Alignment::Bottom) { y = ViewSize.Y - button.Height * 0.5f - y; } else { y = y + button.Height * 0.5f; } if ((button.Align & Fixed) != Fixed) { if ((button.Align & Alignment::Right) == Alignment::Right) { x -= PreferencesCache::TouchRightPadding.X; y += PreferencesCache::TouchRightPadding.Y; } else if ((button.Align & Alignment::Left) == Alignment::Left) { x += PreferencesCache::TouchLeftPadding.X; y += PreferencesCache::TouchLeftPadding.Y; } } Colorf color = (button.Action == PlayerAction::Run && player->_isRunPressed ? Colorf(0.6f, 0.6f, 0.6f) : Colorf::White); DrawTexture(*texture, Vector2f(std::round(x - button.Width * 0.5f), std::round(y - button.Height * 0.5f)), TouchButtonsLayer, Vector2f(button.Width, button.Height), Vector4f(1.0f, 0.0f, 1.0f, 0.0f), color); } } void HUD::DrawHealthCarrots(float x, float y, std::int32_t health) { constexpr Colorf CarrotShadowColor = Colorf(0.0f, 0.0f, 0.0f, 0.5f); std::int32_t lastCarrotIdx = 0; float lastCarrotOffset = 0.0f; float scale = 0.5f; float angleBase1 = sinf(AnimTime * 10.0f) * fDegToRad; float angleBase2 = sinf(AnimTime * 12.0f + 3.0f) * fDegToRad; float angleBase3 = sinf(AnimTime * 11.0f + 7.0f) * fDegToRad; // Limit frame rate of carrot movement angleBase1 = std::round(angleBase1 * 3.0f * fRadToDeg) / (3.0f * fRadToDeg); angleBase2 = std::round(angleBase2 * 3.0f * fRadToDeg) / (3.0f * fRadToDeg); angleBase3 = std::round(angleBase3 * 3.0f * fRadToDeg) / (3.0f * fRadToDeg); if (health >= 1) { float angle = angleBase1 * (health > 1 ? -6.0f : -14.0f) + 0.2f; DrawElement(PickupCarrot, 1, x + 1.0f - 1.0f, y + 2.0f + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 1, x + 1.0f + 1.0f, y + 2.0f + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 1, x + 1.0f, y + 2.0f, FontLayer + lastCarrotIdx * 2 + 1, Alignment::Left, Colorf::White, scale, scale, false, angle); lastCarrotIdx++; lastCarrotOffset = 7.0f; } if (health >= 3) { float angle = angleBase3 * 10.0f; lastCarrotOffset -= 1.0f; DrawElement(PickupCarrot, 2, x + lastCarrotOffset - 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 2, x + lastCarrotOffset + 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 2, x + lastCarrotOffset, y, FontLayer + lastCarrotIdx * 2 + 1, Alignment::Left, Colorf::White, scale, scale, false, angle); lastCarrotIdx++; lastCarrotOffset += 6.0f; } if (health >= 2) { float angle = angleBase2 * -6.0f + 0.2f; DrawElement(PickupCarrot, 2, x + lastCarrotOffset - 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 2, x + lastCarrotOffset + 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 2, x + lastCarrotOffset, y, FontLayer + lastCarrotIdx * 2 + 1, Alignment::Left, Colorf::White, scale, scale, false, angle); lastCarrotIdx++; lastCarrotOffset = 17.0f; } if (health >= 6) { for (std::int32_t i = 0; i < health - 5; i++) { float angle = ((i % 3) == 1 ? angleBase2 : angleBase3) * (4.0f + ((i * 7) % 6)); if ((i % 2) == 1) { angle = -angle; } DrawElement(PickupCarrot, 2, x + lastCarrotOffset - 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 2, x + lastCarrotOffset + 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 2, x + lastCarrotOffset, y, FontLayer + lastCarrotIdx * 2 + 1, Alignment::Left, Colorf::White, scale, scale, false, angle); lastCarrotIdx++; lastCarrotOffset += 5.0f; } } if (health >= 5) { float angle = angleBase1 * 10.0f; lastCarrotOffset -= 1.0f; DrawElement(PickupCarrot, 3, x + lastCarrotOffset - 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 3, x + lastCarrotOffset + 1.0f, y + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 3, x + lastCarrotOffset, y, FontLayer + lastCarrotIdx * 2 + 1, Alignment::Left, Colorf::White, scale, scale, false, angle); lastCarrotIdx++; lastCarrotOffset += 5.0f; } if (health >= 4) { float angle = angleBase2 * -6.0f - 0.4f; lastCarrotOffset -= 2.0f; DrawElement(PickupCarrot, 5, x + lastCarrotOffset - 1.0f, y + 2.0f + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 5, x + lastCarrotOffset + 1.0f, y + 2.0f + 1.0f, FontLayer + lastCarrotIdx * 2, Alignment::Left, CarrotShadowColor, scale, scale, false, angle); DrawElement(PickupCarrot, 5, x + lastCarrotOffset, y + 2.0f, FontLayer + lastCarrotIdx * 2 + 1, Alignment::Left, Colorf::White, scale, scale, false, angle); lastCarrotIdx++; } } void HUD::DrawViewportSeparators() { switch (_levelHandler->_assignedViewports.size()) { case 2: { if (PreferencesCache::PreferVerticalSplitscreen) { std::int32_t halfW = ViewSize.X / 2; DrawSolid(Vector2f(halfW - 1.0f, 0.0f), ShadowLayer, Vector2f(1.0f, ViewSize.Y), Colorf(0.0f, 0.0f, 0.0f, 0.4f)); DrawSolid(Vector2f(halfW, 0.0f), ShadowLayer, Vector2f(1.0f, ViewSize.Y), Colorf(1.0f, 1.0f, 1.0f, 0.1f), true); } else { std::int32_t halfH = ViewSize.Y / 2; DrawSolid(Vector2f(0.0f, halfH - 1.0f), ShadowLayer, Vector2f(ViewSize.X, 1.0f), Colorf(1.0f, 1.0f, 1.0f, 0.1f), true); DrawSolid(Vector2f(0.0f, halfH), ShadowLayer, Vector2f(ViewSize.X, 1.0f), Colorf(0.0f, 0.0f, 0.0f, 0.4f)); } break; } case 3: { std::int32_t halfW = (ViewSize.X + 1) / 2; std::int32_t halfH = (ViewSize.Y + 1) / 2; DrawSolid(Vector2f(halfW, halfH), ShadowLayer, Vector2f(halfW, halfH), Colorf::Black); DEATH_FALLTHROUGH } case 4: { std::int32_t halfW = (ViewSize.X + 1) / 2; std::int32_t halfH = (ViewSize.Y + 1) / 2; DrawSolid(Vector2f(halfW - 1.0f, 0.0f), ShadowLayer, Vector2f(1.0f, ViewSize.Y), Colorf(0.0f, 0.0f, 0.0f, 0.4f)); DrawSolid(Vector2f(halfW, 0.0f), ShadowLayer, Vector2f(1.0f, ViewSize.Y), Colorf(1.0f, 1.0f, 1.0f, 0.1f), true); DrawSolid(Vector2f(0.0f, halfH - 1.0f), ShadowLayer, Vector2f(ViewSize.X, 1.0f), Colorf(1.0f, 1.0f, 1.0f, 0.1f), true); DrawSolid(Vector2f(0.0f, halfH), ShadowLayer, Vector2f(ViewSize.X, 1.0f), Colorf(0.0f, 0.0f, 0.0f, 0.4f)); break; } } } void HUD::DrawElement(AnimState state, std::int32_t frame, float x, float y, std::uint16_t z, Alignment align, const Colorf& color, float scaleX, float scaleY, bool additiveBlending, float angle) { auto* res = _metadata->FindAnimation(state); if (res == nullptr) { return; } if (frame < 0) { frame = res->FrameOffset + ((std::int32_t)(AnimTime * res->FrameCount / res->AnimDuration) % res->FrameCount); } GenericGraphicResource* base = res->Base; Vector2f size = Vector2f(base->FrameDimensions.X * scaleX, base->FrameDimensions.Y * scaleY); Vector2f adjustedPos = ApplyAlignment(align, Vector2f(x, y), size); Vector2i texSize = base->TextureDiffuse->GetSize(); std::int32_t col = frame % base->FrameConfiguration.X; std::int32_t row = frame / base->FrameConfiguration.X; Vector4f texCoords = Vector4f( float(base->FrameDimensions.X) / float(texSize.X), float(base->FrameDimensions.X * col) / float(texSize.X), float(base->FrameDimensions.Y) / float(texSize.Y), float(base->FrameDimensions.Y * row) / float(texSize.Y) ); DrawTexture(*base->TextureDiffuse.get(), adjustedPos, z, size, texCoords, color, additiveBlending, angle); } void HUD::DrawElementClipped(AnimState state, std::int32_t frame, float x, float y, std::uint16_t z, Alignment align, const Colorf& color, float clipX, float clipY) { auto* res = _metadata->FindAnimation(state); if (res == nullptr) { return; } if (frame < 0) { frame = res->FrameOffset + ((std::int32_t)(AnimTime * res->FrameCount / res->AnimDuration) % res->FrameCount); } GenericGraphicResource* base = res->Base; Vector2f size = Vector2f(base->FrameDimensions.X * clipX, base->FrameDimensions.Y * clipY); Vector2f adjustedPos = ApplyAlignment(align, Vector2f(x, y), base->FrameDimensions.As()); Vector2i texSize = base->TextureDiffuse->GetSize(); std::int32_t col = frame % base->FrameConfiguration.X; std::int32_t row = frame / base->FrameConfiguration.X; Vector4f texCoords = Vector4f( std::floor(float(base->FrameDimensions.X) * clipX) / float(texSize.X), float(base->FrameDimensions.X * col) / float(texSize.X), std::floor(float(base->FrameDimensions.Y) * clipY) / float(texSize.Y), float(base->FrameDimensions.Y * row) / float(texSize.Y) ); DrawTexture(*base->TextureDiffuse.get(), adjustedPos, z, size, texCoords, color); } AnimState HUD::GetCurrentWeapon(Actors::Player* player, WeaponType weapon, Vector2f& offset) { if (weapon == WeaponType::Toaster && player->_inWater) { offset.Y += 2; return WeaponToasterDisabled; } else if (weapon == WeaponType::Seeker) { offset.X += 2; } else if (weapon == WeaponType::TNT) { offset.X += 2; } else if (weapon == WeaponType::Electro) { offset.X += 6; } if ((player->_weaponUpgrades[(std::int32_t)weapon] & 0x01) != 0) { switch (weapon) { default: case WeaponType::Blaster: if (player->_playerType == PlayerType::Spaz) { return WeaponPowerUpBlasterSpaz; } else if (player->_playerType == PlayerType::Lori) { return WeaponPowerUpBlasterLori; } else { return WeaponPowerUpBlasterJazz; } case WeaponType::Bouncer: return WeaponPowerUpBouncer; case WeaponType::Freezer: return WeaponPowerUpFreezer; case WeaponType::Seeker: return WeaponPowerUpSeeker; case WeaponType::RF: return WeaponPowerUpRF; case WeaponType::Toaster: return WeaponPowerUpToaster; case WeaponType::TNT: return WeaponPowerUpTNT; case WeaponType::Pepper: return WeaponPowerUpPepper; case WeaponType::Electro: return WeaponPowerUpElectro; case WeaponType::Thunderbolt: return WeaponPowerUpThunderbolt; } } else { switch (weapon) { default: case WeaponType::Blaster: if (player->_playerType == PlayerType::Spaz) { return WeaponBlasterSpaz; } else if (player->_playerType == PlayerType::Lori) { return WeaponBlasterLori; } else { return WeaponBlasterJazz; } case WeaponType::Bouncer: return WeaponBouncer; case WeaponType::Freezer: return WeaponFreezer; case WeaponType::Seeker: return WeaponSeeker; case WeaponType::RF: return WeaponRF; case WeaponType::Toaster: return WeaponToaster; case WeaponType::TNT: return WeaponTNT; case WeaponType::Pepper: return WeaponPepper; case WeaponType::Electro: return WeaponElectro; case WeaponType::Thunderbolt: return WeaponThunderbolt; } } } void HUD::DrawWeaponWheel(const Rectf& view, Actors::Player* player) { auto& state = _weaponWheel[player->_playerIndex]; if (state.Anim <= 0.0f) { return; } auto* res = _metadata->FindAnimation(WeaponWheel); if (res == nullptr) { return; } Texture& lineTexture = *res->Base->TextureDiffuse.get(); auto& input = _levelHandler->_playerInputs[player->_playerIndex]; if (!input.Frozen) { input.Frozen = true; input.FrozenMovement = input.RequiredMovement; } if (player->_weaponWheelState == Actors::Player::WeaponWheelState::Hidden && player->_sugarRushLeft <= 0.0f && state.Anim >= WeaponWheelAnimDuration * 0.1f) { player->_weaponWheelState = Actors::Player::WeaponWheelState::Opening; } Vector2f center = view.Center(); float angleStep = fTwoPi / state.WeaponCount; float h = input.RequiredMovement.X; float v = input.RequiredMovement.Y; if (std::abs(h) + std::abs(v) < 0.5f) { h = 0.0f; v = 0.0f; } if (state.Vertices == nullptr) { state.Vertices = std::make_unique(WeaponWheelMaxVertices); } state.VerticesCount = 0; state.RenderCommandsCount = 0; float requestedAngle; std::int32_t requestedIndex; if (h == 0.0f && v == 0.0f) { requestedAngle = NAN; requestedIndex = -1; } else { requestedAngle = atan2f(v, h); if (requestedAngle < 0) { requestedAngle += fTwoPi; } float adjustedAngle = requestedAngle + fPiOver2 + angleStep * 0.5f; if (adjustedAngle >= fTwoPi) { adjustedAngle -= fTwoPi; } requestedIndex = (std::int32_t)(state.WeaponCount * adjustedAngle / fTwoPi); } float scale = std::min(std::min(view.W, view.H) * 0.0034f, 1.0f); float alpha = state.Anim / WeaponWheelAnimDuration; float easing = Menu::IMenuContainer::EaseOutCubic(alpha); float distance = scale * (20.0f + (70.0f * easing)); float distance2 = scale * (10.0f + (50.0f * easing)); float distance3 = distance2 * 2.0f; float alphaInner = std::min(Vector2f(h, v).Length() * easing * 1.5f - 0.6f, 1.0f); if (alphaInner > 0.0f) { DrawElement(WeaponWheelInner, -1, center.X, center.Y, MainLayer + 5, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, alphaInner), scale * easing, scale * easing, true, requestedAngle); } float angle = -fPiOver2; for (std::int32_t i = 0, j = 0; i < std::int32_t(arraySize(player->_weaponAmmo)); i++) { if (player->_weaponAmmo[i] != 0) { float x = cosf(angle) * distance; float y = sinf(angle) * distance; Vector2f pos = Vector2f(center.X + x, center.Y + y); AnimState weapon = GetCurrentWeapon(player, (WeaponType)i, pos); Colorf color2; float scale; bool isSelected = (j == requestedIndex); if (isSelected) { state.LastIndex = i; color2 = Colorf(1.0f, 0.8f, 0.5f, alpha); scale = 1.0f; } else { color2 = Colorf(1.0f, 1.0f, 1.0f, alpha * 0.9f); scale = 0.9f; } DrawElement(WeaponWheelDim, -1, pos.X, pos.Y, ShadowLayer - 10, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, alpha * 0.6f), 5.0f, 5.0f); DrawElement(weapon, -1, pos.X, pos.Y, MainLayer + 10, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, isSelected ? alpha : alpha * 0.7f), scale, scale); if (PreferencesCache::WeaponWheel == WeaponWheelStyle::EnabledWithAmmoCount) { char stringBuffer[32]; StringView ammoCount; if (player->_weaponAmmo[i] == UINT16_MAX) { ammoCount = "x\u221E"_s; } else { stringBuffer[0] = 'x'; i32tos(player->_weaponAmmo[i] / 256, stringBuffer + 1); ammoCount = stringBuffer; } std::int32_t charOffset = 0; _smallFont->DrawString(this, ammoCount, charOffset, center.X + cosf(angle) * distance * 1.4f, center.Y + sinf(angle) * distance * 1.4f, FontLayer, Alignment::Center, isSelected ? Colorf(0.62f, 0.44f, 0.34f, 0.5f * alpha) : Colorf(0.45f, 0.45f, 0.45f, 0.48f * alpha), 0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.9f); } float angleFrom = angle - angleStep * 0.4f; float angleTo = angle + angleStep * 0.4f; Colorf color1 = Colorf(0.0f, 0.0f, 0.0f, alpha * 0.2f); DrawWeaponWheelSegment(state, center.X - distance2 - 1.0f, center.Y - distance2 - 1.0f, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2 - 1.0f, center.Y - distance2 + 1.0f, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2 + 1.0f, center.Y - distance2 - 1.0f, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2 + 1.0f, center.Y - distance2 + 1.0f, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2 - 0.5f, center.Y - distance2, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2 + 0.5f, center.Y - distance2, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2, center.Y - distance2 - 0.5f, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2, center.Y - distance2 + 0.5f, distance3, distance3, ShadowLayer, angleFrom, angleTo, lineTexture, color1); DrawWeaponWheelSegment(state, center.X - distance2, center.Y - distance2, distance3, distance3, MainLayer, angleFrom, angleTo, lineTexture, color2); if (isSelected) { DrawWeaponWheelSegment(state, center.X - distance2 - 1.0f, center.Y - distance2 - 1.0f, distance3 + 2.0f, distance3 + 2.0f, MainLayer + 1, angleFrom + fRadAngle1, angleTo - fRadAngle1, lineTexture, Colorf(1.0f, 0.8f, 0.5f, alpha * 0.3f)); } angle += angleStep; j++; } } } void HUD::UpdateWeaponWheel(float timeMult) { for (auto& viewport : _levelHandler->_assignedViewports) { if (Actors::Player* player = runtime_cast(viewport->GetTargetActor())) { auto& state = _weaponWheel[player->_playerIndex]; if (PrepareWeaponWheel(player, state.WeaponCount)) { if (state.Anim < WeaponWheelAnimDuration) { state.Anim += timeMult; if (state.Anim > WeaponWheelAnimDuration) { state.Anim = WeaponWheelAnimDuration; } } } else { if (state.Anim > 0.0f) { state.Anim -= timeMult * 2.0f; if (state.Anim <= 0.0f) { state.Anim = 0.0f; _levelHandler->_playerInputs[player->_playerIndex].Frozen = false; if (player->_weaponWheelState == Actors::Player::WeaponWheelState::Visible) { player->_weaponWheelState = Actors::Player::WeaponWheelState::Closing; } } } } } } } bool HUD::PrepareWeaponWheel(Actors::Player* player, std::int32_t& weaponCount) { weaponCount = 0; auto& state = _weaponWheel[player->_playerIndex]; if (PreferencesCache::WeaponWheel == WeaponWheelStyle::Disabled || player == nullptr || !((player->_controllable && player->_controllableExternal) || !_levelHandler->IsReforged()) || player->_playerType == PlayerType::Frog) { if (state.Anim > 0.0f) { state.Shown = false; state.LastIndex = -1; } return false; } bool isGamepad; if (!_levelHandler->PlayerActionPressed(player, PlayerAction::ChangeWeapon, true, isGamepad) || !isGamepad) { if (state.Anim > 0.0f) { if (state.Anim < WeaponWheelAnimDuration * 0.5f) { // Switch to the next weapon on short press if (state.Shown) { player->SwitchToNextWeapon(); } } else if (state.LastIndex != -1) { player->SwitchToWeaponByIndex((std::uint32_t)state.LastIndex); } state.Shown = false; state.LastIndex = -1; weaponCount = GetWeaponCount(player); } return false; } state.Shown = true; weaponCount = GetWeaponCount(player); return (weaponCount > 0); } std::int32_t HUD::GetWeaponCount(Actors::Player* player) { std::int32_t weaponCount = 0; for (std::int32_t i = 0; i < std::int32_t(arraySize(player->_weaponAmmo)); i++) { if (player->_weaponAmmo[i] != 0) { weaponCount++; } } // Player must have at least 2 weapons if (weaponCount < 2) { weaponCount = 0; } return weaponCount; } void HUD::DrawWeaponWheelSegment(WeaponWheelState& state, float x, float y, float width, float height, std::uint16_t z, float minAngle, float maxAngle, const Texture& texture, const Colorf& color) { width *= 0.5f; x += width; height *= 0.5f; y += height; float angleRange = std::min(maxAngle - minAngle, fRadAngle360); std::int32_t segmentNum = std::clamp((std::int32_t)std::round(powf(std::max(width, height), 0.65f) * 3.5f * angleRange / fRadAngle360), 4, 128); float angleStep = angleRange / (segmentNum - 1); std::int32_t vertexCount = segmentNum + 2; float angle = minAngle; Vertex* vertices = &state.Vertices[state.VerticesCount]; state.VerticesCount += vertexCount; if (state.VerticesCount > WeaponWheelMaxVertices) { // This shouldn't happen, 768 vertices should be enough DEATH_DEBUG_ASSERT(state.VerticesCount <= WeaponWheelMaxVertices); return; } constexpr float Mult = 2.2f; { std::int32_t j = 0; vertices[j].X = x + cosf(angle) * (width * Mult - 0.5f); vertices[j].Y = y + sinf(angle) * (height * Mult - 0.5f); vertices[j].U = 0.0f; vertices[j].V = 0.0f; } for (std::int32_t i = 1; i < vertexCount - 1; i++) { vertices[i].X = x + cosf(angle) * (width - 0.5f); vertices[i].Y = y + sinf(angle) * (height - 0.5f); vertices[i].U = 0.15f + (0.7f * (float)(i - 1) / (vertexCount - 3)); vertices[i].V = 0.0f; angle += angleStep; } { angle -= angleStep; std::int32_t j = vertexCount - 1; vertices[j].X = x + cosf(angle) * (width * Mult - 0.5f); vertices[j].Y = y + sinf(angle) * (height * Mult - 0.5f); vertices[j].U = 1.0f; vertices[j].V = 0.0f; } // Create render command RenderCommand* command; if (state.RenderCommandsCount < state.RenderCommands.size()) { command = state.RenderCommands[state.RenderCommandsCount].get(); state.RenderCommandsCount++; } else { command = state.RenderCommands.emplace_back(std::make_unique(RenderCommand::Type::MeshSprite)).get(); command->GetMaterial().SetBlendingEnabled(true); } if (command->GetMaterial().SetShaderProgramType(Material::ShaderProgramType::MeshSprite)) { command->GetMaterial().ReserveUniformsDataMemory(); auto* textureUniform = command->GetMaterial().Uniform(Material::TextureUniformName); if (textureUniform && textureUniform->GetIntValue(0) != 0) { textureUniform->SetIntValue(0); // GL_TEXTURE0 } } command->GetGeometry().SetDrawParameters(GL_LINE_STRIP, 0, vertexCount); command->GetGeometry().SetElementsPerVertex(VertexFloats); command->GetGeometry().SetHostVertexPointer((const float*)vertices); command->GetMaterial().SetBlendingFactors(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); auto instanceBlock = command->GetMaterial().UniformBlock(Material::InstanceBlockName); instanceBlock->GetUniform(Material::TexRectUniformName)->SetFloatValue(1.0f, 0.0f, 1.0f, 0.0f); instanceBlock->GetUniform(Material::SpriteSizeUniformName)->SetFloatValue(1.0f, 1.0f); instanceBlock->GetUniform(Material::ColorUniformName)->SetFloatVector(color.Data()); command->SetTransformation(Matrix4x4f::Identity); command->SetLayer(z); command->GetMaterial().SetTexture(texture); DrawRenderCommand(command); } HUD::TouchButtonInfo HUD::CreateTouchButton(PlayerAction action, AnimState state, Alignment align, float x, float y, float w, float h) { TouchButtonInfo info; info.Action = action; info.Left = std::round(x * LevelHandler::DefaultWidth * 0.5f); info.Top = std::round(y * LevelHandler::DefaultWidth * 0.5f); info.Width = std::round(w * LevelHandler::DefaultWidth * 0.5f); info.Height = std::round(h * LevelHandler::DefaultWidth * 0.5f); info.Graphics = (state != AnimState::Default ? _metadata->FindAnimation(state) : nullptr); info.CurrentPointerId = -1; info.Align = align; return info; } bool HUD::IsOnButton(const HUD::TouchButtonInfo& button, float x, float y) { if ((button.Align & Fixed) != Fixed) { if ((button.Align & Alignment::Right) == Alignment::Right) { x += PreferencesCache::TouchRightPadding.X; y -= PreferencesCache::TouchRightPadding.Y; } else if ((button.Align & Alignment::Left) == Alignment::Left) { x -= PreferencesCache::TouchLeftPadding.X; y -= PreferencesCache::TouchLeftPadding.Y; } } float left = button.Left; if ((button.Align & Alignment::Right) == Alignment::Right) { left = ViewSize.X - button.Width - left; } else if ((button.Align & Alignment::Left) != Alignment::Left) { left = ViewSize.X / 2 - button.Width * 0.5f; } if (x < left) return false; float top = button.Top; if ((button.Align & Alignment::Bottom) == Alignment::Bottom) { top = ViewSize.Y - button.Height - top; } if (y < top) return false; float right = left + button.Width; if (x > right) return false; float bottom = top + button.Height; if (y > bottom) return false; return true; } void HUD::UpdateRgbLights(float timeMult, Rendering::PlayerViewport* viewport) { #if !defined(DEATH_TARGET_ANDROID) && !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_WINDOWS_RT) if (!PreferencesCache::EnableRgbLights) { _rgbHealthLast = 0.0f; return; } RgbLights& rgbLights = RgbLights::Get(); if (!rgbLights.IsSupported()) { return; } _rgbLightsTime -= timeMult; if (_rgbLightsTime > 0.0f) { return; } _rgbLightsAnim += 0.06f; _rgbLightsTime += RgbLights::RefreshRate; Actors::ActorBase* actor = viewport->GetTargetActor(); if (Actors::Player* player = runtime_cast(actor)) { float health = std::clamp((float)player->_health / player->_maxHealth, 0.0f, 1.0f); _rgbHealthLast = lerp(_rgbHealthLast, health, 0.2f); } else { _rgbHealthLast = 0.0f; } _rgbAmbientLight = viewport->_ambientLight.W; constexpr std::int32_t KeyMax2 = 14; Color colors[RgbLights::ColorsSize] {}; if (auto captionTile = _levelHandler->_tileMap->GetCaptionTile()) { for (std::int32_t i = 0; i < std::int32_t(arraySize(KeyLayout)); i++) { std::int32_t x = KeyLayout[i] % AURA_KEYBOARD_WIDTH; std::int32_t y = KeyLayout[i] / AURA_KEYBOARD_WIDTH; Color tileColor = captionTile[y * 32 + x]; colors[AURA_COLORS_LIMITED_SIZE + i] = ApplyRgbGradientAlpha(tileColor, x, y, _rgbLightsAnim, _rgbAmbientLight); } } std::int32_t percent, percentR, percentG; percent = (std::int32_t)(_rgbHealthLast * 255); percentG = percent * percent / 255; percentR = (255 - (percent - 120) * 2); percentR = std::clamp(percentR, 0, 255); for (std::int32_t i = 0; i < KeyMax2; i++) { std::int32_t intensity = (std::int32_t)((_rgbHealthLast - ((float)i / KeyMax2)) * 255 * KeyMax2); intensity = std::clamp(intensity, 0, 200); if (intensity > 0) { colors[(std::int32_t)AuraLight::Tilde + i] = Color(percentR * intensity / 255, percentG * intensity / 255, 0); colors[(std::int32_t)AuraLight::Tab + i] = Color(percentR * intensity / (255 * 12), percentG * intensity / (255 * 12), 0); } } rgbLights.Update(colors); #endif } Color HUD::ApplyRgbGradientAlpha(Color color, std::int32_t x, std::int32_t y, float animProgress, float ambientLight) { constexpr std::int32_t Width = AURA_KEYBOARD_WIDTH; constexpr std::int32_t Height = AURA_KEYBOARD_HEIGHT; constexpr float AnimationMult = 0.11f; constexpr std::int32_t Spacing = 1; float ambientLightLower = std::clamp(ambientLight, 0.0f, 0.5f) * 2.0f; float ambientLightUpper = (std::clamp(ambientLight, 0.5f, 0.8f) - 0.5f) * 2.0f; float distance = sqrtf(powf((float)(Width - x), 2) + powf((float)(Height - y), 2)); float value = cosf(AnimationMult * distance / (0.1f * Spacing) + animProgress); float alpha = lerp(powf((value + 1) * 0.5f, 12.0f) * ambientLightLower, 0.8f, ambientLightUpper); return Color(std::clamp((std::uint32_t)(color.R * alpha), 0u, 255u), std::clamp((std::uint32_t)(color.G * alpha), 0u, 255u), std::clamp((std::uint32_t)(color.B * alpha), 0u, 255u)); } AuraLight HUD::KeyToAuraLight(Keys key) { switch (key) { case Keys::Backspace: return AuraLight::Backspace; case Keys::Tab: return AuraLight::Tab; case Keys::Return: return AuraLight::Enter; case Keys::Escape: return AuraLight::Esc; case Keys::Space: return AuraLight::Space; //case Keys::Quote: return AuraLight::Quote; //case Keys::Plus: return AuraLight::Plus; case Keys::Comma: return AuraLight::Comma; case Keys::Minus: return AuraLight::Minus; case Keys::Period: return AuraLight::Period; case Keys::Slash: return AuraLight::Slash; case Keys::D0: return AuraLight::Zero; case Keys::D1: return AuraLight::One; case Keys::D2: return AuraLight::Two; case Keys::D3: return AuraLight::Three; case Keys::D4: return AuraLight::Four; case Keys::D5: return AuraLight::Five; case Keys::D6: return AuraLight::Six; case Keys::D7: return AuraLight::Seven; case Keys::D8: return AuraLight::Eight; case Keys::D9: return AuraLight::Nine; case Keys::Semicolon: return AuraLight::Semicolon; case Keys::LeftBracket: return AuraLight::OpenBracket; case Keys::Backslash: return AuraLight::Backslash; case Keys::RightBracket: return AuraLight::CloseBracket; case Keys::Backquote: return AuraLight::Tilde; case Keys::A: return AuraLight::A; case Keys::B: return AuraLight::B; case Keys::C: return AuraLight::C; case Keys::D: return AuraLight::D; case Keys::E: return AuraLight::E; case Keys::F: return AuraLight::F; case Keys::G: return AuraLight::G; case Keys::H: return AuraLight::H; case Keys::I: return AuraLight::I; case Keys::J: return AuraLight::J; case Keys::K: return AuraLight::K; case Keys::L: return AuraLight::L; case Keys::M: return AuraLight::M; case Keys::N: return AuraLight::N; case Keys::O: return AuraLight::O; case Keys::P: return AuraLight::P; case Keys::Q: return AuraLight::Q; case Keys::R: return AuraLight::R; case Keys::S: return AuraLight::S; case Keys::T: return AuraLight::T; case Keys::U: return AuraLight::U; case Keys::V: return AuraLight::V; case Keys::W: return AuraLight::W; case Keys::X: return AuraLight::X; case Keys::Y: return AuraLight::Y; case Keys::Z: return AuraLight::Z; case Keys::Delete: return AuraLight::Delete; case Keys::NumPad0: return AuraLight::NumZero; case Keys::NumPad1: return AuraLight::NumOne; case Keys::NumPad2: return AuraLight::NumTwo; case Keys::NumPad3: return AuraLight::NumThree; case Keys::NumPad4: return AuraLight::NumFour; case Keys::NumPad5: return AuraLight::NumFive; case Keys::NumPad6: return AuraLight::NumSix; case Keys::NumPad7: return AuraLight::NumSeven; case Keys::NumPad8: return AuraLight::NumEight; case Keys::NumPad9: return AuraLight::NumNine; case Keys::NumPadPeriod: return AuraLight::NumPeriod; case Keys::NumPadDivide: return AuraLight::NumSlash; case Keys::NumPadMultiply: return AuraLight::NumAsterisk; case Keys::NumPadMinus: return AuraLight::NumMinus; case Keys::NumPadPlus: return AuraLight::NumPlus; case Keys::NumPadEnter: return AuraLight::NumEnter; case Keys::NumPadEquals: return AuraLight::NumEnter; case Keys::Up: return AuraLight::ArrowUp; case Keys::Down: return AuraLight::ArrowDown; case Keys::Right: return AuraLight::ArrowRight; case Keys::Left: return AuraLight::ArrowLeft; case Keys::Insert: return AuraLight::Insert; case Keys::Home: return AuraLight::Home; case Keys::End: return AuraLight::End; case Keys::PageUp: return AuraLight::PageUp; case Keys::PageDown: return AuraLight::PageDown; case Keys::F1: return AuraLight::F1; case Keys::F2: return AuraLight::F2; case Keys::F3: return AuraLight::F3; case Keys::F4: return AuraLight::F4; case Keys::F5: return AuraLight::F5; case Keys::F6: return AuraLight::F6; case Keys::F7: return AuraLight::F7; case Keys::F8: return AuraLight::F8; case Keys::F9: return AuraLight::F9; case Keys::F10: return AuraLight::F10; case Keys::F11: return AuraLight::F11; case Keys::F12: return AuraLight::F12; case Keys::NumLock: return AuraLight::NumLock; case Keys::CapsLock: return AuraLight::CapsLock; case Keys::ScrollLock: return AuraLight::ScrollLock; case Keys::RShift: return AuraLight::RightShift; case Keys::LShift: return AuraLight::LeftShift; case Keys::RCtrl: return AuraLight::RightCtrl; case Keys::LCtrl: return AuraLight::LeftCtrl; case Keys::RAlt: return AuraLight::RightAlt; case Keys::LAlt: return AuraLight::LeftAlt; case Keys::Pause: return AuraLight::PauseBreak; case Keys::Menu: return AuraLight::Menu; default: return AuraLight::Unknown; } } HUD::WeaponWheelState::WeaponWheelState() : Anim(0.0f), LastIndex(-1), Shown(false) { } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/HUD.h000066400000000000000000000156311512772601700226470ustar00rootroot00000000000000#pragma once #include "Canvas.h" #include "Font.h" #include "../LevelHandler.h" #include "../Actors/Player.h" #include "../Input/ControlScheme.h" #include "../Input/RgbLights.h" #include "../Rendering/PlayerViewport.h" #include "../../nCine/Input/InputEvents.h" #if defined(WITH_ANGELSCRIPT) namespace Jazz2::Scripting { class LevelScriptLoader; } namespace Jazz2::Scripting::Legacy { struct jjCANVAS; } #endif namespace Jazz2::UI { /** @brief Player HUD */ class HUD : public Canvas { #if defined(WITH_ANGELSCRIPT) friend class Scripting::LevelScriptLoader; friend struct Scripting::Legacy::jjCANVAS; #endif public: #ifndef DOXYGEN_GENERATING_OUTPUT static constexpr float DpadLeft = 0.02f; static constexpr float DpadBottom = 0.1f; static constexpr float DpadThreshold = 0.09f; static constexpr float DpadSize = 0.37f; static constexpr float ButtonSize = 0.172f; static constexpr float SmallButtonSize = 0.098f; #endif HUD(LevelHandler* levelHandler); ~HUD(); void OnUpdate(float timeMult) override; bool OnDraw(RenderQueue& renderQueue) override; void OnTouchEvent(const TouchEvent& event, std::uint32_t& overrideActions); /** @brief Shows a text notification */ void ShowLevelText(StringView text); /** @brief Shows a notification about coins */ void ShowCoins(std::int32_t count); /** @brief Shows a notification about gems */ void ShowGems(std::uint8_t gemType, std::int32_t count); /** @brief Begins a fullscreen fade-in transition */ void BeginFadeIn(bool skip); /** @brief Begins a fullscreen face-out transition */ void BeginFadeOut(float delay = 0.0f); /** @brief Returns `true` if weapon wheel is visible */ bool IsWeaponWheelVisible(std::int32_t playerIndex) const; protected: #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't struct TouchButtonInfo { PlayerAction Action; float Left; float Top; float Width; float Height; GraphicResource* Graphics; std::int32_t CurrentPointerId; Alignment Align; }; struct Vertex { float X, Y; float U, V; Vertex() : X(0.0f), Y(0.0f), U(0.0f), V(0.0f) {} Vertex(float x, float y, float u, float v) : X(x), Y(y), U(u), V(v) {} }; struct WeaponWheelState { SmallVector, 0> RenderCommands; std::unique_ptr Vertices; std::int32_t WeaponCount; std::int32_t RenderCommandsCount; std::int32_t VerticesCount; std::int32_t LastIndex; float Anim; bool Shown; WeaponWheelState(); }; #endif /** @{ @name Constants */ /** @brief Main layer */ static constexpr std::uint16_t MainLayer = 100; /** @brief Shadow for main layer */ static constexpr std::uint16_t ShadowLayer = 80; /** @brief Font layer */ static constexpr std::uint16_t FontLayer = 200; /** @brief Shadow for font layer */ static constexpr std::uint16_t FontShadowLayer = 120; /** @brief Touch buttons layer */ static constexpr std::uint16_t TouchButtonsLayer = 400; /** @} */ #ifndef DOXYGEN_GENERATING_OUTPUT LevelHandler* _levelHandler; Metadata* _metadata; Font* _smallFont; String _levelText; float _levelTextTime; std::int32_t _coins; std::int32_t _gems; float _coinsTime; float _gemsTime; std::uint8_t _gemsLastType; float _activeBossTime; #endif /** @brief Called when health of the player needs to be drawn */ virtual void OnDrawHealth(const Rectf& view, const Rectf& adjustedView, Actors::Player* player); /** @brief Called when score of the player needs to be drawn */ virtual void OnDrawScore(const Rectf& view, Actors::Player* player); /** @brief Called when weapon ammo of the player needs to be drawn */ virtual void OnDrawWeaponAmmo(const Rectf& adjustedView, Actors::Player* player); /** @brief Called when health of the active boss needs to be drawn */ virtual void OnDrawActiveBoss(const Rectf& adjustedView); /** @brief Called when a text notification needs to be drawn */ virtual void OnDrawLevelText(std::int32_t& charOffset); /** @brief Called when a notification about coins of the player needs to be drawn */ virtual void OnDrawCoins(const Rectf& view, std::int32_t& charOffset); /** @brief Called when a notification about gems of the player needs to be drawn */ virtual void OnDrawGems(const Rectf& view, std::int32_t& charOffset); /** @brief Called when touch buttons need to be drawn */ void OnDrawTouchButtons(Actors::Player* player); /** @brief Draws carrotized health bar (Reforged) */ void DrawHealthCarrots(float x, float y, std::int32_t health); /** @brief Draws separators of split-screen viewports */ void DrawViewportSeparators(); /** @brief Draws a textured element */ void DrawElement(AnimState state, std::int32_t frame, float x, float y, std::uint16_t z, Alignment align, const Colorf& color, float scaleX = 1.0f, float scaleY = 1.0f, bool additiveBlending = false, float angle = 0.0f); /** @brief Draws a textured element with clipping */ void DrawElementClipped(AnimState state, std::int32_t frame, float x, float y, std::uint16_t z, Alignment align, const Colorf& color, float clipX, float clipY); private: enum class TransitionState { None, WaitingForFadeIn, FadeIn, FadeOut, WaitingForFadeOut }; static constexpr std::uint32_t VertexBytes = sizeof(Vertex); static constexpr std::uint32_t VertexFloats = VertexBytes / sizeof(float); static constexpr Alignment Fixed = (Alignment)0x40; static constexpr Alignment AllowRollover = (Alignment)0x80; static constexpr std::int32_t TouchButtonsCount = 11; static constexpr float WeaponWheelAnimDuration = 20.0f; static constexpr std::int32_t WeaponWheelMaxVertices = 768; float _rgbAmbientLight; float _rgbHealthLast; WeaponWheelState _weaponWheel[ControlScheme::MaxSupportedPlayers]; float _rgbLightsAnim; float _rgbLightsTime; TransitionState _transitionState; float _transitionTime; TouchButtonInfo _touchButtons[TouchButtonsCount]; float _touchButtonsTimer; AnimState GetCurrentWeapon(Actors::Player* player, WeaponType weapon, Vector2f& offset); void DrawWeaponWheel(const Rectf& view, Actors::Player* player); void UpdateWeaponWheel(float timeMult); bool PrepareWeaponWheel(Actors::Player* player, std::int32_t& weaponCount); static std::int32_t GetWeaponCount(Actors::Player* player); void DrawWeaponWheelSegment(WeaponWheelState& state, float x, float y, float width, float height, std::uint16_t z, float minAngle, float maxAngle, const Texture& texture, const Colorf& color); TouchButtonInfo CreateTouchButton(PlayerAction action, AnimState state, Alignment align, float x, float y, float w, float h); bool IsOnButton(const TouchButtonInfo& button, float x, float y); void UpdateRgbLights(float timeMult, Rendering::PlayerViewport* viewport); static Color ApplyRgbGradientAlpha(Color color, std::int32_t x, std::int32_t y, float animProgress, float ambientLight); static AuraLight KeyToAuraLight(Keys key); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/InGameConsole.cpp000066400000000000000000000317331512772601700252460ustar00rootroot00000000000000#include "InGameConsole.h" #include "FormattedTextBlock.h" #include "../ContentResolver.h" #include "../LevelHandler.h" #include "../PreferencesCache.h" #include "../../nCine/Application.h" #include "../../nCine/I18n.h" #include "../../nCine/Graphics/RenderQueue.h" #include "../../nCine/Input/IInputManager.h" #include #include namespace Jazz2::UI { struct LogLine { MessageLevel Level; float TimeLeft; FormattedTextBlock Message; LogLine(MessageLevel level, String&& message, Font* font); }; static SmallVector _logHistory; static SmallVector _commandHistory; InGameConsole::InGameConsole(LevelHandler* levelHandler) : _levelHandler(levelHandler), _currentLine{}, _textCursor(0), _carretAnim(0.0f), _scrollPos(0), _isVisible(false) { PruneLogHistory(); _historyIndex = _commandHistory.size(); } InGameConsole::~InGameConsole() { } void InGameConsole::OnInitialized() { auto& resolver = ContentResolver::Get(); _smallFont = resolver.GetFont(FontType::Small); for (auto& line : _logHistory) { line.Message.SetFont(_smallFont); } } void InGameConsole::OnUpdate(float timeMult) { Canvas::OnUpdate(timeMult); _carretAnim += timeMult; for (std::int32_t i = _logHistory.size() - 1; i >= 0; i--) { auto& line = _logHistory[i]; if (line.TimeLeft <= 0.0f) { break; } line.TimeLeft -= timeMult; } } bool InGameConsole::OnDraw(RenderQueue& renderQueue) { Canvas::OnDraw(renderQueue); ViewSize = _levelHandler->GetViewSize(); Vector2f currentLinePos = Vector2f(46.0f, ViewSize.Y - 60.0f); float width = ViewSize.X - currentLinePos.X - 24.0f; if (_isVisible) { DrawSolid(Vector2f(0.0f, 0.0f), 80, ViewSize.As(), Colorf(0.0f, 0.0f, 0.0f, std::min(AnimTime * 5.0f, 0.6f))); Colorf color = (_currentLine[0] == '/' ? Colorf(0.38f, 0.48f, 0.69f, 0.5f) : Colorf(0.62f, 0.44f, 0.34f, 0.5f)); // Current line std::int32_t charOffset = 0, charOffsetShadow = 0; _smallFont->DrawString(this, ">"_s, charOffsetShadow, currentLinePos.X - 16.0f + 1.0f, currentLinePos.Y + 2.0f, FontShadowLayer + 100, Alignment::Left, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 0.8f, 0.0f, 0.0f, 0.0f); _smallFont->DrawString(this, ">"_s, charOffset, currentLinePos.X - 16.0f, currentLinePos.Y, FontLayer + 100, Alignment::Left, color, 0.8f, 0.0f, 0.0f, 0.0f); StringView currentLine = _currentLine; _smallFont->DrawString(this, currentLine, charOffsetShadow, currentLinePos.X + 1.0f, currentLinePos.Y + 2.0f, FontShadowLayer + 100, Alignment::Left, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 0.8f, 0.0f, 0.0f, 0.0f); _smallFont->DrawString(this, currentLine, charOffset, currentLinePos.X, currentLinePos.Y, FontLayer + 100, Alignment::Left, color, 0.8f, 0.0f, 0.0f, 0.0f); // Carret Vector2f textToCursorSize = _smallFont->MeasureString(StringView{_currentLine, _textCursor}, 0.8f); DrawSolid(Vector2f(currentLinePos.X + textToCursorSize.X + 1.0f, currentLinePos.Y - 7.0f), FontLayer + 120, Vector2f(1.0f, 12.0f), Colorf(1.0f, 1.0f, 1.0f, std::clamp(sinf(_carretAnim * 0.1f) * 1.4f, 0.0f, 0.8f)), true); } // History if (_scrollPos > (std::int32_t)_logHistory.size() - 1) { _scrollPos = 0; } Vector2f historyLinePos = currentLinePos; historyLinePos.Y -= 4.0f; for (std::int32_t i = _logHistory.size() - 1 - _scrollPos; i >= 0; i--) { if (historyLinePos.Y < 50.0f) { break; } auto& line = _logHistory[i]; if (line.Level == MessageLevel::Debug && !_isVisible) { continue; } float alpha = (_isVisible ? 1.0f : std::min(line.TimeLeft * 0.06f, 1.0f)); if (alpha <= 0.0f) { break; } Colorf color; switch (line.Level) { default: color = Font::DefaultColor; color.A *= alpha; break; case MessageLevel::Echo: color = Font::DefaultColor; color.A *= alpha; break; case MessageLevel::Debug: color = Colorf(0.36f, 0.44f, 0.35f, 0.5f * sqrtf(alpha)); break; case MessageLevel::Error: color = Colorf(0.6f, 0.41f, 0.40f, 0.5f * sqrtf(alpha)); break; case MessageLevel::Fatal: color = Colorf(0.48f, 0.38f, 0.34f, 0.5f * sqrtf(alpha)); break; } std::int32_t charOffset = 0; line.Message.SetDefaultColor(color); Vector2f textSize = line.Message.MeasureSize(Vector2f(width, 400.0f)); historyLinePos.Y -= textSize.Y; if (line.Level == MessageLevel::Echo) { std::int32_t charOffset = 0, charOffsetShadow = 0; _smallFont->DrawString(this, "›"_s, charOffsetShadow, historyLinePos.X - 13.0f, historyLinePos.Y + 2.0f, FontShadowLayer + 100, Alignment::Left, Colorf(0.0f, 0.0f, 0.0f, 0.3f * sqrtf(alpha)), 0.8f, 0.0f, 0.0f, 0.0f); _smallFont->DrawString(this, "›"_s, charOffset, historyLinePos.X - 13.0f, historyLinePos.Y, FontLayer + 100, Alignment::Left, color, 0.8f, 0.0f, 0.0f, 0.0f); } line.Message.Draw(this, Rectf(historyLinePos.X, historyLinePos.Y, width, 400.0f), FontLayer + 100, charOffset); } return true; } void InGameConsole::OnKeyPressed(const KeyboardEvent& event) { switch (event.sym) { case Keys::Return: case Keys::NumPadEnter: { ProcessCurrentLine(); break; } case Keys::Tab: { // If the console is bound to Tab key, allow to toggle it off if (ControlScheme::ContainsTarget(PlayerAction::Console, ControlScheme::CreateTarget(Keys::Tab))) { Hide(); } break; } case Keys::Backspace: { if (_textCursor > 0) { std::size_t lengthAfter = std::strlen(&_currentLine[_textCursor]); std::size_t startPos; if ((event.mod & KeyMod::Ctrl) != KeyMod::None) { auto found = StringView{_currentLine, _textCursor}.findLastOr(' ', _currentLine); startPos = found.begin() - _currentLine; } else { auto [_, prevPos] = Utf8::PrevChar(_currentLine, _textCursor); startPos = prevPos; } std::memmove(&_currentLine[startPos], &_currentLine[_textCursor], lengthAfter + 1); _textCursor = startPos; _carretAnim = 0.0f; } break; } case Keys::Delete: { if (_currentLine[_textCursor] != '\0') { std::size_t lengthAfter = std::strlen(&_currentLine[_textCursor]); std::size_t endPos; if ((event.mod & KeyMod::Ctrl) != KeyMod::None) { auto found = StringView{&_currentLine[_textCursor]}.findOr(' ', &_currentLine[_textCursor + lengthAfter]); endPos = found.end() - _currentLine; } else { auto [_, nextPos] = Utf8::NextChar(_currentLine, _textCursor); endPos = nextPos; } std::memmove(&_currentLine[_textCursor], &_currentLine[endPos], lengthAfter + 1); _carretAnim = 0.0f; } break; } case Keys::Left: { if (_textCursor > 0) { auto [c, prevPos] = Utf8::PrevChar(_currentLine, _textCursor); _textCursor = prevPos; _carretAnim = 0.0f; } break; } case Keys::Right: { if (_currentLine[_textCursor] != '\0') { auto [c, nextPos] = Utf8::NextChar(_currentLine, _textCursor); _textCursor = nextPos; _carretAnim = 0.0f; } break; } case Keys::Up: { if (event.mod & KeyMod::Ctrl) { ScrollUp(1); } else { GetPreviousCommandFromHistory(); } break; } case Keys::Down: { if (event.mod & KeyMod::Ctrl) { ScrollDown(1); } else { GetNextCommandFromHistory(); } break; } case Keys::PageUp: { ScrollUp(3); break; } case Keys::PageDown: { ScrollDown(3); break; } case Keys::K: { // Clear line if ((event.mod & KeyMod::Ctrl) != KeyMod::None && (event.mod & (KeyMod::Alt | KeyMod::Shift)) == KeyMod::None) { _currentLine[0] = '\0'; _textCursor = 0; _carretAnim = 0.0f; } break; } case Keys::L: { // Clear history if ((event.mod & KeyMod::Ctrl) != KeyMod::None && (event.mod & (KeyMod::Alt | KeyMod::Shift)) == KeyMod::None) { _logHistory.clear(); } break; } case Keys::V: { // Paste if ((event.mod & KeyMod::Ctrl) != KeyMod::None && (event.mod & (KeyMod::Alt | KeyMod::Shift)) == KeyMod::None) { auto text = theApplication().GetInputManager().getClipboardText(); if (!text.empty()) { auto trimmedText = text.trimmedPrefix(); if (auto firstNewLine = trimmedText.find('\n')) { trimmedText = trimmedText.prefix(firstNewLine.begin()); } trimmedText = trimmedText.trimmedSuffix(); std::size_t lengthTotal = std::strlen(_currentLine); if (lengthTotal + trimmedText.size() < MaxLineLength) { std::size_t lengthAfter = std::strlen(&_currentLine[_textCursor]); if (lengthAfter > 0) { std::memmove(&_currentLine[_textCursor + trimmedText.size()], &_currentLine[_textCursor], lengthAfter); } _currentLine[_textCursor + trimmedText.size() + lengthAfter] = '\0'; std::memcpy(&_currentLine[_textCursor], trimmedText.data(), trimmedText.size()); _textCursor += trimmedText.size(); _carretAnim = 0.0f; } } } break; } } } void InGameConsole::OnTextInput(const TextInputEvent& event) { if (_textCursor + event.length < MaxLineLength) { std::size_t lengthAfter = std::strlen(&_currentLine[_textCursor]); if (lengthAfter > 0) { std::memmove(&_currentLine[_textCursor + event.length], &_currentLine[_textCursor], lengthAfter); } _currentLine[_textCursor + event.length + lengthAfter] = '\0'; std::memcpy(&_currentLine[_textCursor], event.text, event.length); _textCursor += event.length; _carretAnim = 0.0f; } } void InGameConsole::Clear() { _logHistory.clear(); } bool InGameConsole::IsVisible() const { return _isVisible; } void InGameConsole::Show() { // Don't allow to show the console if it was hidden in the last frame (mainly to fix toggling with Tab key) if (AnimTime <= AnimTimeMultiplier) { return; } _isVisible = true; _currentLine[0] = '\0'; _textCursor = 0; _carretAnim = 0.0f; AnimTime = 0.0f; } void InGameConsole::Hide() { _isVisible = false; AnimTime = 0.0f; } void InGameConsole::WriteLine(MessageLevel level, String line) { #if defined(DEATH_TRACE) switch (level) { //case MessageLevel::Info: is skipped, because these messages are usually written to the log separately case MessageLevel::Echo: __DEATH_TRACE(TraceLevel::Info, {}, "> │ {}", Font::StripFormatting(line)); break; case MessageLevel::Chat: case MessageLevel::Confirm: __DEATH_TRACE(TraceLevel::Info, {}, "< │ {}", Font::StripFormatting(line)); break; case MessageLevel::Warning: __DEATH_TRACE(TraceLevel::Warning, {}, "< │ {}", Font::StripFormatting(line)); break; case MessageLevel::Error: __DEATH_TRACE(TraceLevel::Error, {}, "< │ {}", Font::StripFormatting(line)); break; case MessageLevel::Assert: __DEATH_TRACE(TraceLevel::Assert, {}, "< │ {}", Font::StripFormatting(line)); break; case MessageLevel::Fatal: __DEATH_TRACE(TraceLevel::Fatal, {}, "< │ {}", Font::StripFormatting(line)); break; } #endif _logHistory.emplace_back(level, std::move(line), _smallFont); _scrollPos = 0; } void InGameConsole::ProcessCurrentLine() { if (_currentLine[0] == '\0') { // Empty line return; } StringView line = _currentLine; if (_commandHistory.empty() || _commandHistory.back() != line) { _commandHistory.emplace_back(line); } _historyIndex = _commandHistory.size(); if (line == "/clear"_s || line == "/cls"_s) { Clear(); } else { if (!_levelHandler->OnConsoleCommand(line)) { WriteLine(MessageLevel::Echo, line); WriteLine(MessageLevel::Error, _("Unknown command")); } } _currentLine[0] = '\0'; _textCursor = 0; _carretAnim = 0.0f; } void InGameConsole::PruneLogHistory() { constexpr std::int32_t MaxHistoryLines = 64; if (_logHistory.size() > MaxHistoryLines) { _logHistory.erase(&_logHistory[0], &_logHistory[_logHistory.size() - MaxHistoryLines]); } } void InGameConsole::GetPreviousCommandFromHistory() { if (_historyIndex > 0) { _historyIndex--; std::memcpy(_currentLine, _commandHistory[_historyIndex].data(), _commandHistory[_historyIndex].size() + 1); _textCursor = _commandHistory[_historyIndex].size(); _carretAnim = 0.0f; } } void InGameConsole::GetNextCommandFromHistory() { if (_historyIndex < _commandHistory.size()) { _historyIndex++; if (_historyIndex == _commandHistory.size()) { _currentLine[0] = '\0'; _textCursor = 0; } else { std::memcpy(_currentLine, _commandHistory[_historyIndex].data(), _commandHistory[_historyIndex].size() + 1); _textCursor = _commandHistory[_historyIndex].size(); } _carretAnim = 0.0f; } } void InGameConsole::ScrollUp(std::int32_t amount) { _scrollPos += amount; if (_scrollPos > (std::int32_t)_logHistory.size() - 1) { _scrollPos = (std::int32_t)_logHistory.size() - 1; } } void InGameConsole::ScrollDown(std::int32_t amount) { _scrollPos -= amount; if (_scrollPos < 0) { _scrollPos = 0; } } LogLine::LogLine(MessageLevel level, String&& message, Font* font) : Level(level), TimeLeft(5.0f * FrameTimer::FramesPerSecond) { Message.SetText(std::move(message)); Message.SetScale(0.8f); Message.SetFont(font); Message.SetMultiline(true); Message.SetWrapping(true); } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/InGameConsole.h000066400000000000000000000036411512772601700247100ustar00rootroot00000000000000#pragma once #include "Canvas.h" #include "Font.h" #include "../LevelHandler.h" #include "../../nCine/Input/InputEvents.h" namespace Jazz2::UI { /** @brief Message importance level */ enum class MessageLevel { Unknown, /**< Unspecified */ Echo, /**< Echo of the input */ Debug, /**< Debug */ Info, /**< Info */ Chat, /**< Chat */ Confirm, /**< Confirmation */ Important, /**< Important */ Warning, /**< Warning */ Error, /**< Error */ Assert, /**< Assert */ Fatal /**< Fatal */ }; /** @brief In-game console */ class InGameConsole : public Canvas { public: InGameConsole(LevelHandler* levelHandler); ~InGameConsole(); void OnInitialized(); void OnUpdate(float timeMult) override; bool OnDraw(RenderQueue& renderQueue) override; void OnKeyPressed(const KeyboardEvent& event); void OnTextInput(const TextInputEvent& event); /** @brief Clears the console and its history */ static void Clear(); /** @brief Returns `true` if the console is visible */ bool IsVisible() const; /** @brief Shows the console */ void Show(); /** @brief Hides the console */ void Hide(); /** @brief Writes a line to the console history */ void WriteLine(MessageLevel level, String line); private: static constexpr std::uint16_t MainLayer = 100; static constexpr std::uint16_t ShadowLayer = 80; static constexpr std::uint16_t FontLayer = 200; static constexpr std::uint16_t FontShadowLayer = 120; static constexpr std::int32_t MaxLineLength = 128; LevelHandler* _levelHandler; Font* _smallFont; char _currentLine[MaxLineLength]; std::size_t _textCursor; float _carretAnim; std::int32_t _historyIndex; std::int32_t _scrollPos; bool _isVisible; void ProcessCurrentLine(); void PruneLogHistory(); void GetPreviousCommandFromHistory(); void GetNextCommandFromHistory(); void ScrollUp(std::int32_t amount); void ScrollDown(std::int32_t amount); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/LoadingHandler.cpp000066400000000000000000000063421512772601700254340ustar00rootroot00000000000000#include "LoadingHandler.h" #include "../../nCine/Application.h" namespace Jazz2::UI { LoadingHandler::LoadingHandler(IRootController* root, bool darkMode) : _root(root), _transition(0.0f), _darkMode(darkMode) { _canvasBackground = std::make_unique(this); auto& resolver = ContentResolver::Get(); _metadata = resolver.RequestMetadata("UI/Loading"_s); DEATH_ASSERT(_metadata != nullptr, "Cannot load required metadata", ); } LoadingHandler::LoadingHandler(IRootController* root, bool darkMode, Function&& callback) : LoadingHandler(root, darkMode) { _callback = std::move(callback); } LoadingHandler::~LoadingHandler() { _canvasBackground->setParent(nullptr); } Vector2i LoadingHandler::GetViewSize() const { return _upscalePass.GetViewSize(); } void LoadingHandler::OnBeginFrame() { float timeMult = theApplication().GetTimeMult(); if (_callback && _callback(_root)) { _callback = nullptr; } if (_transition < 1.0f) { _transition = std::min(_transition + timeMult * 0.04f, 1.0f); } } void LoadingHandler::OnInitializeViewport(std::int32_t width, std::int32_t height) { constexpr float defaultRatio = (float)DefaultWidth / DefaultHeight; float currentRatio = (float)width / height; std::int32_t w, h; if (currentRatio > defaultRatio) { w = std::min(DefaultWidth, width); h = (std::int32_t)roundf(w / currentRatio); } else if (currentRatio < defaultRatio) { h = std::min(DefaultHeight, height); w = (std::int32_t)roundf(h * currentRatio); } else { w = std::min(DefaultWidth, width); h = std::min(DefaultHeight, height); } _upscalePass.Initialize(w, h, width, height); // Viewports must be registered in reverse order _upscalePass.Register(); _canvasBackground->setParent(_upscalePass.GetNode()); } bool LoadingHandler::BackgroundCanvas::OnDraw(RenderQueue& renderQueue) { Canvas::OnDraw(renderQueue); ViewSize = _owner->_upscalePass.GetViewSize(); DrawSolid(Vector2f::Zero, 950, Vector2f(static_cast(ViewSize.X), static_cast(ViewSize.Y)), _owner->_darkMode ? Colorf::Black : Colorf::White); auto* loadingRes = _owner->_metadata->FindAnimation(AnimState::Idle); if (loadingRes != nullptr) { std::int32_t frame = loadingRes->FrameOffset + ((std::int32_t)(AnimTime * loadingRes->FrameCount / loadingRes->AnimDuration) % loadingRes->FrameCount); GenericGraphicResource* base = loadingRes->Base; Vector2f size = Vector2f(base->FrameDimensions.X, base->FrameDimensions.Y); Vector2f pos = Vector2f(ViewSize.X - size.X - 50.0f, ViewSize.Y - size.Y - 40.0f); Vector2i texSize = base->TextureDiffuse->GetSize(); std::int32_t col = frame % base->FrameConfiguration.X; std::int32_t row = frame / base->FrameConfiguration.X; Vector4f texCoords = Vector4f( float(base->FrameDimensions.X) / float(texSize.X), float(base->FrameDimensions.X * col) / float(texSize.X), float(base->FrameDimensions.Y) / float(texSize.Y), float(base->FrameDimensions.Y * row) / float(texSize.Y) ); Colorf color = Colorf(1.0f, 1.0f, 1.0f, (_owner->_darkMode ? 0.8f : 1.0f) * _owner->_transition); DrawTexture(*base->TextureDiffuse.get(), pos, 960, size, texCoords, color, false); } return true; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/LoadingHandler.h000066400000000000000000000027011512772601700250740ustar00rootroot00000000000000#pragma once #include "../IStateHandler.h" #include "../IRootController.h" #include "Canvas.h" #include "../ContentResolver.h" #include "../Rendering/UpscaleRenderPass.h" namespace Jazz2::UI { /** @brief Handler that only shows the loading indicator */ class LoadingHandler : public IStateHandler { public: /** @{ @name Constants */ /** @brief Default width of viewport */ static constexpr std::int32_t DefaultWidth = 720; /** @brief Default height of viewport */ static constexpr std::int32_t DefaultHeight = 405; /** @} */ LoadingHandler(IRootController* root, bool darkMode); LoadingHandler(IRootController* root, bool darkMode, Function&& callback); ~LoadingHandler() override; Vector2i GetViewSize() const override; void OnBeginFrame() override; void OnInitializeViewport(std::int32_t width, std::int32_t height) override; private: IRootController* _root; #ifndef DOXYGEN_GENERATING_OUTPUT // Doxygen 1.12.0 outputs also private structs/unions even if it shouldn't class BackgroundCanvas : public Canvas { public: BackgroundCanvas(LoadingHandler* owner) : _owner(owner) {} bool OnDraw(RenderQueue& renderQueue) override; private: LoadingHandler* _owner; }; #endif Rendering::UpscaleRenderPass _upscalePass; std::unique_ptr _canvasBackground; Metadata* _metadata; Function _callback; float _transition; bool _darkMode; }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Menu/000077500000000000000000000000001512772601700227545ustar00rootroot00000000000000deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Menu/AboutSection.cpp000066400000000000000000000343711512772601700260670ustar00rootroot00000000000000#include "AboutSection.h" #include "MenuResources.h" #include "../../../nCine/Application.h" #include "../../../nCine/I18n.h" #if defined(DEATH_TARGET_EMSCRIPTEN) # define _i1 "\nWebGL" #elif defined(DEATH_TARGET_WINDOWS_RT) # define _i1 "\nUWP" #elif defined(WITH_OPENGLES) # define _i1 "\nOpenGL│ES" #else # define _i1 "\nOpenGL" #endif // Reserved for future use #define _i2 "" #if defined(DEATH_CPU_USE_RUNTIME_DISPATCH) # if defined(DEATH_CPU_USE_IFUNC) # define _i3 " · GNU IF\f[h:82]UNC\f[/h]" # else # define _i3 " · \f[h:86]CPU Runtime Dispatch\f[/h]" # endif #else # define _i3 "" #endif #if defined(WITH_ANGLE) # define _i4 "\nANGLE \f[c:#707070]· \f[h:80]https://github.com/google/angle\f[/h]\f[/c]" #elif defined(DEATH_TARGET_WINDOWS_RT) # define _i4 "\nMesa \f[c:#707070]· \f[h:80]https://gitlab.freedesktop.org/mesa/mesa\f[/h]\f[/c]" #else # define _i4 "" #endif #if defined(WITH_GLFW) # if defined(EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3) # define _i5 "\nGLFW/contrib \f[c:#707070]· \f[h:80]https://github.com/pongasoft/emscripten-glfw\f[/h]\f[/c]" # else # define _i5 "\nGLFW \f[c:#707070]· \f[h:80]https://www.glfw.org/\f[/h]\f[/c]" # endif #elif defined(WITH_QT5) # define _i5 "\nQt5 \f[c:#707070]· \f[h:80]https://www.qt.io/\f[/h]\f[/c]" #elif defined(WITH_SDL) # define _i5 "\nSDL2 \f[c:#707070]· \f[h:80]https://www.libsdl.org/\f[/h]\f[/c]" #else # define _i5 "" #endif #if defined(WITH_GLEW) # define _i6 "\nGLEW \f[c:#707070]· \f[h:80]https://glew.sourceforge.net/\f[/h]\f[/c]" #else # define _i6 "" #endif #if defined(WITH_AUDIO) # define _i7 "\nOpenAL \f[c:#707070]· \f[h:80]https://github.com/kcat/openal-soft\f[/h]\f[/c]" #else # define _i7 "" #endif #if defined(WITH_VORBIS) # define _i8 "\nVorbis \f[c:#707070]· \f[h:80]https://github.com/xiph/vorbis\f[/h]\f[/c]" #else # define _i8 "" #endif #if defined(WITH_OPENMPT) # define _i9 "\nlibopenmpt \f[c:#707070]· \f[h:80]https://lib.openmpt.org/libopenmpt/\f[/h]\f[/c]" #else # define _i9 "" #endif #if defined(WITH_WEBP) # define _i10 "\nlibwebp \f[c:#707070]· \f[h:80]https://github.com/webmproject/libwebp\f[/h]\f[/c]" #else # define _i10 "" #endif #if defined(WITH_CURL) # define _i11 "\nlibcurl \f[c:#707070]· \f[h:80]https://curl.se/libcurl/\f[/h]\f[/c]" #else # define _i11 "" #endif #if defined(WITH_ZLIB) # define _i12 "\nzlib \f[c:#707070]· \f[h:80]https://www.zlib.net/\f[/h]\f[/c]" #else # define _i12 "" #endif #if defined(WITH_IMGUI) # define _i13 "\nImGui \f[c:#707070]· \f[h:80]https://github.com/ocornut/imgui\f[/h]\f[/c]" #else # define _i13 "" #endif #if defined(WITH_ANGELSCRIPT) # define _i14 "\nAngelScript \f[c:#707070]· \f[h:80]https://www.angelcode.com/angelscript/\f[/h]\f[/c]" #else # define _i14 "" #endif #if defined(WITH_BACKWARD) # define _i15 "\nBackward \f[c:#707070]· \f[h:80]https://github.com/bombela/backward-cpp\f[/h]\f[/c]" #else # define _i15 "" #endif #if defined(WITH_MULTIPLAYER) # define _i16 "\nENet \f[c:#707070]· \f[h:80]https://github.com/lsalzman/enet\f[/h]\f[/c]" #else # define _i16 "" #endif #define _i17 "\njsoncpp \f[c:#707070]· \f[h:80]https://github.com/open-source-parsers/jsoncpp\f[/h]\f[/c]\nParallel Hashmap \f[c:#707070]· \f[h:80]https://github.com/greg7mdp/parallel-hashmap\f[/h]\f[/c]\n\f[h:80]Parts of \f[/h]Corrade \f[c:#707070]· \f[h:80]https://github.com/mosra/corrade\f[/h]\f[/c]\nPattern-defeating quicksort \f[c:#707070]· \f[h:80]https://github.com/orlp/pdqsort\f[/h]\f[/c]" #if defined(WITH_TRACY) # define _i18 "\n\n\f[h:86]Tracy integration is enabled.\f[/h]" #else # define _i18 "" #endif #define ADDITIONAL_INFO _i1 _i2 _i3 _i4 _i5 _i6 _i7 _i8 _i9 _i10 _i11 _i12 _i13 _i14 _i15 _i16 _i17 _i18 using namespace Jazz2::UI::Menu::Resources; namespace Jazz2::UI::Menu { AboutSection::AboutSection() : _maxScrollOffset(0.0f), _touchTime(0.0f), _touchSpeed(0.0f), _scrollOffset(0.0f), _scrollRate(0.0f), _touchDirection(0), _autoScroll(true), _rewind(false) { auto& resolver = ContentResolver::Get(); char text[8192]; char* textPtr = text; std::size_t textSize = sizeof(text); // TRANSLATORS: Header text in About section auto headerText = _("Reimplementation of the game \f[c:#9e7056]Jazz Jackrabbit 2\f[/c] released in 1998. Supports various versions of the game (Shareware Demo, Holiday Hare '98, The Secret Files and Christmas Chronicles). Also, it partially supports some features of JJ2+ extension."); // TRANSLATORS: Link to website under header text in About section auto websiteText = _("For more information, visit the official website:"); // TRANSLATORS: Label in About section auto developersText = _("Developers"); // TRANSLATORS: Label in About section auto contributorsText = _("Contributors"); // TRANSLATORS: Label in About section auto translatorsText = _("Translators"); // TRANSLATORS: Footer text in About section auto footerText = _("This project uses modified \f[c:#9e7056]nCine\f[/c] game engine and following libraries:"); std::size_t length = formatInto({ textPtr, textSize }, "{}\n{}\n\f[w:80]\f[c:#707070]https://deat.tk/jazz2/\f[/c]\f[/w]\n\n\n" "\f[h:125]\f[j]{}\f[/j]\f[/h]\n\f[c:#d0705d]Dan R.\f[/c]\n\n\n" "\f[h:125]\f[j]{}\f[/j]\f[/h]\n\f[c:#707070]\f[w:80]JJ\f[h:86]2\f[/h]\f[/w]⁺\f[w:50] \f[/w]Team\f[/c]\n\f[c:#707070]arkamar\f[/c] \f[h:86](Gentoo maintainer)\f[/h]\n\f[c:#707070]Bioxxdevil\f[/c]\n\f[c:#707070]Chewi\f[/c] \f[h:86](Gentoo maintainer)\f[/h]\n\f[c:#707070]JWP\f[/c] \f[h:86](xtradeb maintainer)\f[/h]\n\f[c:#707070]Kreeblah\f[/c] \f[h:86](Homebrew maintainer)\f[/h]\n\f[c:#707070]Mwyann\f[/c]\n\f[c:#707070]nat\f[/c] \f[h:86](NixOS maintainer)\f[/h]\n\f[c:#707070]roox\f[/c] \f[h:86](OpenSUSE maintainer)\f[/h]\n\f[c:#707070]Schroedingers Cat\f[/c]\n\f[c:#707070]tunip3\f[/c]\n\f[c:#707070]x_Dub_CZ\f[/c]\n\f[c:#707070]Xandu\f[/c]\n\n\n" "\f[h:125]\f[j]{}\f[/j]\f[/h]\n", headerText, websiteText, developersText, contributorsText, translatorsText); textPtr += length; textSize -= length; // Search both "Content/Translations/" and "Cache/Translations/" for translators for (auto item : fs::Directory(fs::CombinePath(resolver.GetContentPath(), "Translations"_s), fs::EnumerationOptions::SkipDirectories)) { AddTranslator(item, textPtr, textSize); } for (auto item : fs::Directory(fs::CombinePath(resolver.GetCachePath(), "Translations"_s), fs::EnumerationOptions::SkipDirectories)) { AddTranslator(item, textPtr, textSize); } length = formatInto({ textPtr, textSize }, "\n\n{}{}", footerText, ADDITIONAL_INFO); textPtr += length; textSize -= length; _textBlock.SetAlignment(Alignment::Center); _textBlock.SetScale(0.8f); _textBlock.SetMultiline(true); _textBlock.SetWrapping(true); _textBlock.SetFont(resolver.GetFont(FontType::Small)); _textBlock.SetText(StringView{text, std::size_t(textPtr - text)}); _textBlockHeaderOnly.SetAlignment(Alignment::Center); _textBlockHeaderOnly.SetScale(0.8f); _textBlockHeaderOnly.SetMultiline(true); _textBlockHeaderOnly.SetWrapping(true); _textBlockHeaderOnly.SetFont(resolver.GetFont(FontType::Small)); _textBlockHeaderOnly.SetText(headerText); } Recti AboutSection::GetClipRectangle(const Recti& contentBounds) { return Recti(contentBounds.X, contentBounds.Y + TopLine - 1, contentBounds.W, contentBounds.H - TopLine - BottomLine + 2); } void AboutSection::OnUpdate(float timeMult) { if (_root->ActionHit(PlayerAction::Fire)) { // Approximation of scroll offsets, needs to be changed when header is changed if (_scrollOffset > 40.0f && _scrollOffset < 400.0f) { if (theApplication().OpenUrl("https://deat.tk/jazz2/"_s)) { _root->PlaySfx("MenuSelect"_s, 0.5f); } } } else if (_root->ActionHit(PlayerAction::Menu)) { _root->PlaySfx("MenuSelect"_s, 0.5f); _root->LeaveSection(); return; } else if (_root->ActionPressed(PlayerAction::Up)) { if (_scrollRate < MaxScrollRate) { _scrollRate += 0.16f * timeMult; } _scrollOffset -= _scrollRate * timeMult; _autoScroll = false; } else if (_root->ActionPressed(PlayerAction::Down)) { if (_scrollRate < MaxScrollRate) { _scrollRate += 0.16f * timeMult; } _scrollOffset += _scrollRate * timeMult; _autoScroll = false; } else { _scrollRate = 0.0f; if (_autoScroll) { _scrollOffset += (_rewind ? (AutoScrollRate * -40.0f) : AutoScrollRate) * timeMult; } } if (_touchSpeed > 0.0f) { if (_touchStart == Vector2f::Zero) { float scrollOffset = _scrollOffset + (_touchSpeed * (std::int32_t)_touchDirection * TouchKineticDivider * timeMult); if (scrollOffset < 0.0f && _touchDirection < 0) { scrollOffset = 0.0f; _touchDirection = 1; _touchSpeed *= TouchKineticDamping; } else if (scrollOffset > _maxScrollOffset && _touchDirection > 0) { scrollOffset = _maxScrollOffset; _touchDirection = -1; _touchSpeed *= TouchKineticDamping; } _scrollOffset = scrollOffset; } _touchSpeed = std::max(_touchSpeed - TouchKineticFriction * TouchKineticDivider * timeMult, 0.0f); } _touchTime += timeMult; } void AboutSection::OnDraw(Canvas* canvas) { Recti contentBounds = _root->GetContentBounds(); float centerX = contentBounds.X + contentBounds.W * 0.5f; float topLine = contentBounds.Y + TopLine; float bottomLine = contentBounds.Y + contentBounds.H - BottomLine; _root->DrawElement(MenuDim, centerX, (topLine + bottomLine) * 0.5f, IMenuContainer::BackgroundLayer, Alignment::Center, Colorf::Black, Vector2f(680.0f, bottomLine - topLine + 2.0f), Vector4f(1.0f, 0.0f, 0.4f, 0.3f)); _root->DrawElement(MenuLine, 0, centerX, topLine, IMenuContainer::MainLayer, Alignment::Center, Colorf::White, 1.6f); _root->DrawElement(MenuLine, 1, centerX, bottomLine, IMenuContainer::MainLayer, Alignment::Center, Colorf::White, 1.6f); } void AboutSection::OnDrawClipped(Canvas* canvas) { Recti contentBounds = _root->GetContentBounds(); float centerX = contentBounds.X + contentBounds.W * 0.5f; float topLine = contentBounds.Y + TopLine; float bottomLine = contentBounds.Y + contentBounds.H - BottomLine; float viewHeight = (bottomLine - topLine); float textBlockHeight = _textBlock.GetCachedHeight(); _maxScrollOffset = textBlockHeight + viewHeight; if (_autoScroll && _rewind && _scrollOffset < viewHeight - 16.0f) { _scrollOffset = viewHeight - 16.0f; _autoScroll = false; } else if (_scrollOffset < 0.0f || textBlockHeight <= 0.0f) { _scrollOffset = 0.0f; } else if (_scrollOffset > _maxScrollOffset) { _scrollOffset = _maxScrollOffset; if (_autoScroll) { _rewind = true; } } float padding = (contentBounds.W > 450.0f ? 60.0f : 20.0f); Vector2f headerSize = _textBlockHeaderOnly.MeasureSize(Vector2f(contentBounds.W - padding * 2.0f, 1000000.0f)); _root->DrawElement(MenuGlow, 0, centerX, topLine + viewHeight + headerSize.Y + 14.0f + 2.0f - roundf(_scrollOffset), IMenuContainer::MainLayer, Alignment::Center, Colorf(1.0f, 1.0f, 1.0f, 0.4f), 10.0f, 5.0f, true, true); int32_t charOffset = 0; _textBlock.Draw(canvas, Rectf(contentBounds.X + padding, topLine + viewHeight - roundf(_scrollOffset), contentBounds.W - padding * 2.0f, 1000000.0f), IMenuContainer::FontLayer, charOffset, 0.7f, 1.0f, 1.0f); if (_scrollOffset > viewHeight - 6.0f) { _root->DrawElement(MenuGlow, 0, centerX, topLine, 900, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 30.0f, 5.0f); } if (_scrollOffset < _maxScrollOffset - viewHeight) { _root->DrawElement(MenuGlow, 0, centerX, bottomLine, 900, Alignment::Center, Colorf(0.0f, 0.0f, 0.0f, 0.3f), 30.0f, 5.0f); } } void AboutSection::OnTouchEvent(const nCine::TouchEvent& event, Vector2i viewSize) { switch (event.type) { case TouchEventType::Down: { std::int32_t pointerIndex = event.findPointerIndex(event.actionIndex); if (pointerIndex != -1) { float y = event.pointers[pointerIndex].y * (float)viewSize.Y; if (y < 80.0f) { _root->PlaySfx("MenuSelect"_s, 0.5f); _root->LeaveSection(); return; } _touchStart = Vector2f(event.pointers[pointerIndex].x * viewSize.X, y); _touchLast = _touchStart; _touchTime = 0.0f; _autoScroll = false; } break; } case TouchEventType::Move: { if (_touchStart != Vector2f::Zero) { std::int32_t pointerIndex = event.findPointerIndex(event.actionIndex); if (pointerIndex != -1) { Vector2f touchMove = Vector2f(event.pointers[pointerIndex].x * viewSize.X, event.pointers[pointerIndex].y * viewSize.Y); float delta = _touchLast.Y - touchMove.Y; if (delta != 0.0f) { _scrollOffset += delta; if (delta < -0.1f && _touchDirection >= 0) { _touchDirection = -1; _touchSpeed = 0.0f; } else if (delta > 0.1f && _touchDirection <= 0) { _touchDirection = 1; _touchSpeed = 0.0f; } _touchSpeed = (0.8f * _touchSpeed) + (0.2f * std::abs(delta) / TouchKineticDivider); } _touchLast = touchMove; } } break; } case TouchEventType::Up: { bool alreadyMoved = (_touchStart == Vector2f::Zero || (_touchStart - _touchLast).SqrLength() > 100 || _touchTime > FrameTimer::FramesPerSecond); _touchStart = Vector2f::Zero; if (alreadyMoved) { return; } if (_touchLast.Y > 120.0f) { // Approximation of scroll offsets, needs to be changed when header is changed if (_scrollOffset > 40.0f && _scrollOffset < 400.0f) { if (theApplication().OpenUrl("https://deat.tk/jazz2/"_s)) { _root->PlaySfx("MenuSelect"_s, 0.5f); } } } break; } } } void AboutSection::AddTranslator(StringView languageFile, char*& result, std::size_t& resultLength) { if (fs::GetExtension(languageFile) != "mo"_s) { return; } auto language = fs::GetFileNameWithoutExtension(languageFile); if (language.empty() || language.size() >= sizeof(PreferencesCache::Language)) { return; } I18n i18n; if (!i18n.LoadFromFile(languageFile)) { return; } String desc = i18n.GetTranslationDescription(); std::size_t descLength = desc.size(); if (descLength == 0 || descLength >= resultLength) { return; } StringView langName = I18n::GetLanguageName(language); std::int32_t lineLength; if (langName) { lineLength = formatInto({ result, resultLength }, "\f[c:#707070]{}\f[/c] \f[h:86]({})\f[/h]\n", desc, langName); } else { lineLength = formatInto({ result, resultLength }, "\f[c:#707070]{}\f[/c]\n", desc); } result += lineLength; resultLength -= lineLength; } }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Menu/AboutSection.h000066400000000000000000000020731512772601700255260ustar00rootroot00000000000000#pragma once #include "MenuSection.h" #include "../FormattedTextBlock.h" namespace Jazz2::UI::Menu { /** @brief About menu section */ class AboutSection : public MenuSection { public: AboutSection(); Recti GetClipRectangle(const Recti& contentBounds) override; void OnUpdate(float timeMult) override; void OnDraw(Canvas* canvas) override; void OnDrawClipped(Canvas* canvas) override; void OnTouchEvent(const nCine::TouchEvent& event, Vector2i viewSize) override; private: static constexpr std::int32_t TopLine = 2; static constexpr std::int32_t BottomLine = 42; static constexpr float AutoScrollRate = 0.4f; static constexpr float MaxScrollRate = 8.0f; FormattedTextBlock _textBlock; FormattedTextBlock _textBlockHeaderOnly; Vector2f _touchStart; Vector2f _touchLast; float _maxScrollOffset; float _touchTime; float _touchSpeed; float _scrollOffset; float _scrollRate; std::int8_t _touchDirection; bool _autoScroll; bool _rewind; void AddTranslator(StringView languageFile, char*& result, std::size_t& resultLength); }; }deathkiller-jazz2-native-2a7ccef/Sources/Jazz2/UI/Menu/BeginSection.cpp000066400000000000000000000401761512772601700260410ustar00rootroot00000000000000#include "BeginSection.h" #include "MenuResources.h" #include "EpisodeSelectSection.h" #include "StartGameOptionsSection.h" #include "HighscoresSection.h" #include "OptionsSection.h" #include "AboutSection.h" #include "MainMenu.h" #if defined(WITH_MULTIPLAYER) # include "PlayMultiplayerSection.h" #endif #if defined(SHAREWARE_DEMO_ONLY) # if defined(DEATH_TARGET_EMSCRIPTEN) # include "ImportSection.h" # endif # include "../../PreferencesCache.h" #endif #include "../../../nCine/Application.h" #include "../../../nCine/I18n.h" #if defined(DEATH_TARGET_ANDROID) # include "../../../nCine/Backends/Android/AndroidApplication.h" # include "../../../nCine/Backends/Android/AndroidJniHelper.h" #endif #include #include #include using namespace Jazz2::UI::Menu::Resources; namespace Jazz2::UI::Menu { BeginSection::BeginSection() : _selectedIndex(0), _animation(0.0f), _isPlayable(true), _skipSecondItem(false), _shouldStart(false), _alreadyStarted(false) { } void BeginSection::OnShow(IMenuContainer* root) { MenuSection::OnShow(root); #if defined(DEATH_TARGET_EMSCRIPTEN) _isEmbedded = Environment::IsEmbedded(); #else if (auto* mainMenu = runtime_cast(_root)) { _isPlayable = ((mainMenu->_root->GetFlags() & IRootController::Flags::IsPlayable) == IRootController::Flags::IsPlayable); } #endif _animation = 0.0f; _items.clear(); #if defined(SHAREWARE_DEMO_ONLY) if (PreferencesCache::UnlockedEpisodes != UnlockableEpisodes::None) { // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::PlaySingleplayer, _("Play Story")); } else { // TRANSLATORS: Menu item in main menu (Emscripten only) _items.emplace_back(Item::PlaySingleplayer, _("Play Shareware Demo")); } # if defined(DEATH_TARGET_EMSCRIPTEN) // TRANSLATORS: Menu item in main menu (Emscripten only) _items.emplace_back(Item::Import, _("Import Episodes")); # endif #else if (_isPlayable && root->HasResumableState()) { // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::Continue, _("Continue")); } // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::PlaySingleplayer, _("Play Story")); # if defined(WITH_MULTIPLAYER) if (_isPlayable) { // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::PlayMultiplayer, _("Play Online")); } # endif #endif // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::Highscores, _("Highscores")); // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::Options, _("Options")); // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::About, _("About")); #if !defined(DEATH_TARGET_IOS) && !defined(DEATH_TARGET_SWITCH) # if defined(DEATH_TARGET_EMSCRIPTEN) // Show quit button only in PWA/standalone environment if (PreferencesCache::IsStandalone) # endif // TRANSLATORS: Menu item in main menu _items.emplace_back(Item::Quit, _("Quit")); #endif #if !defined(DEATH_TARGET_EMSCRIPTEN) if (!_isPlayable) { auto& resolver = ContentResolver::Get(); _sourcePath = fs::GetAbsolutePath(resolver.GetSourcePath()); if (_sourcePath.empty()) { // If `Source` directory doesn't exist, GetAbsolutePath() will fail _sourcePath = fs::CombinePath(fs::GetWorkingDirectory(), resolver.GetSourcePath()); } # if defined(DEATH_TARGET_APPLE) || defined(DEATH_TARGET_UNIX) String homeDirectory = fs::GetHomeDirectory(); if (!homeDirectory.empty()) { StringView pathSeparator = fs::PathSeparator; if (!homeDirectory.hasSuffix(pathSeparator)) { homeDirectory += pathSeparator; } if (_sourcePath.hasPrefix(homeDirectory)) { _sourcePath = "~"_s + _sourcePath.exceptPrefix(homeDirectory.size() - pathSeparator.size()); } } # endif } #endif } void BeginSection::OnUpdate(float timeMult) { if (_animation < 1.0f) { _animation = std::min(_animation + timeMult * 0.016f, 1.0f); } if (!_shouldStart) { if (_root->ActionHit(PlayerAction::Fire)) { ExecuteSelected(); } else if (_root->ActionHit(PlayerAction::Menu)) { if (_selectedIndex != (std::int32_t)_items.size() - 1 && _items.back().Type == Item::Quit) { _root->PlaySfx("MenuSelect"_s, 0.6f); _animation = 0.0f; _selectedIndex = (std::int32_t)_items.size() - 1; } } else if (_root->ActionHit(PlayerAction::Up)) { _root->PlaySfx("MenuSelect"_s, 0.5f); _animation = 0.0f; if (_selectedIndex > 0) { _selectedIndex--; if (_skipSecondItem && _selectedIndex == 1) { _selectedIndex--; } } else { _selectedIndex = (std::int32_t)_items.size() - 1; } } else if (_root->ActionHit(PlayerAction::Down)) { _root->PlaySfx("MenuSelect"_s, 0.5f); _animation = 0.0f; if (_selectedIndex < (std::int32_t)_items.size() - 1) { _selectedIndex++; if (_skipSecondItem && _selectedIndex == 1) { _selectedIndex++; } } else { _selectedIndex = 0; } } } else { _transitionTime -= 0.025f * timeMult; if (_transitionTime <= 0.0f) { OnAfterTransition(); } } } void BeginSection::OnDraw(Canvas* canvas) { bool canGrantPermission = false; bool permissionGranted = false; #if defined(DEATH_TARGET_ANDROID) if (auto* mainMenu = runtime_cast(_root)) { IRootController::Flags flags = mainMenu->_root->GetFlags(); canGrantPermission = Backends::AndroidJniHelper::SdkVersion() >= 30 && (flags & IRootController::Flags::HasExternalStoragePermission) != IRootController::Flags::HasExternalStoragePermission; permissionGranted = (flags & (IRootController::Flags::HasExternalStoragePermission | IRootController::Flags::HasExternalStoragePermissionOnResume)) == IRootController::Flags::HasExternalStoragePermissionOnResume; } #endif _skipSecondItem = !_isPlayable && canGrantPermission && !permissionGranted; std::int32_t itemCount = (std::int32_t)_items.size(); float baseReduction = (canvas->ViewSize.Y >= 300 ? 10.0f : 24.0f); if (!_isPlayable) { itemCount += 2; baseReduction += (canvas->ViewSize.Y >= 252 ? 50.0f : 60.0f); } Recti contentBounds = _root->GetContentBounds(); Vector2f center = Vector2f(contentBounds.X + contentBounds.W * 0.5f, contentBounds.Y + baseReduction + (0.2f * (float)canvas->ViewSize.Y / itemCount)); std::int32_t charOffset = 0; #if defined(DEATH_TARGET_EMSCRIPTEN) if (_isEmbedded) { // Show additional label under the logo if the game is embedded in an