Compare commits
44 Commits
lava-shade
...
mention-bu
Author | SHA1 | Date | |
---|---|---|---|
![]() |
8f6201f65a | ||
![]() |
783ac3fc40 | ||
![]() |
29360b3fd5 | ||
![]() |
cdd80bca9e | ||
![]() |
3293d24c36 | ||
![]() |
bf31781d7a | ||
![]() |
f6c43b2db3 | ||
![]() |
f00156a654 | ||
![]() |
41fd1cbcee | ||
![]() |
52c807d74a | ||
![]() |
8713a496b4 | ||
![]() |
a3033f2db1 | ||
![]() |
5fb8faffc1 | ||
![]() |
a883955703 | ||
![]() |
b39d7adcbc | ||
![]() |
c577c8db71 | ||
![]() |
d7d17400dd | ||
![]() |
b219d50fd8 | ||
![]() |
88f318e6df | ||
![]() |
d12ac44b7d | ||
![]() |
7155eea716 | ||
![]() |
ca0796f131 | ||
![]() |
c4b33e2481 | ||
![]() |
ff95564b6b | ||
![]() |
cecf1ab791 | ||
![]() |
a571445e0e | ||
![]() |
89e418cd24 | ||
![]() |
896ccfcffa | ||
![]() |
d98d181755 | ||
![]() |
3c63f9b617 | ||
![]() |
8312bbe4f8 | ||
![]() |
bb77a71f26 | ||
![]() |
4353469554 | ||
![]() |
46ea431f62 | ||
![]() |
2d29d43355 | ||
![]() |
28a4e253a1 | ||
![]() |
f3dcb8eec8 | ||
![]() |
bf9e38ff31 | ||
![]() |
87649b9def | ||
![]() |
60d332e872 | ||
![]() |
bdab12b1e6 | ||
![]() |
02f795f2c1 | ||
![]() |
6e64d8521f | ||
![]() |
0e9aea1c9d |
@@ -13,8 +13,9 @@ orbs:
|
||||
aws-cli: circleci/aws-cli@2.0.6
|
||||
macos: circleci/macos@2.5.1 # For Rosetta (see below)
|
||||
node: circleci/node@5.2.0 # For a recent npm version (see below)
|
||||
win: circleci/windows@5.1.0
|
||||
jobs:
|
||||
# Build the **entire** app for macOS.
|
||||
# Build the **entire** app for macOS (including the GDevelop.js library).
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 14.2.0
|
||||
@@ -46,9 +47,9 @@ jobs:
|
||||
# GDevelop.js dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- gd-macos-nodejs-dependencies---
|
||||
- gd-macos-nodejs-dependencies-
|
||||
|
||||
- run:
|
||||
name: Install GDevelop.js dependencies
|
||||
@@ -69,7 +70,8 @@ jobs:
|
||||
- newIDE/electron-app/node_modules
|
||||
- newIDE/app/node_modules
|
||||
- GDevelop.js/node_modules
|
||||
key: gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- GDJS/node_modules
|
||||
key: gd-macos-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
|
||||
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
|
||||
# Note: Code signing is done using CSC_LINK (see https://www.electron.build/code-signing).
|
||||
@@ -94,7 +96,7 @@ jobs:
|
||||
name: Deploy to S3 (latest)
|
||||
command: export PATH=~/.local/bin:$PATH && aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/
|
||||
|
||||
# Build the **entire** app for Linux.
|
||||
# Build the app for Linux (using a pre-built GDevelop.js library).
|
||||
build-linux:
|
||||
# CircleCI docker workers are failing if they don't have enough memory (no swap)
|
||||
resource_class: xlarge
|
||||
@@ -107,51 +109,33 @@ jobs:
|
||||
- checkout
|
||||
- aws-cli/setup
|
||||
|
||||
# System dependencies (for Electron Builder and Emscripten)
|
||||
# System dependencies (for Electron Builder)
|
||||
- run:
|
||||
name: Install dependencies for Emscripten
|
||||
command: sudo apt-get update && sudo apt install cmake
|
||||
|
||||
- run:
|
||||
name: Install Python3 dependencies for Emscripten
|
||||
command: sudo apt install python-is-python3 python3-distutils -y
|
||||
|
||||
- run:
|
||||
name: Install Emscripten (for GDevelop.js)
|
||||
command: git clone https://github.com/juj/emsdk.git && cd emsdk && ./emsdk install 3.1.21 && ./emsdk activate 3.1.21 && cd ..
|
||||
name: Update system dependencies
|
||||
command: sudo apt-get update
|
||||
|
||||
- run:
|
||||
name: Install system dependencies for Electron builder
|
||||
command: sudo apt install icnsutils && sudo apt install graphicsmagick && sudo apt install rsync
|
||||
|
||||
# GDevelop.js dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- gd-linux-nodejs-dependencies---
|
||||
- gd-linux-nodejs-dependencies-
|
||||
|
||||
- run:
|
||||
name: Install GDevelop.js dependencies and build it
|
||||
command: cd GDevelop.js && npm install && cd ..
|
||||
|
||||
# Build GDevelop.js (and run tests to ensure it works)
|
||||
- run:
|
||||
name: Build GDevelop.js
|
||||
# Use "--runInBand" as it's faster and avoid deadlocks on CircleCI Linux machines (probably because limited in processes number).
|
||||
command: cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && npm test -- --runInBand && cd ..
|
||||
|
||||
# GDevelop IDE dependencies (after building GDevelop.js to avoid downloading a pre-built version)
|
||||
# GDevelop IDE dependencies (using an exact version of GDevelop.js, built previously)
|
||||
- run:
|
||||
name: Install GDevelop IDE dependencies
|
||||
command: cd newIDE/app && npm install && cd ../electron-app && npm install
|
||||
command: export REQUIRES_EXACT_LIBGD_JS_VERSION=true && cd newIDE/app && npm install && cd ../electron-app && npm install
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- newIDE/electron-app/node_modules
|
||||
- newIDE/app/node_modules
|
||||
- GDevelop.js/node_modules
|
||||
key: gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
|
||||
- GDJS/node_modules
|
||||
key: gd-linux-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
|
||||
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
|
||||
- run:
|
||||
@@ -295,14 +279,203 @@ jobs:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: aws s3 sync Binaries/embuild/GDevelop.js s3://gdevelop-gdevelop.js/$(git rev-parse --abbrev-ref HEAD)/variant/debug-sanitizers/commit/$(git rev-parse HEAD)/
|
||||
|
||||
# Trigger AppVeyor build, which also does a Windows build (keep it for redundancy).
|
||||
trigger-appveyor-windows-build:
|
||||
docker:
|
||||
- image: cimg/node:16.13
|
||||
steps:
|
||||
- run:
|
||||
name: Trigger AppVeyor Windows build
|
||||
command: |
|
||||
curl -H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${APPVEYOR_API_KEY}" \
|
||||
--data "{
|
||||
\"accountName\": \"4ian\",
|
||||
\"projectSlug\": \"gdevelop\",
|
||||
\"branch\": \"${CIRCLE_BRANCH}\"
|
||||
}" \
|
||||
-X POST https://ci.appveyor.com/api/builds
|
||||
|
||||
build-windows:
|
||||
executor:
|
||||
name: win/default
|
||||
size: medium
|
||||
working_directory: /home/circleci/project
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
# See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/
|
||||
#
|
||||
# This is necessary because of "signing to be FIPS-140 compliant". See
|
||||
# https://github.com/electron-userland/electron-builder/issues/6158
|
||||
#
|
||||
# Make sure to DISABLE "malware blocker" in SSL.com to avoid errors like:
|
||||
# Error information: "Error: SignerSign() failed." (-2146893821/0x80090003)
|
||||
name: Download and Unzip eSignerCKA Setup
|
||||
command: |
|
||||
|
||||
Invoke-WebRequest -OutFile eSigner_CKA_1.0.3.zip "https://www.ssl.com/download/ssl-com-esigner-cka-1-0-3"
|
||||
|
||||
Expand-Archive -Force eSigner_CKA_1.0.3.zip
|
||||
|
||||
Remove-Item eSigner_CKA_1.0.3.zip
|
||||
|
||||
Move-Item -Destination "eSigner_CKA_1.0.3.exe" -Path "eSigner_CKA_*\*.exe"
|
||||
- run:
|
||||
name: Setup eSignerCKA in Silent Mode
|
||||
command: |
|
||||
|
||||
mkdir -p "/home/circleci/project/eSignerCKA"
|
||||
|
||||
./eSigner_CKA_1.0.3.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR="/home/circleci/project/eSignerCKA" | Out-Null
|
||||
- run:
|
||||
name: Config Account Information on eSignerCKA
|
||||
command: |
|
||||
|
||||
/home/circleci/project/eSignerCKA/eSignerCKATool.exe config -mode product -user "$env:ESIGNER_USER_NAME" -pass "$env:ESIGNER_USER_PASSWORD" -totp "$env:ESIGNER_USER_TOTP" -key "/home/circleci/project/eSignerCKA/master.key" -r
|
||||
- run:
|
||||
name: Load Certificate into Windows Store
|
||||
command: |
|
||||
|
||||
/home/circleci/project/eSignerCKA/eSignerCKATool.exe unload
|
||||
|
||||
/home/circleci/project/eSignerCKA/eSignerCKATool.exe load
|
||||
- run:
|
||||
name: Select Certificate From Windows Store and Sign Sample File with SignTool
|
||||
command: |
|
||||
|
||||
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
|
||||
|
||||
echo Certificate: $CodeSigningCert
|
||||
|
||||
- restore_cache:
|
||||
name: Restore node_modules cache
|
||||
keys:
|
||||
- v1-win-node-{{ checksum "newIDE/app/package-lock.json" }}-{{ checksum "newIDE/electron-app/package-lock.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
- v1-win-node-
|
||||
|
||||
- run:
|
||||
name: Install dependencies
|
||||
no_output_timeout: 25m
|
||||
# Remove package-lock.json because they seems to cause the npm install to be stuck. We should try again after re-generating them.
|
||||
# Also install setuptools as something requires distutils in electron-app, and it was removed in Python 3.12.
|
||||
# setuptools will make distutils available again (but we should migrate our packages probably).
|
||||
command: |
|
||||
pip install setuptools
|
||||
|
||||
cd newIDE\app
|
||||
|
||||
npm -v
|
||||
|
||||
Remove-Item package-lock.json
|
||||
|
||||
$Env:REQUIRES_EXACT_LIBGD_JS_VERSION = "true"
|
||||
|
||||
npm install
|
||||
|
||||
cd ..\electron-app
|
||||
|
||||
Remove-Item package-lock.json
|
||||
|
||||
npm install
|
||||
|
||||
cd ..\..
|
||||
|
||||
- save_cache:
|
||||
name: Save node_modules cache
|
||||
key: v1-win-node-{{ checksum "newIDE/app/package-lock.json" }}-{{ checksum "newIDE/electron-app/package-lock.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
paths:
|
||||
- newIDE/app/node_modules
|
||||
- newIDE/electron-app/node_modules
|
||||
- GDJS/node_modules
|
||||
|
||||
- run:
|
||||
name: Build NSIS executable (with code signing)
|
||||
command: |
|
||||
cd newIDE\electron-app
|
||||
|
||||
$CodeSigningCert = Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert | Select-Object -First 1
|
||||
|
||||
echo Certificate: $CodeSigningCert
|
||||
|
||||
# Use a custom signtool path because of the signtool.exe bundled withy electron-builder not working for some reason.
|
||||
# Can also be found in versioned folders like "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe".
|
||||
# or "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x86\signtool.exe".
|
||||
|
||||
$Env:SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe"
|
||||
|
||||
# Extract thumbprint and subject name of the certificate (will be passed to electron-builder).
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = $CodeSigningCert.Thumbprint
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ($CodeSigningCert.Subject -replace ", ?", "`n" | ConvertFrom-StringData).CN
|
||||
|
||||
# Build the nsis installer (signed: electron-builder will use SignTool.exe with the certificate)
|
||||
|
||||
node scripts/build.js --win nsis --publish=never
|
||||
|
||||
cd ..\..
|
||||
- run:
|
||||
name: Build AppX (without code signing)
|
||||
# Don't sign the appx (it will be signed by the Microsoft Store).
|
||||
command: |
|
||||
cd newIDE\electron-app
|
||||
|
||||
# Build the appx (not signed). Ensure all variables used for code signing are empty.
|
||||
|
||||
$Env:GD_SIGNTOOL_THUMBPRINT = ''
|
||||
|
||||
$Env:GD_SIGNTOOL_SUBJECT_NAME = ''
|
||||
|
||||
$Env:CSC_LINK = ''
|
||||
|
||||
$Env:CSC_KEY_PASSWORD = ''
|
||||
|
||||
node scripts/build.js --skip-app-build --win appx --publish=never
|
||||
|
||||
cd ..\..
|
||||
|
||||
- run:
|
||||
name: Clean binaries
|
||||
shell: cmd.exe
|
||||
command: |
|
||||
rmdir /s /q newIDE\electron-app\dist\win-unpacked
|
||||
|
||||
- run:
|
||||
name: Install AWS CLI
|
||||
command: |
|
||||
# Install the CLI for the current user
|
||||
|
||||
pip install --quiet --upgrade --user awscli
|
||||
|
||||
# Add the user-Scripts dir to PATH for this step and the next.
|
||||
|
||||
$binDir = (python -m site --user-base) + "\Scripts"
|
||||
$Env:Path += ";$binDir"
|
||||
|
||||
# Sanity check:
|
||||
aws --version
|
||||
|
||||
# Upload artifacts (S3)
|
||||
- run:
|
||||
name: Deploy to S3 (specific commit)
|
||||
command: |
|
||||
aws s3 sync newIDE\electron-app\dist "s3://gdevelop-releases/$Env:CIRCLE_BRANCH/commit/$Env:CIRCLE_SHA1/"
|
||||
|
||||
- run:
|
||||
name: Deploy to S3 (latest)
|
||||
command: |
|
||||
aws s3 sync newIDE\electron-app\dist "s3://gdevelop-releases/$Env:CIRCLE_BRANCH/latest/"
|
||||
|
||||
# Upload artifacts (CircleCI)
|
||||
- store_artifacts:
|
||||
path: newIDE/electron-app/dist
|
||||
|
||||
workflows:
|
||||
gdevelop_js-wasm:
|
||||
jobs:
|
||||
- build-gdevelop_js-wasm-only
|
||||
gdevelop_js-wasm-extra-checks:
|
||||
jobs:
|
||||
- build-gdevelop_js-debug-sanitizers-and-extra-checks:
|
||||
# Extra checks are resource intensive so don't all run them.
|
||||
# Extra checks are resource intensive so don't always run them.
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
@@ -310,13 +483,36 @@ workflows:
|
||||
- /experimental-build.*/
|
||||
builds:
|
||||
jobs:
|
||||
- build-gdevelop_js-wasm-only
|
||||
- build-macos:
|
||||
# The macOS version builds by itself GDevelop.js
|
||||
# (so we verify we can build it on macOS).
|
||||
# requires:
|
||||
# - build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
- build-linux:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
- build-windows:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
- trigger-appveyor-windows-build:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
|
@@ -59,36 +59,44 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
|
||||
// end of compatibility code
|
||||
|
||||
extension
|
||||
.AddCondition("Or",
|
||||
_("Or"),
|
||||
_("Check if one of the sub conditions is true"),
|
||||
_("If one of these conditions is true:"),
|
||||
"",
|
||||
"res/conditions/or24_black.png",
|
||||
"res/conditions/or_black.png")
|
||||
.SetCanHaveSubInstructions()
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition("And",
|
||||
_("And"),
|
||||
_("Check if all sub conditions are true"),
|
||||
_("If all of these conditions are true:"),
|
||||
"",
|
||||
"res/conditions/and24_black.png",
|
||||
"res/conditions/and_black.png")
|
||||
.AddCondition(
|
||||
"Or",
|
||||
_("Or"),
|
||||
_("Checks if at least one sub-condition is true. If no "
|
||||
"sub-condition is specified, it will always be false. "
|
||||
"This is rarely used — multiple events and sub-events are "
|
||||
"usually a better approach."),
|
||||
_("If one of these conditions is true:"),
|
||||
"",
|
||||
"res/conditions/or24_black.png",
|
||||
"res/conditions/or_black.png")
|
||||
.SetCanHaveSubInstructions()
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition(
|
||||
"Not",
|
||||
_("Not"),
|
||||
_("Return the contrary of the result of the sub conditions"),
|
||||
_("Invert the logical result of these conditions:"),
|
||||
"And",
|
||||
_("And"),
|
||||
_("Checks if all sub-conditions are true. If no sub-condition is "
|
||||
"specified, it will always be false. This is rarely needed, as "
|
||||
"events already check all conditions before running actions."),
|
||||
_("If all of these conditions are true:"),
|
||||
"",
|
||||
"res/conditions/not24_black.png",
|
||||
"res/conditions/not_black.png")
|
||||
"res/conditions/and24_black.png",
|
||||
"res/conditions/and_black.png")
|
||||
.SetCanHaveSubInstructions()
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition("Not",
|
||||
_("Not"),
|
||||
_("Returns the opposite of the sub-condition(s) result. "
|
||||
"This is rarely needed, as most conditions can be "
|
||||
"inverted or expressed more simply."),
|
||||
_("Invert the logical result of these conditions:"),
|
||||
"",
|
||||
"res/conditions/not24_black.png",
|
||||
"res/conditions/not_black.png")
|
||||
.SetCanHaveSubInstructions()
|
||||
.MarkAsAdvanced();
|
||||
|
||||
|
@@ -18,6 +18,7 @@
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/CustomBehavior.h"
|
||||
#include "GDCore/Project/CustomObjectConfiguration.h"
|
||||
#include "GDCore/Project/EventsBasedObjectVariant.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
@@ -61,9 +62,6 @@ void ObjectAssetSerializer::SerializeTo(
|
||||
element.SetAttribute("version", "");
|
||||
element.SetIntAttribute("animationsCount", 1);
|
||||
element.SetIntAttribute("maxFramesCount", 1);
|
||||
// TODO Find the right object dimensions.
|
||||
element.SetIntAttribute("width", 0);
|
||||
element.SetIntAttribute("height", 0);
|
||||
SerializerElement &authorsElement = element.AddChild("authors");
|
||||
authorsElement.ConsiderAsArrayOf("author");
|
||||
SerializerElement &tagsElement = element.AddChild("tags");
|
||||
@@ -76,16 +74,28 @@ void ObjectAssetSerializer::SerializeTo(
|
||||
|
||||
cleanObject->SerializeTo(objectAssetElement.AddChild("object"));
|
||||
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
if (project.HasEventsBasedObject(object.GetType())) {
|
||||
SerializerElement &variantsElement =
|
||||
objectAssetElement.AddChild("variants");
|
||||
variantsElement.ConsiderAsArrayOf("variant");
|
||||
|
||||
const auto *variant = ObjectAssetSerializer::GetVariant(project, object);
|
||||
if (variant) {
|
||||
width = variant->GetAreaMaxX() - variant->GetAreaMinX();
|
||||
height = variant->GetAreaMaxY() - variant->GetAreaMinY();
|
||||
}
|
||||
|
||||
std::unordered_set<gd::String> alreadyUsedVariantIdentifiers;
|
||||
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
|
||||
project, object, variantsElement, alreadyUsedVariantIdentifiers);
|
||||
}
|
||||
|
||||
// TODO Find the right object dimensions when their is no variant.
|
||||
element.SetIntAttribute("width", width);
|
||||
element.SetIntAttribute("height", height);
|
||||
|
||||
SerializerElement &resourcesElement =
|
||||
objectAssetElement.AddChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
@@ -124,41 +134,54 @@ void ObjectAssetSerializer::SerializeUsedVariantsTo(
|
||||
gd::Project &project, const gd::Object &object,
|
||||
SerializerElement &variantsElement,
|
||||
std::unordered_set<gd::String> &alreadyUsedVariantIdentifiers) {
|
||||
|
||||
if (!project.HasEventsBasedObject(object.GetType())) {
|
||||
const auto *variant = ObjectAssetSerializer::GetVariant(project, object);
|
||||
if (!variant) {
|
||||
return;
|
||||
}
|
||||
const auto &variantIdentifier =
|
||||
object.GetType() + gd::PlatformExtension::GetNamespaceSeparator() +
|
||||
variant->GetName();
|
||||
auto insertResult = alreadyUsedVariantIdentifiers.insert(variantIdentifier);
|
||||
if (!insertResult.second) {
|
||||
return;
|
||||
}
|
||||
SerializerElement &pairElement = variantsElement.AddChild("variant");
|
||||
pairElement.SetAttribute("objectType", object.GetType());
|
||||
SerializerElement &variantElement = pairElement.AddChild("variant");
|
||||
variant->SerializeTo(variantElement);
|
||||
|
||||
for (auto &object : variant->GetObjects().GetObjects()) {
|
||||
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
|
||||
project, *object, variantsElement, alreadyUsedVariantIdentifiers);
|
||||
}
|
||||
}
|
||||
|
||||
const gd::EventsBasedObjectVariant *
|
||||
ObjectAssetSerializer::GetVariant(gd::Project &project,
|
||||
const gd::Object &object) {
|
||||
if (!project.HasEventsBasedObject(object.GetType())) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &eventsBasedObject =
|
||||
project.GetEventsBasedObject(object.GetType());
|
||||
const auto &variants = eventsBasedObject.GetVariants();
|
||||
const auto *customObjectConfiguration =
|
||||
dynamic_cast<const gd::CustomObjectConfiguration *>(
|
||||
&object.GetConfiguration());
|
||||
if (customObjectConfiguration
|
||||
->IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() ||
|
||||
customObjectConfiguration
|
||||
->IsForcedToOverrideEventsBasedObjectChildrenConfiguration()) {
|
||||
return;
|
||||
}
|
||||
const auto &variantName = customObjectConfiguration->GetVariantName();
|
||||
if (!variants.HasVariantNamed(variantName) &&
|
||||
(customObjectConfiguration
|
||||
->IsMarkedAsOverridingEventsBasedObjectChildrenConfiguration() ||
|
||||
customObjectConfiguration
|
||||
->IsForcedToOverrideEventsBasedObjectChildrenConfiguration())) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto &variantIdentifier =
|
||||
object.GetType() + gd::PlatformExtension::GetNamespaceSeparator() +
|
||||
variantName;
|
||||
auto insertResult = alreadyUsedVariantIdentifiers.insert(variantIdentifier);
|
||||
if (insertResult.second) {
|
||||
const auto &eventsBasedObject =
|
||||
project.GetEventsBasedObject(object.GetType());
|
||||
const auto &variants = eventsBasedObject.GetVariants();
|
||||
const auto &variant = variants.HasVariantNamed(variantName)
|
||||
? variants.GetVariant(variantName)
|
||||
: eventsBasedObject.GetDefaultVariant();
|
||||
|
||||
SerializerElement &pairElement = variantsElement.AddChild("variant");
|
||||
pairElement.SetAttribute("objectType", object.GetType());
|
||||
SerializerElement &variantElement = pairElement.AddChild("variant");
|
||||
variant.SerializeTo(variantElement);
|
||||
// TODO Recursivity
|
||||
for (auto &object : variant.GetObjects().GetObjects()) {
|
||||
gd::ObjectAssetSerializer::SerializeUsedVariantsTo(
|
||||
project, *object, variantsElement, alreadyUsedVariantIdentifiers);
|
||||
}
|
||||
}
|
||||
const auto &variant = variants.HasVariantNamed(variantName)
|
||||
? variants.GetVariant(variantName)
|
||||
: eventsBasedObject.GetDefaultVariant();
|
||||
return &variant;
|
||||
}
|
||||
} // namespace gd
|
||||
|
@@ -21,6 +21,7 @@ class InitialInstance;
|
||||
class SerializerElement;
|
||||
class EffectsContainer;
|
||||
class AbstractFileSystem;
|
||||
class EventsBasedObjectVariant;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
@@ -58,6 +59,8 @@ private:
|
||||
gd::Project &project, const gd::Object &object,
|
||||
SerializerElement &variantsElement,
|
||||
std::unordered_set<gd::String> &alreadyUsedVariantIdentifiers);
|
||||
|
||||
static const gd::EventsBasedObjectVariant* GetVariant(gd::Project &project, const gd::Object &object);
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -33,7 +33,132 @@ using namespace gd;
|
||||
|
||||
TEST_CASE("ObjectAssetSerializer", "[common]") {
|
||||
|
||||
SECTION("Can serialize custom objects as assets") {
|
||||
SECTION("Can serialize custom objects as assets with variant") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &eventsBasedObject = eventsExtension.GetEventsBasedObjects().InsertNew(
|
||||
"MyEventsBasedObject", 0);
|
||||
eventsBasedObject.SetFullName("My events based object");
|
||||
eventsBasedObject.SetDescription("An events based object for test");
|
||||
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyExtension::Sprite", "MyChild", 0);
|
||||
auto &childInstance =
|
||||
eventsBasedObject.GetInitialInstances().InsertNewInitialInstance();
|
||||
childInstance.SetObjectName("MyChild");
|
||||
|
||||
auto &resourceManager = project.GetResourcesManager();
|
||||
gd::ImageResource imageResource;
|
||||
imageResource.SetName("assets/Idle.png");
|
||||
imageResource.SetFile("assets/Idle.png");
|
||||
imageResource.SetSmooth(true);
|
||||
resourceManager.AddResource(imageResource);
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &object = layout.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyEventsBasedObject", "MyObject", 0);
|
||||
auto *spriteConfiguration =
|
||||
dynamic_cast<gd::SpriteObject *>(&childObject.GetConfiguration());
|
||||
REQUIRE(spriteConfiguration != nullptr);
|
||||
{
|
||||
gd::Animation animation;
|
||||
animation.SetName("Idle");
|
||||
animation.SetDirectionsCount(1);
|
||||
auto &direction = animation.GetDirection(0);
|
||||
gd::Sprite frame;
|
||||
frame.SetImageName("assets/Idle.png");
|
||||
direction.AddSprite(frame);
|
||||
|
||||
spriteConfiguration->GetAnimations().AddAnimation(animation);
|
||||
}
|
||||
|
||||
SerializerElement assetElement;
|
||||
std::vector<gd::String> usedResourceNames;
|
||||
ObjectAssetSerializer::SerializeTo(project, object, "My Object",
|
||||
assetElement, usedResourceNames);
|
||||
|
||||
// This list is used to copy resource files.
|
||||
REQUIRE(usedResourceNames.size() == 1);
|
||||
REQUIRE(usedResourceNames[0] == "assets/Idle.png");
|
||||
|
||||
// Check that the project is left untouched.
|
||||
REQUIRE(resourceManager.HasResource("assets/Idle.png"));
|
||||
REQUIRE(resourceManager.GetResource("assets/Idle.png").GetFile() ==
|
||||
"assets/Idle.png");
|
||||
REQUIRE(!resourceManager.HasResource("Idle.png"));
|
||||
|
||||
REQUIRE(assetElement.HasChild("objectAssets"));
|
||||
auto &objectAssetsElement = assetElement.GetChild("objectAssets");
|
||||
objectAssetsElement.ConsiderAsArrayOf("objectAsset");
|
||||
REQUIRE(objectAssetsElement.GetChildrenCount() == 1);
|
||||
auto &objectAssetElement = objectAssetsElement.GetChild(0);
|
||||
|
||||
REQUIRE(objectAssetElement.HasChild("variants"));
|
||||
auto &variantsElement = objectAssetElement.GetChild("variants");
|
||||
variantsElement.ConsiderAsArrayOf("variant");
|
||||
REQUIRE(variantsElement.GetChildrenCount() == 1);
|
||||
auto &variantPairElement = variantsElement.GetChild(0);
|
||||
REQUIRE(variantPairElement.GetStringAttribute("objectType") ==
|
||||
"MyEventsExtension::MyEventsBasedObject");
|
||||
REQUIRE(variantPairElement.HasChild("variant"));
|
||||
auto &variantElement = variantPairElement.GetChild("variant");
|
||||
REQUIRE(variantElement.GetStringAttribute("name") == "");
|
||||
REQUIRE(variantElement.HasChild("objects"));
|
||||
auto &objectsElement = variantElement.GetChild("objects");
|
||||
objectsElement.ConsiderAsArrayOf("object");
|
||||
REQUIRE(objectsElement.GetChildrenCount() == 1);
|
||||
auto &childElement = objectsElement.GetChild(0);
|
||||
|
||||
REQUIRE(childElement.HasChild("animations"));
|
||||
auto &animationsElement = childElement.GetChild("animations");
|
||||
animationsElement.ConsiderAsArrayOf("animation");
|
||||
REQUIRE(animationsElement.GetChildrenCount() == 1);
|
||||
auto &animationElement = animationsElement.GetChild(0);
|
||||
|
||||
REQUIRE(animationElement.GetStringAttribute("name") == "Idle");
|
||||
auto &directionsElement = animationElement.GetChild("directions");
|
||||
directionsElement.ConsiderAsArrayOf("direction");
|
||||
REQUIRE(directionsElement.GetChildrenCount() == 1);
|
||||
auto &directionElement = directionsElement.GetChild(0);
|
||||
auto &spritesElement = directionElement.GetChild("sprites");
|
||||
spritesElement.ConsiderAsArrayOf("sprite");
|
||||
REQUIRE(spritesElement.GetChildrenCount() == 1);
|
||||
auto &spriteElement = spritesElement.GetChild(0);
|
||||
REQUIRE(spriteElement.GetStringAttribute("image") == "assets/Idle.png");
|
||||
|
||||
REQUIRE(objectAssetElement.HasChild("requiredExtensions"));
|
||||
auto &requiredExtensionsElement =
|
||||
objectAssetElement.GetChild("requiredExtensions");
|
||||
requiredExtensionsElement.ConsiderAsArrayOf("requiredExtension");
|
||||
REQUIRE(requiredExtensionsElement.GetChildrenCount() == 1);
|
||||
auto &requiredExtensionElement = requiredExtensionsElement.GetChild(0);
|
||||
REQUIRE(requiredExtensionElement.GetStringAttribute("extensionName") ==
|
||||
"MyEventsExtension");
|
||||
|
||||
// Resources are renamed according to asset script naming conventions.
|
||||
REQUIRE(objectAssetElement.HasChild("resources"));
|
||||
auto &resourcesElement = objectAssetElement.GetChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
REQUIRE(resourcesElement.GetChildrenCount() == 1);
|
||||
{
|
||||
auto &resourceElement = resourcesElement.GetChild(0);
|
||||
REQUIRE(resourceElement.GetStringAttribute("name") == "assets/Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("file") == "assets/Idle.png");
|
||||
REQUIRE(resourceElement.GetStringAttribute("kind") == "image");
|
||||
REQUIRE(resourceElement.GetBoolAttribute("smoothed") == true);
|
||||
}
|
||||
|
||||
// Resources used in object configuration are updated.
|
||||
REQUIRE(objectAssetElement.HasChild("object"));
|
||||
auto &objectElement = objectAssetElement.GetChild("object");
|
||||
REQUIRE(objectElement.GetStringAttribute("name") == "MyObject");
|
||||
REQUIRE(objectElement.GetStringAttribute("type") ==
|
||||
"MyEventsExtension::MyEventsBasedObject");
|
||||
}
|
||||
|
||||
SECTION("Can serialize custom objects as assets with children overriding") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
@@ -75,11 +75,12 @@ namespace gdjs {
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer
|
||||
) {
|
||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
getFaceMaterial(runtimeObject, materialIndexToFaceIndex[0]);
|
||||
|
||||
const materials: THREE.Material[] = new Array(6)
|
||||
.fill(0)
|
||||
.map((_, index) => lavaMaterial);
|
||||
.map((_, index) =>
|
||||
getFaceMaterial(runtimeObject, materialIndexToFaceIndex[index])
|
||||
);
|
||||
|
||||
const boxMesh = new THREE.Mesh(geometry, materials);
|
||||
|
||||
@@ -122,7 +123,6 @@ namespace gdjs {
|
||||
this._cube3DRuntimeObject,
|
||||
faceIndex
|
||||
);
|
||||
|
||||
if (this._cube3DRuntimeObject.isFaceAtIndexVisible(faceIndex)) {
|
||||
this.updateTextureUvMapping(faceIndex);
|
||||
}
|
||||
|
@@ -21,7 +21,9 @@ module.exports = {
|
||||
.setExtensionInformation(
|
||||
'Scene3D',
|
||||
_('3D'),
|
||||
_('Support for 3D in GDevelop.'),
|
||||
_(
|
||||
'Support for 3D in GDevelop: this provides 3D objects and the common features for all 3D objects.'
|
||||
),
|
||||
'Florian Rival',
|
||||
'MIT'
|
||||
)
|
||||
@@ -36,7 +38,9 @@ module.exports = {
|
||||
'Base3DBehavior',
|
||||
_('3D capability'),
|
||||
'Object3D',
|
||||
_('Move the object in 3D space.'),
|
||||
_(
|
||||
'Common features for all 3D objects: position in 3D space (including the Z axis, in addition to X and Y), size (including depth, in addition to width and height), rotation (on X and Y axis, in addition to the Z axis), scale (including Z axis, in addition to X and Y), flipping (on Z axis, in addition to horizontal (Y)/vertical (X) flipping).'
|
||||
),
|
||||
'',
|
||||
'res/conditions/3d_box.svg',
|
||||
'Base3DBehavior',
|
||||
|
@@ -618,7 +618,7 @@ module.exports = {
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
if (this._instance.hasCustomSize()) {
|
||||
if (this._instance.hasCustomSize() && this._pixiObject.width !== 0) {
|
||||
const alignmentX =
|
||||
object.content.align === 'right'
|
||||
? 1
|
||||
|
@@ -103,7 +103,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
if (this._object.isWrapping()) {
|
||||
if (this._object.isWrapping() && this._pixiObject.width !== 0) {
|
||||
const alignmentX =
|
||||
this._object._textAlign === 'right'
|
||||
? 1
|
||||
|
@@ -376,7 +376,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override getWidth(): float {
|
||||
return this._renderer.getWidth();
|
||||
return this._wrapping ? this._wrappingWidth : this._renderer.getWidth();
|
||||
}
|
||||
|
||||
override getHeight(): float {
|
||||
|
@@ -721,7 +721,7 @@ module.exports = {
|
||||
this._pixiObject.dirty = true;
|
||||
}
|
||||
|
||||
if (this._instance.hasCustomSize()) {
|
||||
if (this._instance.hasCustomSize() && this.getDefaultWidth() !== 0) {
|
||||
const alignmentX =
|
||||
object.content.align === 'right'
|
||||
? 1
|
||||
@@ -730,17 +730,16 @@ module.exports = {
|
||||
: 0;
|
||||
|
||||
const width = this.getCustomWidth();
|
||||
const renderedWidth = this.getDefaultWidth();
|
||||
|
||||
// A vector from the custom size center to the renderer center.
|
||||
const centerToCenterX =
|
||||
(width - this._pixiObject.width) * (alignmentX - 0.5);
|
||||
const centerToCenterX = (width - renderedWidth) * (alignmentX - 0.5);
|
||||
|
||||
this._pixiObject.position.x = this._instance.getX() + width / 2;
|
||||
this._pixiObject.anchor.x =
|
||||
0.5 - centerToCenterX / this._pixiObject.width;
|
||||
this._pixiObject.anchor.x = 0.5 - centerToCenterX / renderedWidth;
|
||||
} else {
|
||||
this._pixiObject.position.x =
|
||||
this._instance.getX() + this._pixiObject.width / 2;
|
||||
this._instance.getX() + this.getDefaultWidth() / 2;
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
}
|
||||
const alignmentY =
|
||||
@@ -750,7 +749,7 @@ module.exports = {
|
||||
? 0.5
|
||||
: 0;
|
||||
this._pixiObject.position.y =
|
||||
this._instance.getY() + this._pixiObject.height * (0.5 - alignmentY);
|
||||
this._instance.getY() + this.getDefaultHeight() * (0.5 - alignmentY);
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
|
||||
this._pixiObject.rotation = RenderedInstance.toRad(
|
||||
@@ -774,11 +773,11 @@ module.exports = {
|
||||
}
|
||||
|
||||
getDefaultWidth() {
|
||||
return this._pixiObject.width;
|
||||
return this._pixiObject.textWidth * this._pixiObject.scale.x;
|
||||
}
|
||||
|
||||
getDefaultHeight() {
|
||||
return this._pixiObject.height;
|
||||
return this._pixiObject.textHeight * this._pixiObject.scale.y;
|
||||
}
|
||||
|
||||
getOriginY() {
|
||||
|
@@ -146,7 +146,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
if (this._object.isWrapping()) {
|
||||
if (this._object.isWrapping() && this.getWidth() !== 0) {
|
||||
const alignmentX =
|
||||
this._object._textAlign === 'right'
|
||||
? 1
|
||||
@@ -155,17 +155,15 @@ namespace gdjs {
|
||||
: 0;
|
||||
|
||||
const width = this._object.getWrappingWidth();
|
||||
const renderedWidth = this.getWidth();
|
||||
|
||||
// A vector from the custom size center to the renderer center.
|
||||
const centerToCenterX =
|
||||
(width - this._pixiObject.width) * (alignmentX - 0.5);
|
||||
const centerToCenterX = (width - renderedWidth) * (alignmentX - 0.5);
|
||||
|
||||
this._pixiObject.position.x = this._object.x + width / 2;
|
||||
this._pixiObject.anchor.x =
|
||||
0.5 - centerToCenterX / this._pixiObject.width;
|
||||
this._pixiObject.anchor.x = 0.5 - centerToCenterX / renderedWidth;
|
||||
} else {
|
||||
this._pixiObject.position.x =
|
||||
this._object.x + this._pixiObject.width / 2;
|
||||
this._pixiObject.position.x = this._object.x + this.getWidth() / 2;
|
||||
this._pixiObject.anchor.x = 0.5;
|
||||
}
|
||||
|
||||
@@ -176,7 +174,7 @@ namespace gdjs {
|
||||
? 0.5
|
||||
: 0;
|
||||
this._pixiObject.position.y =
|
||||
this._object.y + this._pixiObject.height * (0.5 - alignmentY);
|
||||
this._object.y + this.getHeight() * (0.5 - alignmentY);
|
||||
this._pixiObject.anchor.y = 0.5;
|
||||
}
|
||||
|
||||
|
@@ -419,7 +419,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
override getWidth(): float {
|
||||
return this._renderer.getWidth();
|
||||
return this._wrapping ? this._wrappingWidth : this._renderer.getWidth();
|
||||
}
|
||||
|
||||
override getHeight(): float {
|
||||
|
@@ -12,35 +12,42 @@ This project is released under the MIT License.
|
||||
|
||||
void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
extension
|
||||
.SetExtensionInformation("DestroyOutsideBehavior",
|
||||
_("Destroy Outside Screen Behavior"),
|
||||
_("This behavior can be used to destroy "
|
||||
"objects when they go outside of "
|
||||
"the bounds of the camera. Useful for bullets "
|
||||
"or other short-lived objects."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetExtensionInformation(
|
||||
"DestroyOutsideBehavior",
|
||||
_("Destroy Outside Screen Behavior"),
|
||||
_("This behavior can be used to destroy objects when they go "
|
||||
"outside of the bounds of the 2D camera. Useful for 2D bullets or "
|
||||
"other short-lived objects. Don't use it for 3D objects in a "
|
||||
"FPS/TPS game or any game with a camera not being a top view "
|
||||
"(for 3D objects, prefer comparing "
|
||||
"the position, for example Z position to see if an object goes "
|
||||
"outside of the bound of the map). Be careful when using this "
|
||||
"behavior because if the object appears outside of the screen, it "
|
||||
"will be immediately removed."),
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Game mechanic")
|
||||
.SetTags("screen")
|
||||
.SetExtensionHelpPath("/behaviors/destroyoutside");
|
||||
|
||||
gd::BehaviorMetadata& aut =
|
||||
extension.AddBehavior("DestroyOutside",
|
||||
_("Destroy when outside of the screen"),
|
||||
_("DestroyOutside"),
|
||||
_("Destroy objects automatically when they go "
|
||||
"outside of the screen's borders."),
|
||||
"",
|
||||
"CppPlatform/Extensions/destroyoutsideicon.png",
|
||||
"DestroyOutsideBehavior",
|
||||
std::make_shared<DestroyOutsideBehavior>(),
|
||||
std::shared_ptr<gd::BehaviorsSharedData>())
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
extension
|
||||
.AddBehavior("DestroyOutside",
|
||||
_("Destroy when outside of the screen"),
|
||||
_("DestroyOutside"),
|
||||
_("Destroy objects automatically when they go "
|
||||
"outside of the 2D camera borders."),
|
||||
"",
|
||||
"CppPlatform/Extensions/destroyoutsideicon.png",
|
||||
"DestroyOutsideBehavior",
|
||||
std::make_shared<DestroyOutsideBehavior>(),
|
||||
std::shared_ptr<gd::BehaviorsSharedData>())
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
aut.AddCondition("ExtraBorder",
|
||||
_("Additional border"),
|
||||
_("Compare the additional border that the object must cross "
|
||||
"before being deleted."),
|
||||
_("Additional border (extra distance before deletion)"),
|
||||
_("Compare the extra distance (in pixels) the object must "
|
||||
"travel beyond the screen before it gets deleted."),
|
||||
_("the additional border"),
|
||||
_("Destroy outside configuration"),
|
||||
"CppPlatform/Extensions/destroyoutsideicon24.png",
|
||||
@@ -53,9 +60,9 @@ void DeclareDestroyOutsideBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("GetExtraBorder");
|
||||
|
||||
aut.AddAction("ExtraBorder",
|
||||
_("Additional border"),
|
||||
_("Change the additional border that the object must cross "
|
||||
"before being deleted."),
|
||||
_("Additional border (extra distance before deletion)"),
|
||||
_("Change the extra distance (in pixels) the object must "
|
||||
"travel beyond the screen before it gets deleted."),
|
||||
_("the additional border"),
|
||||
_("Destroy outside configuration"),
|
||||
"CppPlatform/Extensions/destroyoutsideicon24.png",
|
||||
|
@@ -46,7 +46,7 @@ namespace gdjs {
|
||||
layer.getCameraY() + layer.getCameraHeight() / 2
|
||||
) {
|
||||
//We are outside the camera area.
|
||||
this.owner.deleteFromScene(instanceContainer);
|
||||
this.owner.deleteFromScene();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -50,6 +50,11 @@ describeIfOnline('Firebase extension end-to-end tests', function () {
|
||||
.replace('.', '-')}-${Date.now()}`;
|
||||
|
||||
before(async function setupFirebase() {
|
||||
// Delete any existing Firebase app before setup
|
||||
if (firebase.apps.length !== 0) {
|
||||
await firebase.app().delete();
|
||||
}
|
||||
|
||||
await gdjs.evtTools.firebaseTools._setupFirebase({
|
||||
getGame: () => ({
|
||||
getExtensionProperty: () => JSON.stringify(firebaseConfig),
|
||||
|
@@ -1340,7 +1340,7 @@ namespace gdjs {
|
||||
debugLogger.info(
|
||||
`Destroying object ${objectName} with instance network ID ${instanceNetworkId}.`
|
||||
);
|
||||
instance.deleteFromScene(runtimeScene);
|
||||
instance.deleteFromScene();
|
||||
|
||||
debugLogger.info(
|
||||
`Sending acknowledgment of destruction of object ${objectName} with instance network ID ${instanceNetworkId} to ${messageSender}.`
|
||||
@@ -2280,7 +2280,7 @@ namespace gdjs {
|
||||
behavior.getActionOnPlayerDisconnect();
|
||||
if (actionOnPlayerDisconnect === 'DestroyObject') {
|
||||
// No need to remove the ownership, as the destroy message will be sent to all players.
|
||||
instance.deleteFromScene(runtimeScene);
|
||||
instance.deleteFromScene();
|
||||
} else if (actionOnPlayerDisconnect === 'GiveOwnershipToHost') {
|
||||
// Removing the ownership will send a message to all players.
|
||||
behavior.removeObjectOwnership();
|
||||
|
@@ -99,7 +99,7 @@ namespace gdjs {
|
||||
debugLogger.info(
|
||||
`Lobby game is running on a synced scene and object ${owner.getName()} has not been assigned a networkId after a short delay, destroying it.`
|
||||
);
|
||||
owner.deleteFromScene(instanceContainer);
|
||||
owner.deleteFromScene();
|
||||
}
|
||||
}, this._timeBeforeDestroyingObjectWithoutNetworkIdInMs);
|
||||
}
|
||||
@@ -262,7 +262,7 @@ namespace gdjs {
|
||||
debugLogger.info(
|
||||
`Player number ${this.playerNumber} does not exist in the lobby at the moment. Destroying the object.`
|
||||
);
|
||||
this.owner.deleteFromScene(this.owner.getInstanceContainer());
|
||||
this.owner.deleteFromScene();
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -1130,7 +1130,7 @@ describe('Multiplayer', () => {
|
||||
'MySpriteObject'
|
||||
)[0];
|
||||
|
||||
p1SpriteObject1.deleteFromScene(p1RuntimeScene);
|
||||
p1SpriteObject1.deleteFromScene();
|
||||
p1RuntimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
@@ -1297,7 +1297,7 @@ describe('Multiplayer', () => {
|
||||
'MySpriteObject'
|
||||
)[0];
|
||||
|
||||
p2SpriteObject1.deleteFromScene(p2RuntimeScene);
|
||||
p2SpriteObject1.deleteFromScene();
|
||||
p2RuntimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
|
@@ -20,9 +20,10 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
.SetExtensionInformation(
|
||||
"ParticleSystem",
|
||||
_("Particle system"),
|
||||
"A particle emitter allows to create various effects by showing a "
|
||||
"A 2D particle emitter allows to create various effects by showing a "
|
||||
"lot of tiny images called particles. It's ideal for fires, smoke, "
|
||||
"explosions, magical effects, etc...",
|
||||
"explosions, magical effects, etc... in 2D games. For 3D games, use "
|
||||
"the 3D particle emitter instead.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Visual effect")
|
||||
@@ -36,9 +37,9 @@ void DeclareParticleSystemExtension(gd::PlatformExtension& extension) {
|
||||
extension
|
||||
.AddObject<ParticleEmitterObject>(
|
||||
"ParticleEmitter",
|
||||
_("Particles emitter"),
|
||||
_("Displays a large number of small particles to create visual "
|
||||
"effects."),
|
||||
_("2D particles emitter"),
|
||||
_("Displays a large number of small 2D particles to create "
|
||||
"visual effects in a 2D game or user interface."),
|
||||
"CppPlatform/Extensions/particleSystemicon.png")
|
||||
.SetCategoryFullName(_("Visual effect"))
|
||||
.AddDefaultBehavior("EffectCapability::EffectBehavior");
|
||||
|
@@ -560,7 +560,7 @@ namespace gdjs {
|
||||
!this._isEmissionPaused &&
|
||||
this._renderer._mayHaveEndedEmission()
|
||||
) {
|
||||
this.deleteFromScene(instanceContainer);
|
||||
this.deleteFromScene();
|
||||
}
|
||||
if (
|
||||
this.jumpForwardInTimeOnCreation > 0 &&
|
||||
|
@@ -24,7 +24,7 @@ describe('Physics2RuntimeBehavior', () => {
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
if (this.shouldDeleteInPreEvent) {
|
||||
this.owner.deleteFromScene(runtimeScene);
|
||||
this.owner.deleteFromScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,7 +159,7 @@ describe('Physics2RuntimeBehavior', () => {
|
||||
);
|
||||
|
||||
// Delete object from scene
|
||||
object.deleteFromScene(runtimeScene);
|
||||
object.deleteFromScene();
|
||||
expect(behavior.destroyedDuringFrameLogic).to.be(true);
|
||||
expect(behavior.getBody()).to.be(null);
|
||||
expect(behavior._sharedData._registeredBehaviors.size).to.be(0);
|
||||
@@ -194,7 +194,7 @@ describe('Physics2RuntimeBehavior', () => {
|
||||
false
|
||||
);
|
||||
|
||||
object.deleteFromScene(runtimeScene);
|
||||
object.deleteFromScene();
|
||||
|
||||
expect(behavior.destroyedDuringFrameLogic).to.be(true);
|
||||
expect(behavior.getBody()).to.be(null);
|
||||
@@ -712,7 +712,7 @@ describe('Physics2RuntimeBehavior', () => {
|
||||
|
||||
// Destroy (handled by postEvent).
|
||||
runtimeScene.renderAndStepWithEventsFunction(1000 / fps, () => {
|
||||
movingObject.deleteFromScene(runtimeScene);
|
||||
movingObject.deleteFromScene();
|
||||
});
|
||||
|
||||
// Collision should be reset on destroyed object and
|
||||
|
@@ -305,7 +305,14 @@ namespace gdjs {
|
||||
private shapeDimensionA: float;
|
||||
private shapeDimensionB: float;
|
||||
private shapeDimensionC: float;
|
||||
private shapeOffsetX: float;
|
||||
private shapeOffsetY: float;
|
||||
shapeOffsetZ: float;
|
||||
private massCenterOffsetX: float;
|
||||
private massCenterOffsetY: float;
|
||||
private massCenterOffsetZ: float;
|
||||
private density: float;
|
||||
massOverride: float;
|
||||
friction: float;
|
||||
restitution: float;
|
||||
linearDamping: float;
|
||||
@@ -313,7 +320,7 @@ namespace gdjs {
|
||||
gravityScale: float;
|
||||
private layers: integer;
|
||||
private masks: integer;
|
||||
private shapeScale: number = 1;
|
||||
shapeScale: number = 1;
|
||||
|
||||
/**
|
||||
* Array containing the beginning of contacts reported by onContactBegin. Each contact
|
||||
@@ -348,7 +355,10 @@ namespace gdjs {
|
||||
/**
|
||||
* When set to `true` the shape will be recreated before the next physics step.
|
||||
*/
|
||||
private _needToRecreateShape: boolean = false;
|
||||
_needToRecreateShape: boolean = false;
|
||||
|
||||
_shapeHalfWidth: float = 0;
|
||||
_shapeHalfHeight: float = 0;
|
||||
/**
|
||||
* Used by {@link gdjs.PhysicsCharacter3DRuntimeBehavior} to convert coordinates.
|
||||
*/
|
||||
@@ -392,7 +402,14 @@ namespace gdjs {
|
||||
this.shapeDimensionA = behaviorData.shapeDimensionA;
|
||||
this.shapeDimensionB = behaviorData.shapeDimensionB;
|
||||
this.shapeDimensionC = behaviorData.shapeDimensionC;
|
||||
this.shapeOffsetX = behaviorData.shapeOffsetX || 0;
|
||||
this.shapeOffsetY = behaviorData.shapeOffsetY || 0;
|
||||
this.shapeOffsetZ = behaviorData.shapeOffsetZ || 0;
|
||||
this.massCenterOffsetX = behaviorData.massCenterOffsetX || 0;
|
||||
this.massCenterOffsetY = behaviorData.massCenterOffsetY || 0;
|
||||
this.massCenterOffsetZ = behaviorData.massCenterOffsetZ || 0;
|
||||
this.density = behaviorData.density;
|
||||
this.massOverride = behaviorData.massOverride || 0;
|
||||
this.friction = behaviorData.friction;
|
||||
this.restitution = behaviorData.restitution;
|
||||
this.linearDamping = Math.max(0, behaviorData.linearDamping);
|
||||
@@ -634,6 +651,39 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
createShape(): Jolt.Shape {
|
||||
if (
|
||||
this.massCenterOffsetX === 0 &&
|
||||
this.massCenterOffsetY === 0 &&
|
||||
this.massCenterOffsetZ === 0
|
||||
) {
|
||||
return this.createShapeWithoutMassCenterOffset();
|
||||
}
|
||||
const rotatedShapeSettings =
|
||||
this._createNewShapeSettingsWithoutMassCenterOffset();
|
||||
const shapeScale = this.shapeScale * this._sharedData.worldInvScale;
|
||||
const offsetCenterShapeSettings =
|
||||
new Jolt.OffsetCenterOfMassShapeSettings(
|
||||
this.getVec3(
|
||||
this.massCenterOffsetX * shapeScale,
|
||||
this.massCenterOffsetY * shapeScale,
|
||||
this.massCenterOffsetZ * shapeScale
|
||||
),
|
||||
rotatedShapeSettings
|
||||
);
|
||||
const shape = offsetCenterShapeSettings.Create().Get();
|
||||
Jolt.destroy(offsetCenterShapeSettings);
|
||||
return shape;
|
||||
}
|
||||
|
||||
createShapeWithoutMassCenterOffset(): Jolt.Shape {
|
||||
const rotatedShapeSettings =
|
||||
this._createNewShapeSettingsWithoutMassCenterOffset();
|
||||
const shape = rotatedShapeSettings.Create().Get();
|
||||
Jolt.destroy(rotatedShapeSettings);
|
||||
return shape;
|
||||
}
|
||||
|
||||
private _createNewShapeSettingsWithoutMassCenterOffset(): Jolt.RotatedTranslatedShapeSettings {
|
||||
let width = this.owner3D.getWidth() * this._sharedData.worldInvScale;
|
||||
let height = this.owner3D.getHeight() * this._sharedData.worldInvScale;
|
||||
let depth = this.owner3D.getDepth() * this._sharedData.worldInvScale;
|
||||
@@ -679,6 +729,8 @@ namespace gdjs {
|
||||
convexRadius
|
||||
);
|
||||
quat = this.getQuat(0, 0, 0, 1);
|
||||
this._shapeHalfWidth = boxWidth / 2;
|
||||
this._shapeHalfHeight = boxHeight / 2;
|
||||
this._shapeHalfDepth = boxDepth / 2;
|
||||
} else if (this.shape === 'Capsule') {
|
||||
const radius =
|
||||
@@ -694,8 +746,12 @@ namespace gdjs {
|
||||
radius
|
||||
);
|
||||
quat = this._getShapeOrientationQuat();
|
||||
this._shapeHalfWidth =
|
||||
this.shapeOrientation === 'X' ? capsuleDepth / 2 : radius;
|
||||
this._shapeHalfHeight =
|
||||
this.shapeOrientation === 'Y' ? capsuleDepth / 2 : radius;
|
||||
this._shapeHalfDepth =
|
||||
this.shapeOrientation !== 'Z' ? radius : capsuleDepth / 2;
|
||||
this.shapeOrientation === 'Z' ? capsuleDepth / 2 : radius;
|
||||
} else if (this.shape === 'Cylinder') {
|
||||
const radius =
|
||||
shapeDimensionA > 0
|
||||
@@ -716,8 +772,12 @@ namespace gdjs {
|
||||
convexRadius
|
||||
);
|
||||
quat = this._getShapeOrientationQuat();
|
||||
this._shapeHalfWidth =
|
||||
this.shapeOrientation === 'X' ? cylinderDepth / 2 : radius;
|
||||
this._shapeHalfHeight =
|
||||
this.shapeOrientation === 'Y' ? cylinderDepth / 2 : radius;
|
||||
this._shapeHalfDepth =
|
||||
this.shapeOrientation !== 'Z' ? radius : cylinderDepth / 2;
|
||||
this.shapeOrientation === 'Z' ? cylinderDepth / 2 : radius;
|
||||
} else {
|
||||
// Create a 'Sphere' by default.
|
||||
const radius =
|
||||
@@ -728,17 +788,20 @@ namespace gdjs {
|
||||
: onePixel;
|
||||
shapeSettings = new Jolt.SphereShapeSettings(radius);
|
||||
quat = this.getQuat(0, 0, 0, 1);
|
||||
this._shapeHalfWidth = radius;
|
||||
this._shapeHalfHeight = radius;
|
||||
this._shapeHalfDepth = radius;
|
||||
}
|
||||
shapeSettings.mDensity = this.density;
|
||||
const rotatedShapeSettings = new Jolt.RotatedTranslatedShapeSettings(
|
||||
this.getVec3(0, 0, 0),
|
||||
return new Jolt.RotatedTranslatedShapeSettings(
|
||||
this.getVec3(
|
||||
this.shapeOffsetX * shapeScale,
|
||||
this.shapeOffsetY * shapeScale,
|
||||
this.shapeOffsetZ * shapeScale
|
||||
),
|
||||
quat,
|
||||
shapeSettings
|
||||
);
|
||||
const rotatedShape = rotatedShapeSettings.Create().Get();
|
||||
Jolt.destroy(rotatedShapeSettings);
|
||||
return rotatedShape;
|
||||
}
|
||||
|
||||
private _getShapeOrientationQuat(): Jolt.Quat {
|
||||
@@ -933,7 +996,7 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
||||
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
||||
result.Set(
|
||||
this.owner3D.getCenterXInScene() * this._sharedData.worldInvScale,
|
||||
this.owner3D.getCenterYInScene() * this._sharedData.worldInvScale,
|
||||
@@ -942,7 +1005,7 @@ namespace gdjs {
|
||||
return result;
|
||||
}
|
||||
|
||||
getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
|
||||
_getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
|
||||
const threeObject = this.owner3D.get3DRendererObject();
|
||||
result.Set(
|
||||
threeObject.quaternion.x,
|
||||
@@ -953,7 +1016,7 @@ namespace gdjs {
|
||||
return result;
|
||||
}
|
||||
|
||||
moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
|
||||
_moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
|
||||
this.owner3D.setCenterXInScene(
|
||||
physicsPosition.GetX() * this._sharedData.worldScale
|
||||
);
|
||||
@@ -965,7 +1028,7 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
|
||||
_moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
|
||||
const threeObject = this.owner3D.get3DRendererObject();
|
||||
threeObject.quaternion.x = physicsRotation.GetX();
|
||||
threeObject.quaternion.y = physicsRotation.GetY();
|
||||
@@ -1103,6 +1166,18 @@ namespace gdjs {
|
||||
this._needToRecreateShape = true;
|
||||
}
|
||||
|
||||
getMassOverride(): float {
|
||||
return this.massOverride;
|
||||
}
|
||||
|
||||
setMassOverride(mass: float): void {
|
||||
if (this.massOverride === mass) {
|
||||
return;
|
||||
}
|
||||
this.massOverride = mass;
|
||||
this._needToRecreateBody = true;
|
||||
}
|
||||
|
||||
getFriction(): float {
|
||||
return this.friction;
|
||||
}
|
||||
@@ -1470,11 +1545,11 @@ namespace gdjs {
|
||||
const deltaX = towardX - body.GetPosition().GetX();
|
||||
const deltaY = towardY - body.GetPosition().GetY();
|
||||
const deltaZ = towardZ - body.GetPosition().GetZ();
|
||||
const distance = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distance === 0) {
|
||||
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSq === 0) {
|
||||
return;
|
||||
}
|
||||
const ratio = length / distance;
|
||||
const ratio = length / Math.sqrt(distanceSq);
|
||||
|
||||
this._sharedData.bodyInterface.AddForce(
|
||||
body.GetID(),
|
||||
@@ -1540,11 +1615,11 @@ namespace gdjs {
|
||||
const deltaX = towardX - originX;
|
||||
const deltaY = towardY - originY;
|
||||
const deltaZ = towardZ - originZ;
|
||||
const distance = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distance === 0) {
|
||||
const distanceSq = deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ;
|
||||
if (distanceSq === 0) {
|
||||
return;
|
||||
}
|
||||
const ratio = length / distance;
|
||||
const ratio = length / Math.sqrt(distanceSq);
|
||||
|
||||
this._sharedData.bodyInterface.AddImpulse(
|
||||
body.GetID(),
|
||||
@@ -1784,8 +1859,8 @@ namespace gdjs {
|
||||
const shape = behavior.createShape();
|
||||
const bodyCreationSettings = new Jolt.BodyCreationSettings(
|
||||
shape,
|
||||
behavior.getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
||||
behavior.getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
||||
behavior._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
||||
behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
||||
behavior.bodyType === 'Static'
|
||||
? Jolt.EMotionType_Static
|
||||
: behavior.bodyType === 'Kinematic'
|
||||
@@ -1806,6 +1881,12 @@ namespace gdjs {
|
||||
bodyCreationSettings.mLinearDamping = behavior.linearDamping;
|
||||
bodyCreationSettings.mAngularDamping = behavior.angularDamping;
|
||||
bodyCreationSettings.mGravityFactor = behavior.gravityScale;
|
||||
if (behavior.massOverride > 0) {
|
||||
bodyCreationSettings.mOverrideMassProperties =
|
||||
Jolt.EOverrideMassProperties_CalculateInertia;
|
||||
bodyCreationSettings.mMassPropertiesOverride.mMass =
|
||||
behavior.massOverride;
|
||||
}
|
||||
|
||||
const bodyInterface = _sharedData.bodyInterface;
|
||||
const body = bodyInterface.CreateBody(bodyCreationSettings);
|
||||
@@ -1824,8 +1905,8 @@ namespace gdjs {
|
||||
// If the body is null, we just don't do anything
|
||||
// (but still run the physics simulation - this is independent).
|
||||
if (_body !== null && _body.IsActive()) {
|
||||
behavior.moveObjectToPhysicsPosition(_body.GetPosition());
|
||||
behavior.moveObjectToPhysicsRotation(_body.GetRotation());
|
||||
behavior._moveObjectToPhysicsPosition(_body.GetPosition());
|
||||
behavior._moveObjectToPhysicsRotation(_body.GetRotation());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1847,8 +1928,8 @@ namespace gdjs {
|
||||
) {
|
||||
_sharedData.bodyInterface.SetPositionAndRotationWhenChanged(
|
||||
body.GetID(),
|
||||
this.behavior.getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
||||
this.behavior.getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
||||
this.behavior._getPhysicsPosition(_sharedData.getRVec3(0, 0, 0)),
|
||||
this.behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
||||
Jolt.EActivation_Activate
|
||||
);
|
||||
}
|
||||
|
1118
Extensions/Physics3DBehavior/PhysicsCar3DRuntimeBehavior.ts
Normal file
@@ -323,7 +323,7 @@ namespace gdjs {
|
||||
this._dontClearInputsBetweenFrames = true;
|
||||
}
|
||||
|
||||
getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
||||
_getPhysicsPosition(result: Jolt.RVec3): Jolt.RVec3 {
|
||||
const physics3D = this.getPhysics3D();
|
||||
if (!physics3D) {
|
||||
result.Set(0, 0, 0);
|
||||
@@ -343,7 +343,7 @@ namespace gdjs {
|
||||
return result;
|
||||
}
|
||||
|
||||
getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
|
||||
_getPhysicsRotation(result: Jolt.Quat): Jolt.Quat {
|
||||
// Characters body should not rotate around X and Y.
|
||||
const rotation = result.sEulerAngles(
|
||||
this.getVec3(0, 0, gdjs.toRad(this.owner3D.getAngle()))
|
||||
@@ -358,7 +358,7 @@ namespace gdjs {
|
||||
return result;
|
||||
}
|
||||
|
||||
moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
|
||||
_moveObjectToPhysicsPosition(physicsPosition: Jolt.RVec3): void {
|
||||
const physics3D = this.getPhysics3D();
|
||||
if (!physics3D) {
|
||||
return;
|
||||
@@ -376,7 +376,7 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
|
||||
moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
|
||||
_moveObjectToPhysicsRotation(physicsRotation: Jolt.Quat): void {
|
||||
const threeObject = this.owner3D.get3DRendererObject();
|
||||
threeObject.quaternion.x = physicsRotation.GetX();
|
||||
threeObject.quaternion.y = physicsRotation.GetY();
|
||||
@@ -1503,7 +1503,8 @@ namespace gdjs {
|
||||
const { behavior } = physics3D;
|
||||
const { _slopeMaxAngle, owner3D, _sharedData } = this.characterBehavior;
|
||||
|
||||
const shape = behavior.createShape();
|
||||
// Jolt doesn't support center of mass offset for characters.
|
||||
const shape = behavior.createShapeWithoutMassCenterOffset();
|
||||
|
||||
const settings = new Jolt.CharacterVirtualSettings();
|
||||
// Characters innerBody are Kinematic body, they don't allow other
|
||||
@@ -1542,10 +1543,10 @@ namespace gdjs {
|
||||
);
|
||||
const character = new Jolt.CharacterVirtual(
|
||||
settings,
|
||||
this.characterBehavior.getPhysicsPosition(
|
||||
this.characterBehavior._getPhysicsPosition(
|
||||
_sharedData.getRVec3(0, 0, 0)
|
||||
),
|
||||
behavior.getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
||||
behavior._getPhysicsRotation(_sharedData.getQuat(0, 0, 0, 1)),
|
||||
_sharedData.physicsSystem
|
||||
);
|
||||
Jolt.destroy(settings);
|
||||
@@ -1622,6 +1623,19 @@ namespace gdjs {
|
||||
contactNormal,
|
||||
settings
|
||||
) => {};
|
||||
characterContactListener.OnContactPersisted = (
|
||||
inCharacter,
|
||||
inBodyID2,
|
||||
inSubShapeID2,
|
||||
inContactPosition,
|
||||
inContactNormal,
|
||||
ioSettings
|
||||
) => {};
|
||||
characterContactListener.OnContactRemoved = (
|
||||
inCharacter,
|
||||
inBodyID2,
|
||||
inSubShapeID2
|
||||
) => {};
|
||||
characterContactListener.OnCharacterContactAdded = (
|
||||
character,
|
||||
otherCharacter,
|
||||
@@ -1630,6 +1644,19 @@ namespace gdjs {
|
||||
contactNormal,
|
||||
settings
|
||||
) => {};
|
||||
characterContactListener.OnCharacterContactPersisted = (
|
||||
inCharacter,
|
||||
inOtherCharacter,
|
||||
inSubShapeID2,
|
||||
inContactPosition,
|
||||
inContactNormal,
|
||||
ioSettings
|
||||
) => {};
|
||||
characterContactListener.OnCharacterContactRemoved = (
|
||||
inCharacter,
|
||||
inOtherCharacter,
|
||||
inSubShapeID2
|
||||
) => {};
|
||||
characterContactListener.OnContactSolve = (
|
||||
character,
|
||||
bodyID2,
|
||||
@@ -1666,10 +1693,10 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
// We can't rely on the body position because of mCharacterPadding.
|
||||
this.characterBehavior.moveObjectToPhysicsPosition(
|
||||
this.characterBehavior._moveObjectToPhysicsPosition(
|
||||
character.GetPosition()
|
||||
);
|
||||
this.characterBehavior.moveObjectToPhysicsRotation(
|
||||
this.characterBehavior._moveObjectToPhysicsRotation(
|
||||
character.GetRotation()
|
||||
);
|
||||
}
|
||||
@@ -1697,7 +1724,7 @@ namespace gdjs {
|
||||
behavior._objectOldRotationZ !== owner3D.getAngle()
|
||||
) {
|
||||
character.SetRotation(
|
||||
this.characterBehavior.getPhysicsRotation(
|
||||
this.characterBehavior._getPhysicsRotation(
|
||||
_sharedData.getQuat(0, 0, 0, 1)
|
||||
)
|
||||
);
|
||||
@@ -1710,7 +1737,7 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
character.SetPosition(
|
||||
this.characterBehavior.getPhysicsPosition(
|
||||
this.characterBehavior._getPhysicsPosition(
|
||||
_sharedData.getRVec3(0, 0, 0)
|
||||
)
|
||||
);
|
||||
@@ -1732,7 +1759,7 @@ namespace gdjs {
|
||||
if (!character) {
|
||||
return;
|
||||
}
|
||||
const shape = behavior.createShape();
|
||||
const shape = behavior.createShapeWithoutMassCenterOffset();
|
||||
const isShapeValid = character.SetShape(
|
||||
shape,
|
||||
Number.MAX_VALUE,
|
||||
|
103
Extensions/Physics3DBehavior/jolt-physics.d.ts
vendored
@@ -26,6 +26,7 @@ declare namespace Jolt {
|
||||
size(): number;
|
||||
}
|
||||
class ArrayVec3 {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): Vec3;
|
||||
@@ -36,6 +37,7 @@ declare namespace Jolt {
|
||||
data(): Vec3MemRef;
|
||||
}
|
||||
class ArrayQuat {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): Quat;
|
||||
@@ -46,6 +48,7 @@ declare namespace Jolt {
|
||||
data(): QuatMemRef;
|
||||
}
|
||||
class ArrayMat44 {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): Mat44;
|
||||
@@ -56,6 +59,7 @@ declare namespace Jolt {
|
||||
data(): Mat44MemRef;
|
||||
}
|
||||
class ArrayBodyID {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): BodyID;
|
||||
@@ -66,6 +70,7 @@ declare namespace Jolt {
|
||||
data(): BodyIDMemRef;
|
||||
}
|
||||
class ArrayBodyPtr {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): Body;
|
||||
@@ -431,6 +436,13 @@ declare namespace Jolt {
|
||||
function _emscripten_enum_SoftBodySharedSettings_ELRAType_SoftBodySharedSettings_ELRAType_None(): SoftBodySharedSettings_ELRAType;
|
||||
function _emscripten_enum_SoftBodySharedSettings_ELRAType_SoftBodySharedSettings_ELRAType_EuclideanDistance(): SoftBodySharedSettings_ELRAType;
|
||||
function _emscripten_enum_SoftBodySharedSettings_ELRAType_SoftBodySharedSettings_ELRAType_GeodesicDistance(): SoftBodySharedSettings_ELRAType;
|
||||
const MeshShapeSettings_EBuildQuality_FavorRuntimePerformance: number;
|
||||
const MeshShapeSettings_EBuildQuality_FavorBuildSpeed: number;
|
||||
type MeshShapeSettings_EBuildQuality =
|
||||
| typeof MeshShapeSettings_EBuildQuality_FavorRuntimePerformance
|
||||
| typeof MeshShapeSettings_EBuildQuality_FavorBuildSpeed;
|
||||
function _emscripten_enum_MeshShapeSettings_EBuildQuality_MeshShapeSettings_EBuildQuality_FavorRuntimePerformance(): MeshShapeSettings_EBuildQuality;
|
||||
function _emscripten_enum_MeshShapeSettings_EBuildQuality_MeshShapeSettings_EBuildQuality_FavorBuildSpeed(): MeshShapeSettings_EBuildQuality;
|
||||
class Vec3MemRef {}
|
||||
class QuatMemRef {}
|
||||
class Mat44MemRef {}
|
||||
@@ -444,6 +456,7 @@ declare namespace Jolt {
|
||||
constructor(inV: Float3);
|
||||
constructor(inX: number, inY: number, inZ: number);
|
||||
sZero(): Vec3;
|
||||
sOne(): Vec3;
|
||||
sAxisX(): Vec3;
|
||||
sAxisY(): Vec3;
|
||||
sAxisZ(): Vec3;
|
||||
@@ -505,6 +518,7 @@ declare namespace Jolt {
|
||||
constructor();
|
||||
constructor(inX: number, inY: number, inZ: number);
|
||||
sZero(): RVec3;
|
||||
sOne(): RVec3;
|
||||
sAxisX(): RVec3;
|
||||
sAxisY(): RVec3;
|
||||
sAxisZ(): RVec3;
|
||||
@@ -552,6 +566,7 @@ declare namespace Jolt {
|
||||
constructor(inV: Vec3, inW: number);
|
||||
constructor(inX: number, inY: number, inZ: number, inW: number);
|
||||
sZero(): Vec4;
|
||||
sOne(): Vec4;
|
||||
sReplicate(inV: number): Vec4;
|
||||
sMin(inLHS: Vec4, inRHS: Vec4): Vec4;
|
||||
sMax(inLHS: Vec4, inRHS: Vec4): Vec4;
|
||||
@@ -1427,6 +1442,9 @@ declare namespace Jolt {
|
||||
get_mPerTriangleUserData(): boolean;
|
||||
set_mPerTriangleUserData(mPerTriangleUserData: boolean): void;
|
||||
mPerTriangleUserData: boolean;
|
||||
get_mBuildQuality(): MeshShapeSettings_EBuildQuality;
|
||||
set_mBuildQuality(mBuildQuality: MeshShapeSettings_EBuildQuality): void;
|
||||
mBuildQuality: MeshShapeSettings_EBuildQuality;
|
||||
}
|
||||
class MeshShape extends Shape {
|
||||
GetTriangleUserData(inSubShapeID: SubShapeID): number;
|
||||
@@ -2523,6 +2541,8 @@ declare namespace Jolt {
|
||||
GetGravityFactor(inBodyID: BodyID): number;
|
||||
SetUseManifoldReduction(inBodyID: BodyID, inUseReduction: boolean): void;
|
||||
GetUseManifoldReduction(inBodyID: BodyID): boolean;
|
||||
SetCollisionGroup(inBodyID: BodyID, inCollisionGroup: CollisionGroup): void;
|
||||
GetCollisionGroup(inBodyID: BodyID): CollisionGroup;
|
||||
AddForce(
|
||||
inBodyID: BodyID,
|
||||
inForce: Vec3,
|
||||
@@ -2624,9 +2644,9 @@ declare namespace Jolt {
|
||||
get_mLinearCastMaxPenetration(): number;
|
||||
set_mLinearCastMaxPenetration(mLinearCastMaxPenetration: number): void;
|
||||
mLinearCastMaxPenetration: number;
|
||||
get_mManifoldToleranceSq(): number;
|
||||
set_mManifoldToleranceSq(mManifoldToleranceSq: number): void;
|
||||
mManifoldToleranceSq: number;
|
||||
get_mManifoldTolerance(): number;
|
||||
set_mManifoldTolerance(mManifoldTolerance: number): void;
|
||||
mManifoldTolerance: number;
|
||||
get_mMaxPenetrationDistance(): number;
|
||||
set_mMaxPenetrationDistance(mMaxPenetrationDistance: number): void;
|
||||
mMaxPenetrationDistance: number;
|
||||
@@ -2983,6 +3003,7 @@ declare namespace Jolt {
|
||||
AddHit(inResult: number): void;
|
||||
}
|
||||
class ArrayRayCastResult {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): RayCastResult;
|
||||
@@ -3040,6 +3061,7 @@ declare namespace Jolt {
|
||||
AddHit(inResult: number): void;
|
||||
}
|
||||
class ArrayCollidePointResult {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): CollidePointResult;
|
||||
@@ -3114,6 +3136,7 @@ declare namespace Jolt {
|
||||
AddHit(inResult: number): void;
|
||||
}
|
||||
class ArrayCollideShapeResult {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): CollideShapeResult;
|
||||
@@ -3188,6 +3211,7 @@ declare namespace Jolt {
|
||||
AddHit(inResult: number): void;
|
||||
}
|
||||
class ArrayShapeCastResult {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): ShapeCastResult;
|
||||
@@ -3618,6 +3642,7 @@ declare namespace Jolt {
|
||||
mMaxDistance: number;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsVertex {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsVertex;
|
||||
@@ -3627,6 +3652,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsFace {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsFace;
|
||||
@@ -3636,6 +3662,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsEdge {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsEdge;
|
||||
@@ -3645,6 +3672,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsDihedralBend {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsDihedralBend;
|
||||
@@ -3654,6 +3682,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsVolume {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsVolume;
|
||||
@@ -3663,6 +3692,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsInvBind {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsInvBind;
|
||||
@@ -3672,6 +3702,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsSkinned {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsSkinned;
|
||||
@@ -3681,6 +3712,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsLRA {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsLRA;
|
||||
@@ -3708,6 +3740,7 @@ declare namespace Jolt {
|
||||
mLRAMaxDistanceMultiplier: number;
|
||||
}
|
||||
class ArraySoftBodySharedSettingsVertexAttributes {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodySharedSettingsVertexAttributes;
|
||||
@@ -3856,6 +3889,7 @@ declare namespace Jolt {
|
||||
readonly mVelocityOffset: number;
|
||||
}
|
||||
class ArraySoftBodyVertex {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): SoftBodyVertex;
|
||||
@@ -3923,8 +3957,18 @@ declare namespace Jolt {
|
||||
set_mShape(mShape: Shape): void;
|
||||
mShape: Shape;
|
||||
}
|
||||
class CharacterID {
|
||||
constructor();
|
||||
GetValue(): number;
|
||||
IsInvalid(): boolean;
|
||||
sNextCharacterID(): CharacterID;
|
||||
sSetNextCharacterID(inNextValue: number): void;
|
||||
}
|
||||
class CharacterVirtualSettings extends CharacterBaseSettings {
|
||||
constructor();
|
||||
get_mID(): CharacterID;
|
||||
set_mID(mID: CharacterID): void;
|
||||
mID: CharacterID;
|
||||
get_mMass(): number;
|
||||
set_mMass(mMass: number): void;
|
||||
mMass: number;
|
||||
@@ -3967,6 +4011,9 @@ declare namespace Jolt {
|
||||
get_mInnerBodyShape(): Shape;
|
||||
set_mInnerBodyShape(mInnerBodyShape: Shape): void;
|
||||
mInnerBodyShape: Shape;
|
||||
get_mInnerBodyIDOverride(): BodyID;
|
||||
set_mInnerBodyIDOverride(mInnerBodyIDOverride: BodyID): void;
|
||||
mInnerBodyIDOverride: BodyID;
|
||||
get_mInnerBodyLayer(): number;
|
||||
set_mInnerBodyLayer(mInnerBodyLayer: number): void;
|
||||
mInnerBodyLayer: number;
|
||||
@@ -4008,6 +4055,19 @@ declare namespace Jolt {
|
||||
inContactNormal: number,
|
||||
ioSettings: number
|
||||
): void;
|
||||
OnContactPersisted(
|
||||
inCharacter: number,
|
||||
inBodyID2: number,
|
||||
inSubShapeID2: number,
|
||||
inContactPosition: number,
|
||||
inContactNormal: number,
|
||||
ioSettings: number
|
||||
): void;
|
||||
OnContactRemoved(
|
||||
inCharacter: number,
|
||||
inBodyID2: number,
|
||||
inSubShapeID2: number
|
||||
): void;
|
||||
OnCharacterContactAdded(
|
||||
inCharacter: number,
|
||||
inOtherCharacter: number,
|
||||
@@ -4016,6 +4076,19 @@ declare namespace Jolt {
|
||||
inContactNormal: number,
|
||||
ioSettings: number
|
||||
): void;
|
||||
OnCharacterContactPersisted(
|
||||
inCharacter: number,
|
||||
inOtherCharacter: number,
|
||||
inSubShapeID2: number,
|
||||
inContactPosition: number,
|
||||
inContactNormal: number,
|
||||
ioSettings: number
|
||||
): void;
|
||||
OnCharacterContactRemoved(
|
||||
inCharacter: number,
|
||||
inOtherCharacter: number,
|
||||
inSubShapeID2: number
|
||||
): void;
|
||||
OnContactSolve(
|
||||
inCharacter: number,
|
||||
inBodyID2: number,
|
||||
@@ -4091,9 +4164,9 @@ declare namespace Jolt {
|
||||
get_mBodyB(): BodyID;
|
||||
set_mBodyB(mBodyB: BodyID): void;
|
||||
mBodyB: BodyID;
|
||||
get_mCharacterB(): CharacterVirtual;
|
||||
set_mCharacterB(mCharacterB: CharacterVirtual): void;
|
||||
mCharacterB: CharacterVirtual;
|
||||
get_mCharacterIDB(): CharacterID;
|
||||
set_mCharacterIDB(mCharacterIDB: CharacterID): void;
|
||||
mCharacterIDB: CharacterID;
|
||||
get_mSubShapeIDB(): SubShapeID;
|
||||
set_mSubShapeIDB(mSubShapeIDB: SubShapeID): void;
|
||||
mSubShapeIDB: SubShapeID;
|
||||
@@ -4103,6 +4176,9 @@ declare namespace Jolt {
|
||||
get_mIsSensorB(): boolean;
|
||||
set_mIsSensorB(mIsSensorB: boolean): void;
|
||||
mIsSensorB: boolean;
|
||||
get_mCharacterB(): CharacterVirtual;
|
||||
set_mCharacterB(mCharacterB: CharacterVirtual): void;
|
||||
mCharacterB: CharacterVirtual;
|
||||
get_mUserData(): number;
|
||||
set_mUserData(mUserData: number): void;
|
||||
mUserData: number;
|
||||
@@ -4120,6 +4196,7 @@ declare namespace Jolt {
|
||||
mCanPushCharacter: boolean;
|
||||
}
|
||||
class ArrayCharacterVirtualContact {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): CharacterVirtualContact;
|
||||
@@ -4234,6 +4311,7 @@ declare namespace Jolt {
|
||||
inRotation: Quat,
|
||||
inSystem: PhysicsSystem
|
||||
);
|
||||
GetID(): CharacterID;
|
||||
SetListener(inListener: CharacterContactListener): void;
|
||||
SetCharacterVsCharacterCollision(
|
||||
inCharacterVsCharacterCollision: CharacterVsCharacterCollision
|
||||
@@ -4265,6 +4343,8 @@ declare namespace Jolt {
|
||||
GetUserData(): number;
|
||||
SetUserData(inUserData: number): void;
|
||||
GetInnerBodyID(): BodyID;
|
||||
StartTrackingContactChanges(): void;
|
||||
FinishTrackingContactChanges(): void;
|
||||
CancelVelocityTowardsSteepSlopes(inDesiredVelocity: Vec3): Vec3;
|
||||
Update(
|
||||
inDeltaTime: number,
|
||||
@@ -4326,6 +4406,7 @@ declare namespace Jolt {
|
||||
SetInnerBodyShape(inShape: Shape): void;
|
||||
GetTransformedShape(): TransformedShape;
|
||||
HasCollidedWith(inBodyID: BodyID): boolean;
|
||||
HasCollidedWithCharacterID(inCharacterID: CharacterID): boolean;
|
||||
HasCollidedWithCharacter(inCharacter: CharacterVirtual): boolean;
|
||||
GetActiveContacts(): ArrayCharacterVirtualContact;
|
||||
}
|
||||
@@ -4340,6 +4421,7 @@ declare namespace Jolt {
|
||||
GetValue(inX: number): number;
|
||||
}
|
||||
class ArrayFloat {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): number;
|
||||
@@ -4350,6 +4432,7 @@ declare namespace Jolt {
|
||||
data(): FloatMemRef;
|
||||
}
|
||||
class ArrayUint {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): number;
|
||||
@@ -4360,6 +4443,7 @@ declare namespace Jolt {
|
||||
data(): UintMemRef;
|
||||
}
|
||||
class ArrayUint8 {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): number;
|
||||
@@ -4370,6 +4454,7 @@ declare namespace Jolt {
|
||||
data(): Uint8MemRef;
|
||||
}
|
||||
class ArrayVehicleAntiRollBar {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): VehicleAntiRollBar;
|
||||
@@ -4378,6 +4463,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArrayWheelSettings {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): WheelSettings;
|
||||
@@ -4386,6 +4472,7 @@ declare namespace Jolt {
|
||||
clear(): void;
|
||||
}
|
||||
class ArrayVehicleDifferentialSettings {
|
||||
constructor();
|
||||
empty(): boolean;
|
||||
size(): number;
|
||||
at(inIndex: number): VehicleDifferentialSettings;
|
||||
@@ -4457,6 +4544,7 @@ declare namespace Jolt {
|
||||
inWheelRight: Vec3,
|
||||
inWheelUp: Vec3
|
||||
): RMat44;
|
||||
GetAntiRollBars(): ArrayVehicleAntiRollBar;
|
||||
SetNumStepsBetweenCollisionTestActive(inSteps: number): void;
|
||||
GetNumStepsBetweenCollisionTestActive(): number;
|
||||
SetNumStepsBetweenCollisionTestInactive(inSteps: number): void;
|
||||
@@ -4819,9 +4907,6 @@ declare namespace Jolt {
|
||||
}
|
||||
class VehicleControllerSettings {}
|
||||
class VehicleController {
|
||||
GetRefCount(): number;
|
||||
AddRef(): void;
|
||||
Release(): void;
|
||||
GetConstraint(): VehicleConstraint;
|
||||
}
|
||||
class WheeledVehicleController extends VehicleController {
|
||||
|
@@ -23,7 +23,9 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
"held, customizable gravity... It can be used for the player, but "
|
||||
"also for other objects moving on platforms. In this case though, "
|
||||
"it's recommended to first check if there is a simpler behavior that "
|
||||
"could be used.",
|
||||
"could be used. Default controls for keyboards are included. For "
|
||||
"touch or gamepads, use the \"Multitouch Joystick\" objects and the "
|
||||
"associated \"mapper\" behaviors.",
|
||||
"Florian Rival",
|
||||
"Open source (MIT License)")
|
||||
.SetCategory("Movement")
|
||||
@@ -33,16 +35,16 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.SetIcon("CppPlatform/Extensions/platformerobjecticon.png");
|
||||
|
||||
{
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
"PlatformerObjectBehavior",
|
||||
_("Platformer character"),
|
||||
"PlatformerObject",
|
||||
_("Jump and run on platforms."),
|
||||
"",
|
||||
"CppPlatform/Extensions/platformerobjecticon.png",
|
||||
"PlatformerObjectBehavior",
|
||||
std::make_shared<PlatformerObjectBehavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>());
|
||||
gd::BehaviorMetadata& aut =
|
||||
extension.AddBehavior("PlatformerObjectBehavior",
|
||||
_("Platformer character"),
|
||||
"PlatformerObject",
|
||||
_("Jump and run on platforms."),
|
||||
"",
|
||||
"CppPlatform/Extensions/platformerobjecticon.png",
|
||||
"PlatformerObjectBehavior",
|
||||
std::make_shared<PlatformerObjectBehavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>());
|
||||
|
||||
// Deprecated, use IsMovingEvenALittle instead
|
||||
aut.AddCondition("IsMoving",
|
||||
@@ -59,14 +61,15 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.MarkAsSimple()
|
||||
.SetFunctionName("IsMoving");
|
||||
|
||||
aut.AddScopedCondition("IsMovingEvenALittle",
|
||||
_("Is moving"),
|
||||
_("Check if the object is moving (whether it is on the "
|
||||
"floor or in the air)."),
|
||||
_("_PARAM0_ is moving"),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
aut.AddScopedCondition(
|
||||
"IsMovingEvenALittle",
|
||||
_("Is moving"),
|
||||
_("Check if the object is moving (whether it is on the "
|
||||
"floor or in the air)."),
|
||||
_("_PARAM0_ is moving"),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png",
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.MarkAsSimple();
|
||||
@@ -525,14 +528,14 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("SimulateLadderKey");
|
||||
|
||||
aut.AddAction(
|
||||
"SimulateReleaseLadderKey",
|
||||
_("Simulate release ladder key press"),
|
||||
_("Simulate a press of the Release Ladder key (used to get off a ladder)."),
|
||||
_("Simulate pressing Release Ladder key for _PARAM0_"),
|
||||
_("Platformer controls"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
aut.AddAction("SimulateReleaseLadderKey",
|
||||
_("Simulate release ladder key press"),
|
||||
_("Simulate a press of the Release Ladder key (used to get "
|
||||
"off a ladder)."),
|
||||
_("Simulate pressing Release Ladder key for _PARAM0_"),
|
||||
_("Platformer controls"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.MarkAsAdvanced();
|
||||
@@ -550,7 +553,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
|
||||
aut.AddAction("SimulateReleasePlatformKey",
|
||||
_("Simulate release platform key press"),
|
||||
_("Simulate a press of the release platform key (used when grabbing a "
|
||||
_("Simulate a press of the release platform key (used when "
|
||||
"grabbing a "
|
||||
"platform ledge)."),
|
||||
_("Simulate pressing Release Platform key for _PARAM0_"),
|
||||
_("Platformer controls"),
|
||||
@@ -561,7 +565,8 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.SetFunctionName("SimulateReleasePlatformKey");
|
||||
|
||||
// Support for deprecated names:
|
||||
aut.AddDuplicatedAction("SimulateReleaseKey", "SimulateReleasePlatformKey").SetHidden();
|
||||
aut.AddDuplicatedAction("SimulateReleaseKey", "SimulateReleasePlatformKey")
|
||||
.SetHidden();
|
||||
|
||||
aut.AddAction("SimulateControl",
|
||||
_("Simulate control"),
|
||||
@@ -574,23 +579,27 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Key"),
|
||||
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
|
||||
_("Key"),
|
||||
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release "
|
||||
"Ladder\", \"Up\", \"Down\"]")
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("SimulateControl");
|
||||
|
||||
aut.AddScopedCondition("IsUsingControl",
|
||||
_("Control pressed or simulated"),
|
||||
_("A control was applied from a default control or simulated by an action."),
|
||||
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
|
||||
_("Platformer state"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
aut.AddScopedCondition(
|
||||
"IsUsingControl",
|
||||
_("Control pressed or simulated"),
|
||||
_("A control was applied from a default control or simulated by an "
|
||||
"action."),
|
||||
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
|
||||
_("Platformer state"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Key"),
|
||||
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
|
||||
_("Key"),
|
||||
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release "
|
||||
"Ladder\", \"Up\", \"Down\"]")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
aut.AddAction("IgnoreDefaultControls",
|
||||
@@ -792,59 +801,65 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.SetFunctionName("GetJumpSpeed");
|
||||
|
||||
aut.AddExpression("JumpSustainTime",
|
||||
_("Jump sustain time"),
|
||||
_("Return the jump sustain time of the object (in seconds)."
|
||||
"This is the time during which keeping the jump button held "
|
||||
"allow the initial jump speed to be maintained."),
|
||||
_("Platformer configuration"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
aut.AddExpression(
|
||||
"JumpSustainTime",
|
||||
_("Jump sustain time"),
|
||||
_("Return the jump sustain time of the object (in seconds)."
|
||||
"This is the time during which keeping the jump button held "
|
||||
"allow the initial jump speed to be maintained."),
|
||||
_("Platformer configuration"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior");
|
||||
|
||||
aut.AddExpression("CurrentFallSpeed",
|
||||
_("Current fall speed"),
|
||||
_("Return the current fall speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
aut.AddExpression(
|
||||
"CurrentFallSpeed",
|
||||
_("Current fall speed"),
|
||||
_("Return the current fall speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.SetFunctionName("GetCurrentFallSpeed");
|
||||
|
||||
aut.AddExpression("CurrentSpeed",
|
||||
_("Current horizontal speed"),
|
||||
_("Return the current horizontal speed of the object "
|
||||
"(in pixels per second). The object moves to the left "
|
||||
"with negative values and to the right with positive ones"),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
aut.AddExpression(
|
||||
"CurrentSpeed",
|
||||
_("Current horizontal speed"),
|
||||
_("Return the current horizontal speed of the object "
|
||||
"(in pixels per second). The object moves to the left "
|
||||
"with negative values and to the right with positive ones"),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.SetFunctionName("GetCurrentSpeed");
|
||||
|
||||
aut.AddExpression("CurrentJumpSpeed",
|
||||
_("Current jump speed"),
|
||||
_("Return the current jump speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
aut.AddExpression(
|
||||
"CurrentJumpSpeed",
|
||||
_("Current jump speed"),
|
||||
_("Return the current jump speed of the object "
|
||||
"(in pixels per second). Its value is always positive."),
|
||||
_("Platformer state"),
|
||||
"CppPlatform/Extensions/platformerobjecticon.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.SetFunctionName("GetCurrentJumpSpeed");
|
||||
}
|
||||
{
|
||||
gd::BehaviorMetadata& aut = extension.AddBehavior(
|
||||
"PlatformBehavior",
|
||||
_("Platform"),
|
||||
"Platform",
|
||||
_("Flag objects as being platforms which characters can run on."),
|
||||
"",
|
||||
"CppPlatform/Extensions/platformicon.png",
|
||||
"PlatformBehavior",
|
||||
std::make_shared<PlatformBehavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
gd::BehaviorMetadata& aut =
|
||||
extension
|
||||
.AddBehavior("PlatformBehavior",
|
||||
_("Platform"),
|
||||
"Platform",
|
||||
_("Flag objects as being platforms which characters "
|
||||
"can run on."),
|
||||
"",
|
||||
"CppPlatform/Extensions/platformicon.png",
|
||||
"PlatformBehavior",
|
||||
std::make_shared<PlatformBehavior>(),
|
||||
std::make_shared<gd::BehaviorsSharedData>())
|
||||
.SetQuickCustomizationVisibility(gd::QuickCustomization::Hidden);
|
||||
|
||||
aut.AddAction("ChangePlatformType",
|
||||
_("Platform type"),
|
||||
@@ -857,21 +872,23 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformBehavior")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Platform type"),
|
||||
"[\"Platform\",\"Jumpthru\",\"Ladder\"]")
|
||||
_("Platform type"),
|
||||
"[\"Platform\",\"Jumpthru\",\"Ladder\"]")
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("ChangePlatformType");
|
||||
}
|
||||
|
||||
extension.AddCondition("IsObjectOnGivenFloor",
|
||||
_("Character is on given platform"),
|
||||
_("Check if a platformer character is on a given platform."),
|
||||
_("_PARAM0_ is on platform _PARAM2_"),
|
||||
_("Collision"),
|
||||
"CppPlatform/Extensions/platformicon.png",
|
||||
"CppPlatform/Extensions/platformicon.png")
|
||||
.AddParameter("objectList", _("Object"), "", false)
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("objectList", _("Platforms"), "", false)
|
||||
.AddCodeOnlyParameter("conditionInverted", "");
|
||||
extension
|
||||
.AddCondition(
|
||||
"IsObjectOnGivenFloor",
|
||||
_("Character is on given platform"),
|
||||
_("Check if a platformer character is on a given platform."),
|
||||
_("_PARAM0_ is on platform _PARAM2_"),
|
||||
_("Collision"),
|
||||
"CppPlatform/Extensions/platformicon.png",
|
||||
"CppPlatform/Extensions/platformicon.png")
|
||||
.AddParameter("objectList", _("Object"), "", false)
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("objectList", _("Platforms"), "", false)
|
||||
.AddCodeOnlyParameter("conditionInverted", "");
|
||||
}
|
||||
|
@@ -146,7 +146,7 @@ describe('gdjs.TextInputRuntimeObject (using a PixiJS RuntimeGame with DOM eleme
|
||||
expect(gameDomElementContainer.querySelector('input')).not.to.be(null);
|
||||
expect(gameDomElementContainer.querySelector('textarea')).to.be(null);
|
||||
|
||||
object.deleteFromScene(runtimeScene);
|
||||
object.deleteFromScene();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(gameDomElementContainer.querySelector('input')).to.be(null);
|
||||
expect(gameDomElementContainer.querySelector('textarea')).to.be(null);
|
||||
|
@@ -98,7 +98,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
if (this._object.isWrapping()) {
|
||||
if (this._object.isWrapping() && this._text.width !== 0) {
|
||||
const alignmentX =
|
||||
this._object._textAlign === 'right'
|
||||
? 1
|
||||
@@ -117,7 +117,6 @@ namespace gdjs {
|
||||
this._text.position.x = this._object.x + this._text.width / 2;
|
||||
this._text.anchor.x = 0.5;
|
||||
}
|
||||
this._text.position.y = this._object.y + this._text.height / 2;
|
||||
|
||||
const alignmentY =
|
||||
this._object._verticalTextAlignment === 'bottom'
|
||||
|
@@ -89,7 +89,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
private _deleteFromScene() {
|
||||
this.owner.deleteFromScene(this.owner.getInstanceContainer());
|
||||
this.owner.deleteFromScene();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -105,7 +105,14 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
let usedVariantData: EventsBasedObjectVariantData = eventsBasedObjectData;
|
||||
if (!eventsBasedObjectData.defaultVariant) {
|
||||
eventsBasedObjectData.defaultVariant = {
|
||||
...eventsBasedObjectData,
|
||||
name: '',
|
||||
};
|
||||
}
|
||||
let usedVariantData: EventsBasedObjectVariantData =
|
||||
eventsBasedObjectData.defaultVariant;
|
||||
if (customObjectData.variant) {
|
||||
for (
|
||||
let variantIndex = 0;
|
||||
@@ -199,13 +206,13 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
override onDeletedFromScene(parent: gdjs.RuntimeInstanceContainer): void {
|
||||
override onDeletedFromScene(): void {
|
||||
// Let subclasses do something before the object is destroyed.
|
||||
this.onDestroy(parent);
|
||||
this.onDestroy(this._runtimeScene);
|
||||
// Let behaviors do something before the object is destroyed.
|
||||
super.onDeletedFromScene(parent);
|
||||
super.onDeletedFromScene();
|
||||
// Destroy the children.
|
||||
this._instanceContainer.onDestroyFromScene(parent);
|
||||
this._instanceContainer.onDestroyFromScene(this._runtimeScene);
|
||||
}
|
||||
|
||||
override update(parent: gdjs.RuntimeInstanceContainer): void {
|
||||
|
@@ -80,7 +80,11 @@ namespace gdjs {
|
||||
++i
|
||||
) {
|
||||
const childObjectData = eventsBasedObjectVariantData.objects[i];
|
||||
if (customObjectData.childrenContent) {
|
||||
// The children configuration override only applies to the default variant.
|
||||
if (
|
||||
customObjectData.childrenContent &&
|
||||
!eventsBasedObjectVariantData.name
|
||||
) {
|
||||
this.registerObject({
|
||||
...childObjectData,
|
||||
// The custom object overrides its events-based object configuration.
|
||||
@@ -183,7 +187,7 @@ namespace gdjs {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
object.onDeletedFromScene(this);
|
||||
object.onDeletedFromScene();
|
||||
// The object can free all its resource directly...
|
||||
object.onDestroyed();
|
||||
}
|
||||
|
@@ -678,7 +678,7 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
// Notify the object it was removed from the container
|
||||
obj.onDeletedFromScene(this);
|
||||
obj.onDeletedFromScene();
|
||||
|
||||
// Notify the global callbacks
|
||||
for (let j = 0; j < gdjs.callbacksObjectDeletedFromScene.length; ++j) {
|
||||
|
@@ -1376,7 +1376,7 @@ namespace gdjs {
|
||||
) {
|
||||
// Instance was deleted (or object name changed, in which case it will be re-created later)
|
||||
if (runtimeObject) {
|
||||
runtimeObject.deleteFromScene(runtimeInstanceContainer);
|
||||
runtimeObject.deleteFromScene();
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
@@ -224,8 +224,7 @@ namespace gdjs {
|
||||
let yOffset = 0;
|
||||
if (anticipateMove && !object.hasNoForces()) {
|
||||
const objectAverageForce = object.getAverageForce();
|
||||
const elapsedTimeInSeconds =
|
||||
object.getElapsedTime(instanceContainer) / 1000;
|
||||
const elapsedTimeInSeconds = object.getElapsedTime() / 1000;
|
||||
xOffset = objectAverageForce.getX() * elapsedTimeInSeconds;
|
||||
yOffset = objectAverageForce.getY() * elapsedTimeInSeconds;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 80 KiB |
@@ -3,7 +3,6 @@
|
||||
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
|
||||
* This project is released under the MIT License.
|
||||
*/
|
||||
let lavaMaterial: THREE.ShaderMaterial;
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('PIXI Image manager');
|
||||
|
||||
@@ -235,96 +234,29 @@ namespace gdjs {
|
||||
vertexColors: boolean;
|
||||
}
|
||||
): THREE.Material {
|
||||
// const cacheKey = `${resourceName}|${useTransparentTexture ? 1 : 0}|${
|
||||
// forceBasicMaterial ? 1 : 0
|
||||
// }`;
|
||||
const cacheKey = `${resourceName}|${useTransparentTexture ? 1 : 0}|${
|
||||
forceBasicMaterial ? 1 : 0
|
||||
}|${vertexColors ? 1 : 0}`;
|
||||
|
||||
// const loadedThreeMaterial = this._loadedThreeMaterials.get(cacheKey);
|
||||
// if (loadedThreeMaterial) return loadedThreeMaterial;
|
||||
const loadedThreeMaterial = this._loadedThreeMaterials.get(cacheKey);
|
||||
if (loadedThreeMaterial) return loadedThreeMaterial;
|
||||
|
||||
// const material = forceBasicMaterial
|
||||
// ? new THREE.MeshBasicMaterial({
|
||||
// map: this.getThreeTexture(resourceName),
|
||||
// side: useTransparentTexture ? THREE.DoubleSide : THREE.FrontSide,
|
||||
// transparent: useTransparentTexture,
|
||||
// vertexColors: true,
|
||||
// })
|
||||
// : new THREE.MeshStandardMaterial({
|
||||
// map: this.getThreeTexture(resourceName),
|
||||
// side: useTransparentTexture ? THREE.DoubleSide : THREE.FrontSide,
|
||||
// transparent: useTransparentTexture,
|
||||
// metalness: 0,
|
||||
// vertexColors: true,
|
||||
// });
|
||||
// this._loadedThreeMaterials.put(cacheKey, material);
|
||||
let tex1 = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/GDJS/Runtime/pixi-renderers/cloudLava.png'
|
||||
);
|
||||
tex1.wrapS = THREE.RepeatWrapping;
|
||||
tex1.wrapT = THREE.RepeatWrapping;
|
||||
let text2 = new THREE.TextureLoader().load(
|
||||
'C:/Users/Utilisateur/Desktop/Gdevelop/GDevelop/GDJS/Runtime/pixi-renderers/tileLava.jpg'
|
||||
);
|
||||
text2.wrapS = THREE.RepeatWrapping;
|
||||
text2.wrapT = THREE.RepeatWrapping;
|
||||
lavaMaterial = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
time: { value: time },
|
||||
fogDensity: { value: 0.001 },
|
||||
fogColor: { value: new THREE.Vector3(0.1, 0.1, 0.1) },
|
||||
texture1: { value: tex1 },
|
||||
texture2: { value: text2 },
|
||||
uvScale: { value: new THREE.Vector2(1, 1) },
|
||||
},
|
||||
vertexShader: `uniform vec2 uvScale;
|
||||
varying vec2 vUv;
|
||||
|
||||
void main()
|
||||
{
|
||||
vUv = uvScale * uv;
|
||||
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}`,
|
||||
fragmentShader: `uniform float time;
|
||||
uniform float fogDensity;
|
||||
uniform vec3 fogColor;
|
||||
uniform sampler2D texture1;
|
||||
uniform sampler2D texture2;
|
||||
|
||||
varying vec2 vUv;
|
||||
|
||||
void main( void ) {
|
||||
vec2 position = -1.0 + 2.0 * vUv;
|
||||
|
||||
vec4 noise = texture2D( texture1, vUv );
|
||||
vec2 T1 = vUv + vec2( 1.5, - 1.5 ) * time * 0.02;
|
||||
vec2 T2 = vUv + vec2( - 0.5, 2.0 ) * time * 0.01;
|
||||
|
||||
T1.x += noise.x * 2.0;
|
||||
T1.y += noise.y * 2.0;
|
||||
T2.x -= noise.y * 0.2;
|
||||
T2.y += noise.z * 0.2;
|
||||
|
||||
float p = texture2D( texture1, T1 * 2.0 ).a;
|
||||
|
||||
vec4 color = texture2D( texture2, T2 * 2.0 );
|
||||
vec4 temp = color * ( vec4( p, p, p, p ) * 2.0 ) + ( color * color - 0.1 );
|
||||
|
||||
if( temp.r > 1.0 ) { temp.bg += clamp( temp.r - 2.0, 0.0, 100.0 ); }
|
||||
if( temp.g > 1.0 ) { temp.rb += temp.g - 1.0; }
|
||||
if( temp.b > 1.0 ) { temp.rg += temp.b - 1.0; }
|
||||
|
||||
gl_FragColor = temp;
|
||||
|
||||
float depth = gl_FragCoord.z / gl_FragCoord.w;
|
||||
const float LOG2 = 1.442695;
|
||||
float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );
|
||||
fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );
|
||||
|
||||
gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );
|
||||
}`,
|
||||
});
|
||||
return lavaMaterial;
|
||||
const material = forceBasicMaterial
|
||||
? new THREE.MeshBasicMaterial({
|
||||
map: this.getThreeTexture(resourceName),
|
||||
side: useTransparentTexture ? THREE.DoubleSide : THREE.FrontSide,
|
||||
transparent: useTransparentTexture,
|
||||
vertexColors,
|
||||
})
|
||||
: new THREE.MeshStandardMaterial({
|
||||
map: this.getThreeTexture(resourceName),
|
||||
side: useTransparentTexture ? THREE.DoubleSide : THREE.FrontSide,
|
||||
transparent: useTransparentTexture,
|
||||
metalness: 0,
|
||||
vertexColors,
|
||||
});
|
||||
this._loadedThreeMaterials.put(cacheKey, material);
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Before Width: | Height: | Size: 545 KiB |
@@ -382,10 +382,8 @@ namespace gdjs {
|
||||
* in milliseconds, for the object.
|
||||
*
|
||||
* Objects can have different elapsed time if they are on layers with different time scales.
|
||||
*
|
||||
* @param instanceContainer The instance container the object belongs to (deprecated - can be omitted).
|
||||
*/
|
||||
getElapsedTime(instanceContainer?: gdjs.RuntimeInstanceContainer): float {
|
||||
getElapsedTime(): float {
|
||||
const theLayer = this._runtimeScene.getLayer(this.layer);
|
||||
return theLayer.getElapsedTime();
|
||||
}
|
||||
@@ -599,11 +597,10 @@ namespace gdjs {
|
||||
* Remove an object from a scene.
|
||||
*
|
||||
* Do not change/redefine this method. Instead, redefine the onDestroyFromScene method.
|
||||
* @param instanceContainer The container owning the object.
|
||||
*/
|
||||
deleteFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
deleteFromScene(): void {
|
||||
if (this._livingOnScene) {
|
||||
instanceContainer.markObjectForDeletion(this);
|
||||
this._runtimeScene.markObjectForDeletion(this);
|
||||
this._livingOnScene = false;
|
||||
}
|
||||
}
|
||||
@@ -620,11 +617,9 @@ namespace gdjs {
|
||||
* Called when the object is destroyed (because it is removed from a scene or the scene
|
||||
* is being unloaded). If you redefine this function, **make sure to call the original method**
|
||||
* (`RuntimeObject.prototype.onDestroyFromScene.call(this, runtimeScene);`).
|
||||
*
|
||||
* @param instanceContainer The container owning the object.
|
||||
*/
|
||||
onDeletedFromScene(instanceContainer: gdjs.RuntimeInstanceContainer): void {
|
||||
const theLayer = instanceContainer.getLayer(this.layer);
|
||||
onDeletedFromScene(): void {
|
||||
const theLayer = this._runtimeScene.getLayer(this.layer);
|
||||
const rendererObject = this.getRendererObject();
|
||||
if (rendererObject) {
|
||||
theLayer.getRenderer().removeRendererObject(rendererObject);
|
||||
@@ -797,12 +792,7 @@ namespace gdjs {
|
||||
return this.getY();
|
||||
}
|
||||
|
||||
rotateTowardPosition(
|
||||
x: float,
|
||||
y: float,
|
||||
speed: float,
|
||||
scene: gdjs.RuntimeScene
|
||||
): void {
|
||||
rotateTowardPosition(x: float, y: float, speed: float): void {
|
||||
this.rotateTowardAngle(
|
||||
gdjs.toDegrees(
|
||||
Math.atan2(
|
||||
@@ -810,21 +800,15 @@ namespace gdjs {
|
||||
x - (this.getDrawableX() + this.getCenterX())
|
||||
)
|
||||
),
|
||||
speed,
|
||||
scene
|
||||
speed
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param angle The targeted direction angle.
|
||||
* @param speed The rotation speed.
|
||||
* @param instanceContainer The container the object belongs to (deprecated - can be omitted).
|
||||
*/
|
||||
rotateTowardAngle(
|
||||
angle: float,
|
||||
speed: float,
|
||||
instanceContainer?: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
rotateTowardAngle(angle: float, speed: float): void {
|
||||
if (speed === 0) {
|
||||
this.setAngle(angle);
|
||||
return;
|
||||
@@ -863,10 +847,7 @@ namespace gdjs {
|
||||
* @param speed The speed, in degrees per second.
|
||||
* @param instanceContainer The container the object belongs to (deprecated - can be omitted).
|
||||
*/
|
||||
rotate(
|
||||
speed: float,
|
||||
instanceContainer?: gdjs.RuntimeInstanceContainer
|
||||
): void {
|
||||
rotate(speed: float): void {
|
||||
this.setAngle(this.getAngle() + (speed * this.getElapsedTime()) / 1000);
|
||||
}
|
||||
|
||||
|
@@ -284,7 +284,7 @@ namespace gdjs {
|
||||
const allInstancesList = this.getAdhocListOfAllInstances();
|
||||
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
|
||||
const object = allInstancesList[i];
|
||||
object.onDeletedFromScene(this);
|
||||
object.onDeletedFromScene();
|
||||
object.onDestroyed();
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,5 @@
|
||||
let time = 0.0;
|
||||
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Scene stack');
|
||||
const debugLogger = new gdjs.Logger('Multiplayer - Debug');
|
||||
|
||||
/**
|
||||
@@ -36,8 +35,6 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
step(elapsedTime: float): boolean {
|
||||
time += this._stack[0].getTimeManager().getElapsedTime() / 100;
|
||||
lavaMaterial.uniforms.time.value = time;
|
||||
this._throwIfDisposed();
|
||||
if (this._isNextLayoutLoading || this._stack.length === 0) {
|
||||
return false;
|
||||
@@ -71,8 +68,10 @@ namespace gdjs {
|
||||
} else if (request === gdjs.SceneChangeRequest.CLEAR_SCENES) {
|
||||
this.replace(currentScene.getRequestedScene(), true);
|
||||
} else {
|
||||
logger.error('Unrecognized change in scene stack: ' + request);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -945,18 +945,16 @@ namespace gdjs {
|
||||
//Other :
|
||||
/**
|
||||
* @param obj The target object
|
||||
* @param scene The scene containing the object
|
||||
* @deprecated
|
||||
*/
|
||||
turnTowardObject(obj: gdjs.RuntimeObject | null, scene: gdjs.RuntimeScene) {
|
||||
turnTowardObject(obj: gdjs.RuntimeObject | null) {
|
||||
if (obj === null) {
|
||||
return;
|
||||
}
|
||||
this.rotateTowardPosition(
|
||||
obj.getDrawableX() + obj.getCenterX(),
|
||||
obj.getDrawableY() + obj.getCenterY(),
|
||||
0,
|
||||
scene
|
||||
0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
3
GDJS/Runtime/types/project-data.d.ts
vendored
@@ -212,6 +212,9 @@ declare interface EventsBasedObjectData
|
||||
name: string;
|
||||
isInnerAreaFollowingParentSize: boolean;
|
||||
variants: Array<EventsBasedObjectVariantData>;
|
||||
/** Added at runtime to have the default variant with an empty name instead
|
||||
* of the events-based object name. */
|
||||
defaultVariant?: EventsBasedObjectVariantData;
|
||||
}
|
||||
|
||||
declare interface EventsBasedObjectVariantData extends InstanceContainerData {
|
||||
|
@@ -1196,6 +1196,7 @@ interface MeasurementUnit {
|
||||
[Const, Ref] MeasurementUnit STATIC_GetPixel();
|
||||
[Const, Ref] MeasurementUnit STATIC_GetPixelSpeed();
|
||||
[Const, Ref] MeasurementUnit STATIC_GetPixelAcceleration();
|
||||
[Const, Ref] MeasurementUnit STATIC_GetAngularSpeed();
|
||||
[Const, Ref] MeasurementUnit STATIC_GetNewton();
|
||||
|
||||
long STATIC_GetDefaultMeasurementUnitsCount();
|
||||
|
@@ -830,6 +830,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
|
||||
#define STATIC_GetPixel GetPixel
|
||||
#define STATIC_GetPixelSpeed GetPixelSpeed
|
||||
#define STATIC_GetPixelAcceleration GetPixelAcceleration
|
||||
#define STATIC_GetAngularSpeed GetAngularSpeed
|
||||
#define STATIC_GetNewton GetNewton
|
||||
#define STATIC_GetDefaultMeasurementUnitsCount GetDefaultMeasurementUnitsCount
|
||||
#define STATIC_GetDefaultMeasurementUnitAtIndex GetDefaultMeasurementUnitAtIndex
|
||||
|
@@ -388,7 +388,8 @@ class VariablesContainer {
|
||||
}
|
||||
|
||||
class RuntimeObject {
|
||||
constructor(runtimeScene, objectData) {
|
||||
constructor(instanceContainer, objectData) {
|
||||
this._runtimeScene = instanceContainer;
|
||||
this.name = objectData.name || '';
|
||||
this._variables = new VariablesContainer(objectData.variables);
|
||||
this._livingOnScene = true;
|
||||
@@ -466,10 +467,9 @@ class RuntimeObject {
|
||||
array.pushValue(value);
|
||||
}
|
||||
|
||||
/** @param {RuntimeScene} runtimeScene */
|
||||
deleteFromScene(runtimeScene) {
|
||||
deleteFromScene() {
|
||||
if (this._livingOnScene) {
|
||||
runtimeScene.markObjectForDeletion(this);
|
||||
this._runtimeScene.markObjectForDeletion(this);
|
||||
this._livingOnScene = false;
|
||||
}
|
||||
}
|
||||
|
@@ -1153,7 +1153,7 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
);
|
||||
|
||||
// Delete an object while the task is running.
|
||||
myObjectA1.deleteFromScene(runtimeScene);
|
||||
myObjectA1.deleteFromScene();
|
||||
|
||||
// Process the tasks (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -1175,8 +1175,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
);
|
||||
|
||||
// Delete other objects while the task is running.
|
||||
myObjectA3.deleteFromScene(runtimeScene);
|
||||
myObjectB1.deleteFromScene(runtimeScene);
|
||||
myObjectA3.deleteFromScene();
|
||||
myObjectB1.deleteFromScene();
|
||||
|
||||
// Process the tasks again (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -1291,7 +1291,7 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
);
|
||||
|
||||
// Delete an object while the task is running.
|
||||
myObjectA1.deleteFromScene(runtimeScene);
|
||||
myObjectA1.deleteFromScene();
|
||||
|
||||
// Process the tasks (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -1315,8 +1315,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
);
|
||||
|
||||
// Delete other objects while the task is running.
|
||||
myObjectA3.deleteFromScene(runtimeScene);
|
||||
myObjectB1.deleteFromScene(runtimeScene);
|
||||
myObjectA3.deleteFromScene();
|
||||
myObjectB1.deleteFromScene();
|
||||
|
||||
// Process the tasks again (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -1632,7 +1632,7 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
);
|
||||
|
||||
// Delete an object while the task is running.
|
||||
myObjectA1.deleteFromScene(runtimeScene);
|
||||
myObjectA1.deleteFromScene();
|
||||
|
||||
// Process the tasks (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -1656,8 +1656,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
);
|
||||
|
||||
// Delete other objects while the task is running.
|
||||
myObjectA3.deleteFromScene(runtimeScene);
|
||||
myObjectB1.deleteFromScene(runtimeScene);
|
||||
myObjectA3.deleteFromScene();
|
||||
myObjectB1.deleteFromScene();
|
||||
|
||||
// Process the tasks again (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -2132,8 +2132,8 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
const newMyObjectA = runtimeScene.getObjects('MyObjectA')[3];
|
||||
|
||||
// Delete some objects while the second Wait task is running.
|
||||
myObjectA1.deleteFromScene(runtimeScene);
|
||||
myObjectB1.deleteFromScene(runtimeScene);
|
||||
myObjectA1.deleteFromScene();
|
||||
myObjectB1.deleteFromScene();
|
||||
|
||||
// Process the tasks again (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
@@ -2285,9 +2285,9 @@ describe('libGD.js - GDJS Async Code Generation integration tests', function ()
|
||||
const newMyObjectA2 = runtimeScene.getObjects('MyObjectA')[4];
|
||||
|
||||
// Delete some objects while the second Wait task is running.
|
||||
myObjectA1.deleteFromScene(runtimeScene);
|
||||
myObjectB1.deleteFromScene(runtimeScene);
|
||||
newMyObjectA1.deleteFromScene(runtimeScene);
|
||||
myObjectA1.deleteFromScene();
|
||||
myObjectB1.deleteFromScene();
|
||||
newMyObjectA1.deleteFromScene();
|
||||
|
||||
// Process the tasks again (after faking it's finished).
|
||||
runtimeScene.getAsyncTasksManager().markAllFakeAsyncTasksAsFinished();
|
||||
|
1
GDevelop.js/types.d.ts
vendored
@@ -987,6 +987,7 @@ export class MeasurementUnit extends EmscriptenObject {
|
||||
static getPixel(): MeasurementUnit;
|
||||
static getPixelSpeed(): MeasurementUnit;
|
||||
static getPixelAcceleration(): MeasurementUnit;
|
||||
static getAngularSpeed(): MeasurementUnit;
|
||||
static getNewton(): MeasurementUnit;
|
||||
static getDefaultMeasurementUnitsCount(): number;
|
||||
static getDefaultMeasurementUnitAtIndex(index: number): MeasurementUnit;
|
||||
|
@@ -17,6 +17,7 @@ declare class gdMeasurementUnit {
|
||||
static getPixel(): gdMeasurementUnit;
|
||||
static getPixelSpeed(): gdMeasurementUnit;
|
||||
static getPixelAcceleration(): gdMeasurementUnit;
|
||||
static getAngularSpeed(): gdMeasurementUnit;
|
||||
static getNewton(): gdMeasurementUnit;
|
||||
static getDefaultMeasurementUnitsCount(): number;
|
||||
static getDefaultMeasurementUnitAtIndex(index: number): gdMeasurementUnit;
|
||||
|
60
appveyor.yml
@@ -1,17 +1,15 @@
|
||||
# AppVeyor configuration to build GDevelop app running
|
||||
# Deprecated AppVeyor configuration to build GDevelop app running
|
||||
# on the Electron runtime (newIDE/electron-app) for Windows.
|
||||
# For macOS and Linux, see the config.yml file.
|
||||
#
|
||||
# This was replaced by build on CircleCI - but kept for redundancy/tests.
|
||||
# For Windows, macOS and Linux builds, see the config.yml file.
|
||||
|
||||
version: 1.0.{build}
|
||||
pull_requests:
|
||||
do_not_increment_build_number: true
|
||||
image: Visual Studio 2019
|
||||
clone_depth: 5
|
||||
# Only build
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /experimental-build.*/
|
||||
# Build must be triggered by the API.
|
||||
skip_tags: true # Don't rebuild on tags.
|
||||
init:
|
||||
- ps: Install-Product node 16
|
||||
@@ -21,13 +19,14 @@ cache:
|
||||
- newIDE\app\node_modules -> newIDE\app\package-lock.json
|
||||
- newIDE\electron-app\node_modules -> newIDE\electron-app\package-lock.json
|
||||
- GDevelop.js\node_modules -> GDevelop.js\package-lock.json
|
||||
- GDJS\node_modules -> GDJS\package-lock.json
|
||||
install:
|
||||
# Download and install SSL.com eSigner CKA.
|
||||
# Download and install SSL.com eSigner CKA.
|
||||
# See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/.
|
||||
#
|
||||
# This is necessary because of "signing to be FIPS-140 compliant". See
|
||||
# This is necessary because of "signing to be FIPS-140 compliant". See
|
||||
# https://github.com/electron-userland/electron-builder/issues/6158
|
||||
#
|
||||
#
|
||||
# Make sure to DISABLE "malware blocker" in SSL.com to avoid errors like:
|
||||
# Error information: "Error: SignerSign() failed." (-2146893821/0x80090003)
|
||||
- ps: >-
|
||||
@@ -42,13 +41,13 @@ install:
|
||||
Remove-Item eSigner_CKA_Setup.zip
|
||||
|
||||
Move-Item -Destination "eSigner_CKA_Installer.exe" -Path "eSigner_CKA_*\*.exe"
|
||||
|
||||
|
||||
# Install it. See https://www.ssl.com/how-to/how-to-integrate-esigner-cka-with-ci-cd-tools-for-automated-code-signing/
|
||||
|
||||
New-Item -ItemType Directory -Force -Path "C:\projects\gdevelop\eSignerCKA"
|
||||
|
||||
./eSigner_CKA_Installer.exe /CURRENTUSER /VERYSILENT /SUPPRESSMSGBOXES /DIR="C:\projects\gdevelop\eSignerCKA" | Out-Null
|
||||
|
||||
|
||||
# Disable logger.
|
||||
|
||||
# $LogConfig = Get-Content -Path C:\projects\gdevelop\eSignerCKA/log4net.config
|
||||
@@ -57,23 +56,6 @@ install:
|
||||
|
||||
# $LogConfig | Set-Content -Path C:\projects\gdevelop\eSignerCKA/log4net.config
|
||||
|
||||
# Build GDevelop.js (and run tests to ensure it works).
|
||||
# (in a subshell to avoid Emscripten polluting the Node.js and npm version for the rest of the build)
|
||||
- cmd: >-
|
||||
cd GDevelop.js
|
||||
|
||||
npm -v && npm install
|
||||
|
||||
git clone https://github.com/juj/emsdk.git
|
||||
|
||||
cd emsdk
|
||||
|
||||
emsdk install 3.1.21
|
||||
|
||||
CMD /C "emsdk activate 3.1.21 && cd .. && npm run build"
|
||||
|
||||
cd ..\..
|
||||
|
||||
# Build GDevelop IDE.
|
||||
# Also install setuptools as something requires distutils in electron-app, and it was removed in Python 3.12.
|
||||
# setuptools will make distutils available again (but we should migrate our packages probably).
|
||||
@@ -111,9 +93,8 @@ build_script:
|
||||
echo Certificate: $CodeSigningCert
|
||||
|
||||
# Use a custom signtool path because of the signtool.exe bundled withy electron-builder not working for some reason.
|
||||
# Can also be found in versioned folders like "C:/Program Files (x86)/Windows Kits/10/bin/10.0.22000.0/x86/signtool.exe".
|
||||
|
||||
$Env:SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe"
|
||||
$Env:SIGNTOOL_PATH = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x86\signtool.exe"
|
||||
|
||||
# Extract thumbprint and subject name of the certificate (will be passed to electron-builder).
|
||||
|
||||
@@ -142,12 +123,6 @@ build_script:
|
||||
# Run a few tests on Windows.
|
||||
test_script:
|
||||
- cmd: >-
|
||||
cd GDevelop.js
|
||||
|
||||
npm test
|
||||
|
||||
cd ..
|
||||
|
||||
cd newIDE\app
|
||||
|
||||
npm test
|
||||
@@ -159,8 +134,9 @@ artifacts:
|
||||
name: GDevelopWindows
|
||||
|
||||
# Upload artifacts (AWS) - configuration is stored on AppVeyor itself.
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: Amazon S3 releases
|
||||
- provider: Environment
|
||||
name: Amazon S3 latest releases
|
||||
# Disabled because done by CircleCI "build-windows" job.
|
||||
# deploy:
|
||||
# - provider: Environment
|
||||
# name: Amazon S3 releases
|
||||
# - provider: Environment
|
||||
# name: Amazon S3 latest releases
|
||||
|
Before Width: | Height: | Size: 779 KiB After Width: | Height: | Size: 819 KiB |
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="14" height="14" version="1.1" viewBox="0 0 14 14" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="m7 0-6 3.5v7l6 3.5 6-3.5v-7z" fill="#55b1e0" style="paint-order:normal"/><path d="m7.001 0.289 5.75 3.355v6.711l-5.75 3.355-5.75-3.355v-6.711z" fill="#2e388a" style="paint-order:normal"/><path d="m9.595 4.129c-0.075-0.2212-0.285-0.3788-0.5325-0.3788h-4.125c-0.2475 0-0.4538 0.1575-0.5325 0.3788l-0.78 2.246v3c0 0.2062 0.1688 0.375 0.375 0.375h0.375c0.2062 0 0.375-0.1688 0.375-0.375v-0.375h4.5v0.375c0 0.2062 0.1688 0.375 0.375 0.375h0.375c0.2062 0 0.375-0.1688 0.375-0.375v-3zm-4.658 3.746c-0.3112 0-0.5625-0.2512-0.5625-0.5625s0.2512-0.5625 0.5625-0.5625 0.5625 0.2512 0.5625 0.5625-0.2512 0.5625-0.5625 0.5625zm4.125 0c-0.3112 0-0.5625-0.2512-0.5625-0.5625s0.2512-0.5625 0.5625-0.5625 0.5625 0.2512 0.5625 0.5625-0.2512 0.5625-0.5625 0.5625zm-4.688-1.875 0.5625-1.688h4.125l0.5625 1.688z" fill="#55b1e0" stroke-width=".375"/></svg>
|
After Width: | Height: | Size: 1001 B |
BIN
newIDE/app/public/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 29 KiB |
BIN
newIDE/app/public/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 185 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 26 KiB |
BIN
newIDE/app/public/favicon-16x16.png
Normal file
After Width: | Height: | Size: 777 B |
Before Width: | Height: | Size: 9.3 KiB |
BIN
newIDE/app/public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 15 KiB |
@@ -3,8 +3,9 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon-256.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png">
|
||||
<title>GDevelop 5</title>
|
||||
|
||||
<meta name="title" content="GDevelop game making app" />
|
||||
|
@@ -3,24 +3,14 @@
|
||||
"name": "GDevelop",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon-512.png",
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "favicon-256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "apple-touch-icon.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"screenshots": [
|
||||
|
@@ -158,13 +158,18 @@ ${extension.getDescription()} ${generateReadMoreLink(extension.getHelpPath())}
|
||||
};
|
||||
};
|
||||
|
||||
/** @returns {String} */
|
||||
const generateBuiltInExtensionNote = ({ extension }) => {
|
||||
return `The ${extension.getFullName()} extension is always installed in all GDevelop projects: there is no need to add it from the Project Manager.\n\n`;
|
||||
};
|
||||
|
||||
/** @returns {RawText} */
|
||||
const generateExtensionFooterText = ({ extension }) => {
|
||||
return {
|
||||
text:
|
||||
`
|
||||
---
|
||||
*This page is an auto-generated reference page about the **${extension.getFullName()}** feature of [GDevelop, the open-source, cross-platform game engine designed for everyone](https://gdevelop.io/).*` +
|
||||
`\n\n---\n\n` +
|
||||
generateBuiltInExtensionNote({ extension }) +
|
||||
`*This page is an auto-generated reference page about the **${extension.getFullName()}** feature of [GDevelop, the open-source, cross-platform game engine designed for everyone](https://gdevelop.io/).*` +
|
||||
' ' +
|
||||
'Learn more about [all GDevelop features here](/gdevelop5/all-features).',
|
||||
};
|
||||
|
@@ -63,6 +63,19 @@ if (shell.test('-f', path.join(sourceDirectory, 'libGD.js'))) {
|
||||
var hash = (hashShellString.stdout || 'unknown-hash').trim();
|
||||
var branch = (branchShellString.stdout || 'unknown-branch').trim();
|
||||
|
||||
if (branch === 'HEAD') {
|
||||
// We're in detached HEAD. Try to read the branch from the CI environment variables.
|
||||
if (process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH) {
|
||||
branch = process.env.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH;
|
||||
} else if (process.env.APPVEYOR_REPO_BRANCH) {
|
||||
branch = process.env.APPVEYOR_REPO_BRANCH;
|
||||
} else {
|
||||
shell.echo(
|
||||
`⚠️ Can't find the branch of the associated commit - if you're in detached HEAD, you need to be on a branch instead.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
resolve(
|
||||
downloadLibGdJs(
|
||||
`https://s3.amazonaws.com/gdevelop-gdevelop.js/${branch}/commit/${hash}`
|
||||
@@ -129,7 +142,20 @@ if (shell.test('-f', path.join(sourceDirectory, 'libGD.js'))) {
|
||||
|
||||
// Try to download the latest libGD.js, fallback to previous or master ones
|
||||
// if not found (including different parents, for handling of merge commits).
|
||||
downloadCommitLibGdJs('HEAD').then(onLibGdJsDownloaded, () =>
|
||||
downloadCommitLibGdJs('HEAD').then(onLibGdJsDownloaded, () => {
|
||||
// Force the exact version of GDevelop.js to be downloaded for AppVeyor - because
|
||||
// this means we build the app and we don't want to risk mismatch (Core C++ not up to date
|
||||
// with the IDE JavaScript).
|
||||
if (process.env.APPVEYOR || process.env.REQUIRES_EXACT_LIBGD_JS_VERSION) {
|
||||
shell.echo(
|
||||
`❌ Can't download the exact required version of libGD.js - check it was built by CircleCI before running this CI.`
|
||||
);
|
||||
shell.echo(
|
||||
`ℹ️ See the pipeline on https://app.circleci.com/pipelines/github/4ian/GDevelop.`
|
||||
);
|
||||
shell.exit(1);
|
||||
}
|
||||
|
||||
downloadCommitLibGdJs('HEAD~1').then(onLibGdJsDownloaded, () =>
|
||||
downloadCommitLibGdJs('HEAD~2').then(onLibGdJsDownloaded, () =>
|
||||
downloadCommitLibGdJs('HEAD~3').then(onLibGdJsDownloaded, () =>
|
||||
@@ -150,6 +176,6 @@ if (shell.test('-f', path.join(sourceDirectory, 'libGD.js'))) {
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@@ -18,6 +18,7 @@ import {
|
||||
installRequiredExtensions,
|
||||
installPublicAsset,
|
||||
type RequiredExtensionInstallation,
|
||||
complyVariantsToEventsBasedObjectOf,
|
||||
} from './InstallAsset';
|
||||
import EventsFunctionsExtensionsContext from '../EventsFunctionsExtensionsLoader/EventsFunctionsExtensionsContext';
|
||||
import { showErrorBox } from '../UI/Messages/MessageBox';
|
||||
@@ -226,6 +227,7 @@ const AssetPackInstallDialog = ({
|
||||
const createdObjects = results
|
||||
.map(result => result.createdObjects)
|
||||
.flat();
|
||||
complyVariantsToEventsBasedObjectOf(project, createdObjects);
|
||||
onAssetsAdded(createdObjects);
|
||||
} catch (error) {
|
||||
setAreAssetsBeingInstalled(false);
|
||||
|
@@ -600,3 +600,21 @@ export const checkRequiredExtensionsUpdateForAssets = async ({
|
||||
|
||||
return checkRequiredExtensionsUpdate({ requiredExtensions, project });
|
||||
};
|
||||
|
||||
export const complyVariantsToEventsBasedObjectOf = (
|
||||
project: gdProject,
|
||||
createdObjects: Array<gdObject>
|
||||
) => {
|
||||
const installedVariantObjectTypes = new Set<string>();
|
||||
for (const createdObject of createdObjects) {
|
||||
if (project.hasEventsBasedObject(createdObject.getType())) {
|
||||
installedVariantObjectTypes.add(createdObject.getType());
|
||||
}
|
||||
}
|
||||
for (const installedVariantObjectType of installedVariantObjectTypes) {
|
||||
gd.EventsBasedObjectVariantHelper.complyVariantsToEventsBasedObject(
|
||||
project,
|
||||
project.getEventsBasedObject(installedVariantObjectType)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@@ -20,6 +20,7 @@ import {
|
||||
checkRequiredExtensionsUpdate,
|
||||
checkRequiredExtensionsUpdateForAssets,
|
||||
type InstallAssetOutput,
|
||||
complyVariantsToEventsBasedObjectOf,
|
||||
} from './InstallAsset';
|
||||
import {
|
||||
type Asset,
|
||||
@@ -217,6 +218,10 @@ export const useInstallAsset = ({
|
||||
openedAssetPack && openedAssetPack.id ? openedAssetPack.id : null,
|
||||
assetPackKind: isPrivate ? 'private' : 'public',
|
||||
});
|
||||
complyVariantsToEventsBasedObjectOf(
|
||||
project,
|
||||
installOutput.createdObjects
|
||||
);
|
||||
|
||||
await resourceManagementProps.onFetchNewlyAddedResources();
|
||||
return installOutput;
|
||||
|
@@ -17,7 +17,7 @@ const gd: libGDevelop = global.gd;
|
||||
|
||||
type Props = BehaviorEditorProps;
|
||||
|
||||
const areAdvancedPropertiesModified = (behavior: gdBehavior) => {
|
||||
export const areAdvancedPropertiesModified = (behavior: gdBehavior) => {
|
||||
const behaviorMetadata = gd.MetadataProvider.getBehaviorMetadata(
|
||||
gd.JsPlatform.get(),
|
||||
behavior.getTypeName()
|
||||
|
@@ -16,6 +16,12 @@ import useForceUpdate from '../../../Utils/UseForceUpdate';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import ButtonGroup from '@material-ui/core/ButtonGroup';
|
||||
import { NumericProperty, UnitAdornment } from '../Physics2Editor';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionHeader,
|
||||
AccordionBody,
|
||||
} from '../../../UI/Accordion';
|
||||
import { areAdvancedPropertiesModified } from '../BehaviorPropertiesEditor';
|
||||
|
||||
type Props = BehaviorEditorProps;
|
||||
|
||||
@@ -57,6 +63,11 @@ const Physics3DEditor = (props: Props) => {
|
||||
const { behavior, onBehaviorUpdated } = props;
|
||||
const forceUpdate = useForceUpdate();
|
||||
|
||||
const areAdvancedPropertiesExpandedByDefault = React.useMemo(
|
||||
() => areAdvancedPropertiesModified(behavior),
|
||||
[behavior]
|
||||
);
|
||||
|
||||
const updateBehaviorProperty = React.useCallback(
|
||||
(property, value) => {
|
||||
behavior.updateProperty(property, value);
|
||||
@@ -244,6 +255,18 @@ const Physics3DEditor = (props: Props) => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
id="physics3d-parameter-mass-override"
|
||||
properties={properties}
|
||||
propertyName={'massOverride'}
|
||||
step={0.1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty(
|
||||
'massOverride',
|
||||
parseFloat(newValue) > 0 ? newValue : '0'
|
||||
)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'gravityScale'}
|
||||
@@ -356,6 +379,72 @@ const Physics3DEditor = (props: Props) => {
|
||||
disabled={isStatic}
|
||||
/>
|
||||
</Line>
|
||||
<Accordion
|
||||
defaultExpanded={areAdvancedPropertiesExpandedByDefault}
|
||||
noMargin
|
||||
>
|
||||
<AccordionHeader noMargin>
|
||||
<Text size="sub-title">
|
||||
<Trans>Advanced properties</Trans>
|
||||
</Text>
|
||||
</AccordionHeader>
|
||||
<AccordionBody disableGutters>
|
||||
<Column expand noMargin>
|
||||
<ResponsiveLineStackLayout>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'shapeOffsetX'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('shapeOffsetX', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'shapeOffsetY'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('shapeOffsetY', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'shapeOffsetZ'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('shapeOffsetZ', newValue)
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
<ResponsiveLineStackLayout>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'massCenterOffsetX'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('massCenterOffsetX', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'massCenterOffsetY'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('massCenterOffsetY', newValue)
|
||||
}
|
||||
/>
|
||||
<NumericProperty
|
||||
properties={properties}
|
||||
propertyName={'massCenterOffsetZ'}
|
||||
step={1}
|
||||
onUpdate={newValue =>
|
||||
updateBehaviorProperty('massCenterOffsetZ', newValue)
|
||||
}
|
||||
/>
|
||||
</ResponsiveLineStackLayout>
|
||||
</Column>
|
||||
</AccordionBody>
|
||||
</Accordion>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
@@ -1,55 +1,15 @@
|
||||
// @flow
|
||||
|
||||
type WatchedComponent = {
|
||||
+onHeightsChanged: Function => void,
|
||||
};
|
||||
|
||||
/**
|
||||
* Store the height of events and notify a component whenever
|
||||
* heights have changed.
|
||||
* Needed for EventsTree as we need to tell it when heights have changed
|
||||
* so it can recompute the internal row heights of the react-virtualized List.
|
||||
* Store the height of events.
|
||||
* Needed for EventsTree as we need to tell the react-virtualized List
|
||||
* the size of each event - which we only know after the event has been rendered.
|
||||
*/
|
||||
export default class EventHeightsCache {
|
||||
eventHeights = {};
|
||||
updateTimeoutId: ?TimeoutID = null;
|
||||
component: ?WatchedComponent = null;
|
||||
|
||||
constructor(component: WatchedComponent) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
_notifyComponent() {
|
||||
if (this.updateTimeoutId) {
|
||||
return; // An update is already scheduled.
|
||||
}
|
||||
|
||||
// Notify the component, on the next tick, that heights have changed
|
||||
this.updateTimeoutId = setTimeout(() => {
|
||||
if (this.component) {
|
||||
this.component.onHeightsChanged(() => (this.updateTimeoutId = null));
|
||||
} else {
|
||||
this.updateTimeoutId = null;
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
setEventHeight(event: gdBaseEvent, height: number) {
|
||||
if (height === 0) {
|
||||
// Don't store the new height because it's 0, meaning a new rendering later
|
||||
// will then render the proper height. In the meantime, we don't want to
|
||||
// store this empty rendering BUT we still need to notify the parent that
|
||||
// an update is needed.
|
||||
this._notifyComponent();
|
||||
return;
|
||||
}
|
||||
|
||||
const cachedHeight = this.eventHeights[event.ptr];
|
||||
if (cachedHeight === undefined || cachedHeight !== height) {
|
||||
// Notify the parent component that a height changed.
|
||||
this._notifyComponent();
|
||||
}
|
||||
|
||||
this.eventHeights[event.ptr] = height;
|
||||
}
|
||||
|
||||
|
@@ -70,6 +70,9 @@ export default class CommentEvent extends React.Component<
|
||||
if (this._textField) {
|
||||
this._textField.focus({ caretPosition: 'end' });
|
||||
}
|
||||
// Wait for the change to be applied on the DOM before calling onUpdate,
|
||||
// so that the height of the event is updated.
|
||||
this.props.onUpdate();
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -78,8 +81,11 @@ export default class CommentEvent extends React.Component<
|
||||
const commentEvent = gd.asCommentEvent(this.props.event);
|
||||
commentEvent.setComment(text);
|
||||
|
||||
this.props.onUpdate();
|
||||
this.forceUpdate();
|
||||
this.forceUpdate(() => {
|
||||
// Wait for the change to be applied on the DOM before calling onUpdate,
|
||||
// so that the height of the event is updated.
|
||||
this.props.onUpdate();
|
||||
});
|
||||
};
|
||||
|
||||
endEditing = () => {
|
||||
|
@@ -4,7 +4,7 @@ import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import * as React from 'react';
|
||||
import EventsTree from './EventsTree';
|
||||
import EventsTree, { type EventsTreeInterface } from './EventsTree';
|
||||
import { getInstructionMetadata } from './InstructionEditor/InstructionEditor';
|
||||
import InstructionEditorDialog from './InstructionEditor/InstructionEditorDialog';
|
||||
import InstructionEditorMenu from './InstructionEditor/InstructionEditorMenu';
|
||||
@@ -232,7 +232,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
ComponentProps,
|
||||
State
|
||||
> {
|
||||
_eventsTree: ?EventsTree;
|
||||
_eventsTree: ?EventsTreeInterface;
|
||||
_eventSearcher: ?EventsSearcher;
|
||||
_searchPanel: ?SearchPanelInterface;
|
||||
_containerDiv = React.createRef<HTMLDivElement>();
|
||||
@@ -558,16 +558,16 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
insertion.indexInList + 1
|
||||
);
|
||||
|
||||
const currentTree = this._eventsTree;
|
||||
if (currentTree) {
|
||||
currentTree.forceEventsUpdate(() => {
|
||||
const eventsTree = this._eventsTree;
|
||||
if (eventsTree) {
|
||||
eventsTree.forceEventsUpdate(() => {
|
||||
const positions = this._getChangedEventRows([newEvent]);
|
||||
this._saveChangesToHistory(
|
||||
'ADD',
|
||||
{ positionsBeforeAction: positions, positionAfterAction: positions },
|
||||
() => {
|
||||
if (!context && !selectedEventContext) {
|
||||
currentTree.scrollToRow(currentTree.getEventRow(newEvent));
|
||||
eventsTree.scrollToRow(eventsTree.getEventRow(newEvent));
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -580,7 +580,7 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
(type === 'BuiltinCommonInstructions::Comment' ||
|
||||
type === 'BuiltinCommonInstructions::Group')
|
||||
) {
|
||||
const rowIndex = currentTree.getEventRow(newEvent);
|
||||
const rowIndex = eventsTree.getEventRow(newEvent);
|
||||
const clickableElement = document.querySelector(
|
||||
`[data-row-index="${rowIndex}"] [data-editable-text="true"]`
|
||||
);
|
||||
@@ -1372,9 +1372,9 @@ export class EventsSheetComponentWithoutHandle extends React.Component<
|
||||
};
|
||||
|
||||
_getChangedEventRows = (events: Array<gdBaseEvent>) => {
|
||||
const currentTree = this._eventsTree;
|
||||
if (currentTree) {
|
||||
return events.map(event => currentTree.getEventRow(event));
|
||||
const eventsTree = this._eventsTree;
|
||||
if (eventsTree) {
|
||||
return events.map(event => eventsTree.getEventRow(event));
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
import { roundPositionsToGrid } from '../Utils/GridHelpers';
|
||||
import { unserializeFromJSObject } from '../Utils/Serializer';
|
||||
import { type InstancesEditorSettings } from './InstancesEditorSettings';
|
||||
const gd: libGDevelop = global.gd;
|
||||
@@ -9,29 +9,6 @@ type Props = {|
|
||||
instancesEditorSettings: InstancesEditorSettings,
|
||||
|};
|
||||
|
||||
const roundPositionsToGrid = (
|
||||
pos: [number, number],
|
||||
instancesEditorSettings: InstancesEditorSettings
|
||||
): [number, number] => {
|
||||
const newPos = pos;
|
||||
|
||||
if (instancesEditorSettings.grid && instancesEditorSettings.snap) {
|
||||
roundPosition(
|
||||
newPos,
|
||||
instancesEditorSettings.gridWidth,
|
||||
instancesEditorSettings.gridHeight,
|
||||
instancesEditorSettings.gridOffsetX,
|
||||
instancesEditorSettings.gridOffsetY,
|
||||
instancesEditorSettings.gridType
|
||||
);
|
||||
} else {
|
||||
newPos[0] = Math.round(newPos[0]);
|
||||
newPos[1] = Math.round(newPos[1]);
|
||||
}
|
||||
|
||||
return newPos;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allow to add instances on the scene. Supports "temporary" instances,
|
||||
* which are real instances but can be deleted as long as they are not "committed".
|
||||
@@ -56,14 +33,12 @@ export default class InstancesAdder {
|
||||
position,
|
||||
copyReferential,
|
||||
serializedInstances,
|
||||
preventSnapToGrid = false,
|
||||
addInstancesInTheForeground = false,
|
||||
doesObjectExistInContext,
|
||||
}: {|
|
||||
position: [number, number],
|
||||
copyReferential: [number, number],
|
||||
serializedInstances: Array<Object>,
|
||||
preventSnapToGrid?: boolean,
|
||||
addInstancesInTheForeground?: boolean,
|
||||
doesObjectExistInContext: string => boolean,
|
||||
|}): Array<gdInitialInstance> => {
|
||||
@@ -78,18 +53,8 @@ export default class InstancesAdder {
|
||||
const instance = new gd.InitialInstance();
|
||||
unserializeFromJSObject(instance, serializedInstance);
|
||||
if (!doesObjectExistInContext(instance.getObjectName())) return null;
|
||||
const desiredPosition = [
|
||||
instance.getX() - copyReferential[0] + position[0],
|
||||
instance.getY() - copyReferential[1] + position[1],
|
||||
];
|
||||
const newPos = preventSnapToGrid
|
||||
? desiredPosition
|
||||
: roundPositionsToGrid(
|
||||
desiredPosition,
|
||||
this._instancesEditorSettings
|
||||
);
|
||||
instance.setX(newPos[0]);
|
||||
instance.setY(newPos[1]);
|
||||
instance.setX(instance.getX() - copyReferential[0] + position[0]);
|
||||
instance.setY(instance.getY() - copyReferential[1] + position[1]);
|
||||
if (addInstancesInTheForeground) {
|
||||
if (
|
||||
addedInstancesLowestZOrder === null ||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// @flow
|
||||
import { roundPosition } from '../Utils/GridHelpers';
|
||||
import { roundPositionsToGrid } from '../Utils/GridHelpers';
|
||||
import Rectangle from '../Utils/Rectangle';
|
||||
import { type InstancesEditorSettings } from './InstancesEditorSettings';
|
||||
import { type InstanceMeasurer } from './InstancesRenderer';
|
||||
@@ -106,30 +106,15 @@ export default class InstancesMover {
|
||||
const magnetPosition = this._temporaryPoint;
|
||||
magnetPosition[0] = initialMagnetX + this.totalDeltaX;
|
||||
magnetPosition[1] = initialMagnetY + this.totalDeltaY;
|
||||
if (
|
||||
this.instancesEditorSettings.snap &&
|
||||
this.instancesEditorSettings.grid &&
|
||||
!noGridSnap
|
||||
) {
|
||||
roundPosition(
|
||||
magnetPosition,
|
||||
this.instancesEditorSettings.gridWidth,
|
||||
this.instancesEditorSettings.gridHeight,
|
||||
this.instancesEditorSettings.gridOffsetX,
|
||||
this.instancesEditorSettings.gridOffsetY,
|
||||
this.instancesEditorSettings.gridType
|
||||
);
|
||||
} else {
|
||||
// Without a grid, the position is still rounded to the nearest pixel.
|
||||
// The size of the instance (or selection of instances) might not be round,
|
||||
// so the magnet corner is still relevant.
|
||||
magnetPosition[0] = Math.round(magnetPosition[0]);
|
||||
magnetPosition[1] = Math.round(magnetPosition[1]);
|
||||
}
|
||||
roundPositionsToGrid(
|
||||
magnetPosition,
|
||||
this.instancesEditorSettings,
|
||||
noGridSnap
|
||||
);
|
||||
const roundedTotalDeltaX = magnetPosition[0] - initialMagnetX;
|
||||
const roundedTotalDeltaY = magnetPosition[1] - initialMagnetY;
|
||||
|
||||
for (var i = 0; i < nonLockedInstances.length; i++) {
|
||||
for (let i = 0; i < nonLockedInstances.length; i++) {
|
||||
const selectedInstance = nonLockedInstances[i];
|
||||
|
||||
let initialPosition = this.instancePositions[selectedInstance.ptr];
|
||||
@@ -170,6 +155,16 @@ export default class InstancesMover {
|
||||
this.totalDeltaY = 0;
|
||||
}
|
||||
|
||||
snapSelection(instances: gdInitialInstance[]): void {
|
||||
// The snapping doesn't work well on 3D model objects because their default
|
||||
// dimensions and origin change when the model is loaded from an async call.
|
||||
this.endMove();
|
||||
// Force magnet from selection top left corner.
|
||||
this.startMove(Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY);
|
||||
this.moveBy(instances, 0, 0, false, false);
|
||||
this.endMove();
|
||||
}
|
||||
|
||||
isMoving() {
|
||||
return !!this._initialSelectionAABB;
|
||||
}
|
||||
|
@@ -770,13 +770,16 @@ export default class InstancesEditor extends Component<Props, State> {
|
||||
position: [number, number],
|
||||
copyReferential: [number, number],
|
||||
serializedInstances: Array<Object>,
|
||||
preventSnapToGrid?: boolean,
|
||||
addInstancesInTheForeground?: boolean,
|
||||
doesObjectExistInContext: string => boolean,
|
||||
|}): Array<gdInitialInstance> => {
|
||||
return this._instancesAdder.addSerializedInstances(options);
|
||||
};
|
||||
|
||||
snapSelection = (instances: gdInitialInstance[]) => {
|
||||
this.instancesMover.snapSelection(instances);
|
||||
};
|
||||
|
||||
/**
|
||||
* Immediately add instances for the specified objects at the given
|
||||
* position (in scene coordinates) given their names.
|
||||
|
@@ -191,6 +191,16 @@ const jsExtensions = [
|
||||
},
|
||||
];
|
||||
|
||||
const getExpectedNumberOfJSExtensionModules = ({
|
||||
filterExamples,
|
||||
}: {|
|
||||
filterExamples: boolean,
|
||||
|}): number => {
|
||||
return jsExtensions.filter(
|
||||
({ name }) => !filterExamples || !name.includes('Example')
|
||||
).length;
|
||||
};
|
||||
|
||||
type MakeExtensionsLoaderArguments = {|
|
||||
objectsEditorService: typeof ObjectsEditorService,
|
||||
objectsRenderingService: typeof ObjectsRenderingService,
|
||||
@@ -210,52 +220,57 @@ export default function makeExtensionsLoader({
|
||||
return {
|
||||
loadAllExtensions(
|
||||
_: TranslationFunction
|
||||
): Promise<
|
||||
Array<{ extensionModulePath: string, result: ExtensionLoadingResult }>
|
||||
> {
|
||||
return Promise.resolve(
|
||||
jsExtensions
|
||||
.filter(({ name }) => !filterExamples || !name.includes('Example'))
|
||||
.map(({ name, extensionModule, objectsRenderingServiceModules }) => {
|
||||
if (
|
||||
objectsEditorService &&
|
||||
extensionModule.registerEditorConfigurations
|
||||
) {
|
||||
extensionModule.registerEditorConfigurations(
|
||||
objectsEditorService
|
||||
);
|
||||
}
|
||||
): Promise<{|
|
||||
results: Array<{|
|
||||
extensionModulePath: string,
|
||||
result: ExtensionLoadingResult,
|
||||
|}>,
|
||||
expectedNumberOfJSExtensionModulesLoaded: number,
|
||||
|}> {
|
||||
const results = jsExtensions
|
||||
.filter(({ name }) => !filterExamples || !name.includes('Example'))
|
||||
.map(({ name, extensionModule, objectsRenderingServiceModules }) => {
|
||||
if (
|
||||
objectsEditorService &&
|
||||
extensionModule.registerEditorConfigurations
|
||||
) {
|
||||
extensionModule.registerEditorConfigurations(objectsEditorService);
|
||||
}
|
||||
|
||||
if (objectsRenderingService) {
|
||||
if (objectsRenderingServiceModules) {
|
||||
for (const requirePath in objectsRenderingServiceModules) {
|
||||
objectsRenderingService.registerModule(
|
||||
requirePath,
|
||||
objectsRenderingServiceModules[requirePath]
|
||||
);
|
||||
}
|
||||
}
|
||||
if (extensionModule.registerInstanceRenderers) {
|
||||
extensionModule.registerInstanceRenderers(
|
||||
objectsRenderingService
|
||||
if (objectsRenderingService) {
|
||||
if (objectsRenderingServiceModules) {
|
||||
for (const requirePath in objectsRenderingServiceModules) {
|
||||
objectsRenderingService.registerModule(
|
||||
requirePath,
|
||||
objectsRenderingServiceModules[requirePath]
|
||||
);
|
||||
}
|
||||
if (extensionModule.registerClearCache) {
|
||||
extensionModule.registerClearCache(objectsRenderingService);
|
||||
}
|
||||
}
|
||||
if (extensionModule.registerInstanceRenderers) {
|
||||
extensionModule.registerInstanceRenderers(
|
||||
objectsRenderingService
|
||||
);
|
||||
}
|
||||
if (extensionModule.registerClearCache) {
|
||||
extensionModule.registerClearCache(objectsRenderingService);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
extensionModulePath: 'internal-extension://' + name,
|
||||
result: loadExtension(
|
||||
_,
|
||||
gd,
|
||||
gd.JsPlatform.get(),
|
||||
extensionModule
|
||||
),
|
||||
};
|
||||
})
|
||||
return {
|
||||
extensionModulePath: 'internal-extension://' + name,
|
||||
result: loadExtension(_, gd, gd.JsPlatform.get(), extensionModule),
|
||||
};
|
||||
});
|
||||
const expectedNumberOfJSExtensionModulesLoaded = getExpectedNumberOfJSExtensionModules(
|
||||
{
|
||||
filterExamples,
|
||||
}
|
||||
);
|
||||
|
||||
return Promise.resolve({
|
||||
results,
|
||||
expectedNumberOfJSExtensionModulesLoaded,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
@@ -17,8 +17,23 @@ type MakeExtensionsLoaderArguments = {|
|
||||
filterExamples: boolean,
|
||||
onFindGDJS?: ?() => Promise<{gdjsRoot: string}>
|
||||
|};
|
||||
|
||||
type GetExpectedNumberOfJSExtensionModulesArguments = {|
|
||||
filterExamples: boolean,
|
||||
|};
|
||||
*/
|
||||
|
||||
// This value is hardcoded to allow raising an error if the number of JS extensions
|
||||
// loaded is different from the expected one, which may lead
|
||||
// to projects corruption in the future if the app is still used.
|
||||
// If a new extension is added, update this value.
|
||||
// Also remember to add the extension in the list of extensions in BrowserJsExtensionsLoader.js
|
||||
function getExpectedNumberOfJSExtensionModules(
|
||||
{ filterExamples } /*: GetExpectedNumberOfJSExtensionModulesArguments*/
|
||||
) /*:number*/ {
|
||||
return 28 + (filterExamples ? 0 : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loader that will find all JS extensions declared in GDJS/Runtime/Extensions/xxx/JsExtension.js.
|
||||
* If you add a new extension and also want it to be available for the web-app version, add it in
|
||||
@@ -97,7 +112,14 @@ module.exports = function makeExtensionsLoader(
|
||||
),
|
||||
};
|
||||
})
|
||||
);
|
||||
).then(results => {
|
||||
return {
|
||||
results,
|
||||
expectedNumberOfJSExtensionModulesLoaded: getExpectedNumberOfJSExtensionModules(
|
||||
{ filterExamples }
|
||||
),
|
||||
};
|
||||
});
|
||||
},
|
||||
err => {
|
||||
console.error(`Unable to find JS extensions modules`);
|
||||
|
@@ -10,17 +10,18 @@ export type JsExtensionModule = {
|
||||
runExtensionSanityTests(gd: any, extension: gdPlatformExtension): Array<string>,
|
||||
};
|
||||
|
||||
export type ExtensionLoadingResult = {
|
||||
export type ExtensionLoadingResult = {|
|
||||
error: boolean,
|
||||
message: string,
|
||||
dangerous?: boolean,
|
||||
rawError?: any,
|
||||
};
|
||||
|};
|
||||
|
||||
export interface JsExtensionsLoader {
|
||||
loadAllExtensions(_: TranslationFunction): Promise<
|
||||
Array<{ extensionModulePath: string, result: ExtensionLoadingResult }>
|
||||
>,
|
||||
loadAllExtensions(_: TranslationFunction): Promise<{|
|
||||
results: Array<{| extensionModulePath: string, result: ExtensionLoadingResult |}>,
|
||||
expectedNumberOfJSExtensionModulesLoaded: number,
|
||||
|}>,
|
||||
}
|
||||
*/
|
||||
|
||||
|
@@ -116,7 +116,9 @@ export const create = (authentication: Authentication) => {
|
||||
filterExamples: !isDev,
|
||||
})}
|
||||
initialFileMetadataToOpen={initialFileMetadataToOpen}
|
||||
initialExampleSlugToOpen={appArguments['create-from-example'] || null}
|
||||
initialExampleSlugToOpen={
|
||||
appArguments['create-from-example'] || null
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</ProjectStorageProviders>
|
||||
|
@@ -188,18 +188,19 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
)
|
||||
}
|
||||
recommendedPlanIdIfNoSubscription="gdevelop_gold"
|
||||
canHide
|
||||
>
|
||||
<Line>
|
||||
<Column noMargin>
|
||||
<Text noMargin>
|
||||
{increaseQuotaOffering === 'subscribe' ? (
|
||||
<Trans>
|
||||
Get more free AI requests with a GDevelop premium plan.
|
||||
Unlock AI requests included with a GDevelop premium plan.
|
||||
</Trans>
|
||||
) : (
|
||||
<Trans>
|
||||
Upgrade to another premium plan to get more free AI
|
||||
requests.
|
||||
Get even more AI requests included with a higher premium
|
||||
plan.
|
||||
</Trans>
|
||||
)}
|
||||
</Text>
|
||||
@@ -232,45 +233,56 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
<Trans>What do you want to make?</Trans>
|
||||
</Text>
|
||||
</Column>
|
||||
<Column noMargin alignItems="stretch" justifyContent="stretch">
|
||||
<CompactTextAreaField
|
||||
maxLength={6000}
|
||||
value={userRequestText}
|
||||
disabled={isLaunchingAiRequest}
|
||||
onChange={userRequestText =>
|
||||
setUserRequestText(userRequestText)
|
||||
}
|
||||
placeholder={newChatPlaceholder}
|
||||
rows={5}
|
||||
/>
|
||||
</Column>
|
||||
<Line noMargin>
|
||||
<ResponsiveLineStackLayout
|
||||
noMargin
|
||||
alignItems="flex-start"
|
||||
justifyContent="space-between"
|
||||
expand
|
||||
>
|
||||
{!isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
<Line noMargin justifyContent="flex-end">
|
||||
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
label={<Trans>Send</Trans>}
|
||||
style={{ flexShrink: 0 }}
|
||||
disabled={isLaunchingAiRequest}
|
||||
onClick={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
/>
|
||||
</LeftLoader>
|
||||
<form
|
||||
onSubmit={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
>
|
||||
<ColumnStackLayout justifyContent="center" noMargin>
|
||||
<Column noMargin alignItems="stretch" justifyContent="stretch">
|
||||
<CompactTextAreaField
|
||||
maxLength={6000}
|
||||
value={userRequestText}
|
||||
disabled={isLaunchingAiRequest}
|
||||
onChange={userRequestText =>
|
||||
setUserRequestText(userRequestText)
|
||||
}
|
||||
onSubmit={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
placeholder={newChatPlaceholder}
|
||||
rows={5}
|
||||
/>
|
||||
</Column>
|
||||
<Line noMargin>
|
||||
<ResponsiveLineStackLayout
|
||||
noMargin
|
||||
alignItems="flex-start"
|
||||
justifyContent="space-between"
|
||||
expand
|
||||
>
|
||||
{!isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
<Line noMargin justifyContent="flex-end">
|
||||
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
label={<Trans>Send</Trans>}
|
||||
style={{ flexShrink: 0 }}
|
||||
disabled={isLaunchingAiRequest || !userRequestText}
|
||||
onClick={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
/>
|
||||
</LeftLoader>
|
||||
</Line>
|
||||
{isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
</ResponsiveLineStackLayout>
|
||||
</Line>
|
||||
{isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
</ResponsiveLineStackLayout>
|
||||
</Line>
|
||||
</ColumnStackLayout>
|
||||
</form>
|
||||
{subscriptionBanner}
|
||||
</ColumnStackLayout>
|
||||
<Column justifyContent="center" noMargin>
|
||||
<Column justifyContent="center">
|
||||
<Text size="body-small" color="secondary" align="center" noMargin>
|
||||
<Trans>
|
||||
The AI is experimental and still being improved.{' '}
|
||||
@@ -458,37 +470,52 @@ export const AiRequestChat = React.forwardRef<Props, AiRequestChatInterface>(
|
||||
) : (
|
||||
subscriptionBanner
|
||||
)}
|
||||
<CompactTextAreaField
|
||||
maxLength={6000}
|
||||
value={userRequestText}
|
||||
disabled={isLaunchingAiRequest}
|
||||
onChange={userRequestText => setUserRequestText(userRequestText)}
|
||||
placeholder={t`Ask a follow up question`}
|
||||
rows={2}
|
||||
/>
|
||||
<Column noMargin alignItems="flex-end">
|
||||
<ResponsiveLineStackLayout
|
||||
<form
|
||||
onSubmit={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
>
|
||||
<ColumnStackLayout
|
||||
justifyContent="stretch"
|
||||
alignItems="stretch"
|
||||
noMargin
|
||||
alignItems="flex-start"
|
||||
justifyContent="space-between"
|
||||
expand
|
||||
>
|
||||
{!isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
<Line noMargin justifyContent="flex-end">
|
||||
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
disabled={aiRequest.status === 'working'}
|
||||
label={<Trans>Send</Trans>}
|
||||
onClick={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
/>
|
||||
</LeftLoader>
|
||||
</Line>
|
||||
{isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
</ResponsiveLineStackLayout>
|
||||
</Column>
|
||||
<CompactTextAreaField
|
||||
maxLength={6000}
|
||||
value={userRequestText}
|
||||
disabled={isLaunchingAiRequest}
|
||||
onChange={userRequestText => setUserRequestText(userRequestText)}
|
||||
placeholder={t`Ask a follow up question`}
|
||||
rows={2}
|
||||
onSubmit={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
/>
|
||||
<Column noMargin alignItems="flex-end">
|
||||
<ResponsiveLineStackLayout
|
||||
noMargin
|
||||
alignItems="flex-start"
|
||||
justifyContent="space-between"
|
||||
expand
|
||||
>
|
||||
{!isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
<Line noMargin justifyContent="flex-end">
|
||||
<LeftLoader reserveSpace isLoading={isLaunchingAiRequest}>
|
||||
<RaisedButton
|
||||
color="primary"
|
||||
disabled={aiRequest.status === 'working'}
|
||||
label={<Trans>Send</Trans>}
|
||||
onClick={() => {
|
||||
onSendUserRequest(userRequestText);
|
||||
}}
|
||||
/>
|
||||
</LeftLoader>
|
||||
</Line>
|
||||
{isMobile && errorOrQuotaOrCreditsExplanation}
|
||||
</ResponsiveLineStackLayout>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</form>
|
||||
{dislikeFeedbackDialogOpenedFor && (
|
||||
<DislikeFeedbackDialog
|
||||
open
|
||||
|
@@ -59,6 +59,7 @@ export type AskAiEditorInterface = {|
|
||||
scene: gdLayout,
|
||||
objectWithContext: ObjectWithContext
|
||||
) => void,
|
||||
onSceneObjectsDeleted: (scene: gdLayout) => void,
|
||||
|};
|
||||
|
||||
const noop = () => {};
|
||||
@@ -109,6 +110,7 @@ export const AskAi = React.memo<Props>(
|
||||
forceUpdateEditor: noop,
|
||||
onEventsBasedObjectChildrenEdited: noop,
|
||||
onSceneObjectEdited: noop,
|
||||
onSceneObjectsDeleted: noop,
|
||||
}));
|
||||
|
||||
const aiRequestChatRef = React.useRef<AiRequestChatInterface | null>(
|
||||
|
@@ -151,6 +151,7 @@ export type RenderEditorContainerProps = {|
|
||||
scene: gdLayout,
|
||||
objectWithContext: ObjectWithContext
|
||||
) => void,
|
||||
onSceneObjectsDeleted: (scene: gdLayout) => void,
|
||||
|
||||
onExtractAsExternalLayout: (name: string) => void,
|
||||
onExtractAsEventBasedObject: (
|
||||
|
@@ -237,9 +237,15 @@ export class CustomObjectEditorContainer extends React.Component<RenderEditorCon
|
||||
onObjectEdited={() =>
|
||||
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject)
|
||||
}
|
||||
onObjectsDeleted={() =>
|
||||
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject)
|
||||
}
|
||||
onObjectGroupEdited={() =>
|
||||
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject)
|
||||
}
|
||||
onObjectGroupsDeleted={() =>
|
||||
this.props.onEventsBasedObjectChildrenEdited(eventsBasedObject)
|
||||
}
|
||||
onEventsBasedObjectChildrenEdited={
|
||||
this.props.onEventsBasedObjectChildrenEdited
|
||||
}
|
||||
|
@@ -57,6 +57,10 @@ export class DebuggerEditorContainer extends React.Component<
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
onSceneObjectsDeleted(scene: gdLayout) {
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
// To be updated, see https://reactjs.org/docs/react-component.html#unsafe_componentwillreceiveprops.
|
||||
UNSAFE_componentWillReceiveProps() {
|
||||
this._checkUserHasSubscription();
|
||||
|
@@ -53,6 +53,10 @@ export class EventsEditorContainer extends React.Component<RenderEditorContainer
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
onSceneObjectsDeleted(scene: gdLayout) {
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
getLayout(): ?gdLayout {
|
||||
const { project, projectItemName } = this.props;
|
||||
if (
|
||||
|
@@ -46,6 +46,10 @@ export class EventsFunctionsExtensionEditorContainer extends React.Component<Ren
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
onSceneObjectsDeleted(scene: gdLayout) {
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps: RenderEditorContainerProps) {
|
||||
// We stop updates when the component is inactive.
|
||||
// If it's active, was active or becoming active again we let update propagate.
|
||||
|
@@ -90,6 +90,10 @@ export class ExternalEventsEditorContainer extends React.Component<
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
onSceneObjectsDeleted(scene: gdLayout) {
|
||||
// No thing to be done.
|
||||
}
|
||||
|
||||
getExternalEvents(): ?gdExternalEvents {
|
||||
const { project, projectItemName } = this.props;
|
||||
if (!project || !projectItemName) return null;
|
||||
|
@@ -138,6 +138,20 @@ export class ExternalLayoutEditorContainer extends React.Component<
|
||||
}
|
||||
}
|
||||
|
||||
onSceneObjectsDeleted(scene: gdLayout) {
|
||||
const { editor } = this;
|
||||
const externalLayout = this.getExternalLayout();
|
||||
if (!externalLayout) {
|
||||
return;
|
||||
}
|
||||
if (externalLayout.getAssociatedLayout() !== scene.getName()) {
|
||||
return;
|
||||
}
|
||||
if (editor) {
|
||||
editor.forceUpdateObjectsList();
|
||||
}
|
||||
}
|
||||
|
||||
getExternalLayout(): ?gdExternalLayout {
|
||||
const { project, projectItemName } = this.props;
|
||||
if (!project || !projectItemName) return null;
|
||||
@@ -266,8 +280,10 @@ export class ExternalLayoutEditorContainer extends React.Component<
|
||||
onObjectEdited={objectWithContext =>
|
||||
this.props.onSceneObjectEdited(layout, objectWithContext)
|
||||
}
|
||||
onObjectsDeleted={() => this.props.onSceneObjectsDeleted(layout)}
|
||||
// It's only used to refresh events-based object variants.
|
||||
onObjectGroupEdited={() => {}}
|
||||
onObjectGroupsDeleted={() => {}}
|
||||
// Nothing to do as events-based objects can't have external layout.
|
||||
onEventsBasedObjectChildrenEdited={() => {}}
|
||||
onExtensionInstalled={this.props.onExtensionInstalled}
|
||||
|
@@ -313,6 +313,7 @@ const EducationMarketingSection = ({
|
||||
tutorials={educationTutorials}
|
||||
// In this marketing view, users are not allowed to open tutorials so no need to specify this prop.
|
||||
onSelectTutorial={() => {}}
|
||||
onSelectCourse={() => {}}
|
||||
// In this marketing view, users are not allowed to open tutorials so no need to specify this prop.
|
||||
onOpenTemplateFromTutorial={async () => {}}
|
||||
isLocked
|
||||
|
@@ -167,12 +167,27 @@ const BadgeItem = ({
|
||||
hasThisBadge,
|
||||
buttonLabel,
|
||||
linkUrl,
|
||||
onOpenProfile,
|
||||
}: {|
|
||||
achievement: ?Achievement,
|
||||
hasThisBadge: boolean,
|
||||
buttonLabel: React.Node,
|
||||
linkUrl: string,
|
||||
onOpenProfile: () => void,
|
||||
|}) => {
|
||||
const [hasBeenClicked, setHasBeenClicked] = React.useState(false);
|
||||
const onClick = React.useCallback(
|
||||
() => {
|
||||
if (hasBeenClicked) {
|
||||
onOpenProfile();
|
||||
} else {
|
||||
Window.openExternalURL(linkUrl);
|
||||
setHasBeenClicked(true);
|
||||
}
|
||||
},
|
||||
[hasBeenClicked, linkUrl, onOpenProfile]
|
||||
);
|
||||
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
@@ -217,11 +232,9 @@ const BadgeItem = ({
|
||||
</Text>
|
||||
</Column>
|
||||
<TextButton
|
||||
label={buttonLabel}
|
||||
label={!hasBeenClicked ? buttonLabel : <Trans>Claim credits</Trans>}
|
||||
secondary
|
||||
onClick={() => {
|
||||
Window.openExternalURL(linkUrl);
|
||||
}}
|
||||
onClick={onClick}
|
||||
disabled={hasThisBadge}
|
||||
/>
|
||||
</LineStackLayout>
|
||||
@@ -364,6 +377,7 @@ export const EarnCredits = ({
|
||||
hasThisBadge={!!item.hasThisBadge}
|
||||
buttonLabel={item.label}
|
||||
linkUrl={item.linkUrl}
|
||||
onOpenProfile={onOpenProfile}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@@ -1,185 +0,0 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { t, Trans } from '@lingui/macro';
|
||||
import { I18n as I18nType } from '@lingui/core';
|
||||
import { useResponsiveWindowSize } from '../../../../UI/Responsive/ResponsiveWindowMeasurer';
|
||||
import GDevelopThemeContext from '../../../../UI/Theme/GDevelopThemeContext';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { Column, Line } from '../../../../UI/Grid';
|
||||
import Paper from '../../../../UI/Paper';
|
||||
import {
|
||||
ColumnStackLayout,
|
||||
ResponsiveLineStackLayout,
|
||||
} from '../../../../UI/Layout';
|
||||
import Text from '../../../../UI/Text';
|
||||
import CheckCircle from '../../../../UI/CustomSvgIcons/CheckCircle';
|
||||
import RaisedButton from '../../../../UI/RaisedButton';
|
||||
import Window from '../../../../Utils/Window';
|
||||
import { selectMessageByLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
|
||||
const styles = {
|
||||
bulletIcon: { width: 20, height: 20, marginRight: 10 },
|
||||
bulletText: { flex: 1 },
|
||||
planRecommendationThumbnail: { maxWidth: 350, flex: 1 },
|
||||
planRecommendationContainer: { borderRadius: 8, maxWidth: 850, padding: 8 },
|
||||
};
|
||||
|
||||
const planImages = {
|
||||
individual: {
|
||||
path: 'res/plan-individual.svg',
|
||||
alt: t`Red hero taking care of their diamond`,
|
||||
},
|
||||
education: {
|
||||
path: 'res/plan-education.svg',
|
||||
alt: t`Red hero sharing knowledge with pink cloud students`,
|
||||
},
|
||||
professional: {
|
||||
path: 'res/plan-professional.svg',
|
||||
alt: t`Red and Green heroes running side by side carrying their diamonds`,
|
||||
},
|
||||
};
|
||||
|
||||
const planDetailsById = {
|
||||
silver: {
|
||||
title: <Trans>GDevelop's Silver plan</Trans>,
|
||||
description: (
|
||||
<Trans>Unlock GDevelop's features to build more and faster.</Trans>
|
||||
),
|
||||
image: planImages.individual,
|
||||
link: 'https://gdevelop.io/pricing/individual',
|
||||
},
|
||||
gold: {
|
||||
title: <Trans>GDevelop's Gold plan</Trans>,
|
||||
description: (
|
||||
<Trans>Unlock GDevelop's features to build more and faster.</Trans>
|
||||
),
|
||||
image: planImages.individual,
|
||||
link: 'https://gdevelop.io/pricing/individual',
|
||||
},
|
||||
education: {
|
||||
title: <Trans>GDevelop's Education plan</Trans>,
|
||||
description: (
|
||||
<Trans>
|
||||
For universities, extra curricular classes and summer camps.
|
||||
</Trans>
|
||||
),
|
||||
image: planImages.education,
|
||||
link: 'https://gdevelop.io/pricing/education',
|
||||
},
|
||||
startup: {
|
||||
title: <Trans>GDevelop's Startup plan</Trans>,
|
||||
description: (
|
||||
<Trans>
|
||||
Get the most out of GDevelop and get your games out in no time.
|
||||
</Trans>
|
||||
),
|
||||
image: planImages.professional,
|
||||
link: 'https://gdevelop.io/pricing/business',
|
||||
},
|
||||
business: {
|
||||
title: <Trans>GDevelop's Business plan</Trans>,
|
||||
description: (
|
||||
<Trans>
|
||||
Get the most out of GDevelop and get your games out in no time.
|
||||
</Trans>
|
||||
),
|
||||
image: planImages.professional,
|
||||
link: 'https://gdevelop.io/pricing/business',
|
||||
},
|
||||
};
|
||||
|
||||
const PlanRecommendationRow = ({
|
||||
recommendationPlanId,
|
||||
subscriptionPlansWithPricingSystems,
|
||||
i18n,
|
||||
}: {|
|
||||
recommendationPlanId: string,
|
||||
subscriptionPlansWithPricingSystems: SubscriptionPlanWithPricingSystems[],
|
||||
i18n: I18nType,
|
||||
|}) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const { isMobile } = useResponsiveWindowSize();
|
||||
const planToUse =
|
||||
recommendationPlanId === 'silver'
|
||||
? 'gdevelop_silver'
|
||||
: recommendationPlanId === 'gold'
|
||||
? 'gdevelop_gold'
|
||||
: recommendationPlanId === 'education'
|
||||
? 'gdevelop_education'
|
||||
: recommendationPlanId === 'startup' ||
|
||||
recommendationPlanId === 'business'
|
||||
? 'gdevelop_startup'
|
||||
: null;
|
||||
if (!planToUse) return null;
|
||||
|
||||
const plan = subscriptionPlansWithPricingSystems.find(
|
||||
plan => plan.id === planToUse
|
||||
);
|
||||
if (!plan) return null;
|
||||
|
||||
const planDetails = planDetailsById[recommendationPlanId];
|
||||
|
||||
return (
|
||||
<Line justifyContent="center">
|
||||
<Paper
|
||||
background="dark"
|
||||
style={{
|
||||
...styles.planRecommendationContainer,
|
||||
border: `1px solid ${gdevelopTheme.palette.secondary}`,
|
||||
}}
|
||||
>
|
||||
<ResponsiveLineStackLayout noColumnMargin noMargin>
|
||||
<img
|
||||
src={planDetails.image.path}
|
||||
alt={i18n._(planDetails.image.alt)}
|
||||
style={styles.planRecommendationThumbnail}
|
||||
/>
|
||||
<Line expand>
|
||||
<ColumnStackLayout>
|
||||
<Text
|
||||
noMargin
|
||||
align={isMobile ? 'center' : 'left'}
|
||||
size="section-title"
|
||||
>
|
||||
{planDetails.title}
|
||||
</Text>
|
||||
<Text align={isMobile ? 'center' : 'left'}>
|
||||
{planDetails.description}
|
||||
</Text>
|
||||
<div style={{ padding: `0 20px` }}>
|
||||
<ColumnStackLayout noMargin>
|
||||
{plan.bulletPointsByLocale.map(
|
||||
(bulletPointByLocale, index) => (
|
||||
<Column key={index} expand noMargin>
|
||||
<Line noMargin alignItems="center">
|
||||
<CheckCircle
|
||||
style={{
|
||||
...styles.bulletIcon,
|
||||
color: gdevelopTheme.message.valid,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text style={styles.bulletText} size="body2" noMargin>
|
||||
{selectMessageByLocale(i18n, bulletPointByLocale)}
|
||||
</Text>
|
||||
</Line>
|
||||
</Column>
|
||||
)
|
||||
)}
|
||||
</ColumnStackLayout>
|
||||
</div>
|
||||
<Column noMargin>
|
||||
<RaisedButton
|
||||
primary
|
||||
label={<Trans>Learn More</Trans>}
|
||||
onClick={() => Window.openExternalURL(planDetails.link)}
|
||||
/>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</Line>
|
||||
</ResponsiveLineStackLayout>
|
||||
</Paper>
|
||||
</Line>
|
||||
);
|
||||
};
|
||||
export default PlanRecommendationRow;
|
@@ -8,7 +8,6 @@ import { makeStyles } from '@material-ui/styles';
|
||||
import GridList from '@material-ui/core/GridList';
|
||||
import GridListTile from '@material-ui/core/GridListTile';
|
||||
import { type AuthenticatedUser } from '../../../../Profile/AuthenticatedUserContext';
|
||||
import { type Subscription } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { TutorialContext } from '../../../../Tutorial/TutorialContext';
|
||||
import { SectionRow } from '../SectionContainer';
|
||||
import GuidedLessons from '../InAppTutorials/GuidedLessons';
|
||||
@@ -21,16 +20,11 @@ import {
|
||||
import Text from '../../../../UI/Text';
|
||||
import { Column, Line, Spacer } from '../../../../UI/Grid';
|
||||
import { type Tutorial } from '../../../../Utils/GDevelopServices/Tutorial';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import { CardWidget } from '../CardWidget';
|
||||
import Window from '../../../../Utils/Window';
|
||||
import { ColumnStackLayout } from '../../../../UI/Layout';
|
||||
import {
|
||||
type GuidedLessonsRecommendation,
|
||||
type PlanRecommendation,
|
||||
} from '../../../../Utils/GDevelopServices/User';
|
||||
import { type GuidedLessonsRecommendation } from '../../../../Utils/GDevelopServices/User';
|
||||
import PreferencesContext from '../../../Preferences/PreferencesContext';
|
||||
import PlanRecommendationRow from './PlanRecommendationRow';
|
||||
import { SurveyCard } from './SurveyCard';
|
||||
import PlaceholderLoader from '../../../../UI/PlaceholderLoader';
|
||||
import PromotionsSlideshow from '../../../../Promotions/PromotionsSlideshow';
|
||||
@@ -109,26 +103,6 @@ const getTutorialsLimitsFromWidth = (
|
||||
}
|
||||
};
|
||||
|
||||
const isPlanRecommendationRelevant = (
|
||||
subscription: Subscription,
|
||||
planRecommendation: PlanRecommendation
|
||||
): boolean => {
|
||||
// Don't recommend plans to holders of education plan.
|
||||
if (subscription.planId === 'gdevelop_education') return false;
|
||||
|
||||
const relevantPlans =
|
||||
subscription.planId === 'gdevelop_silver' ||
|
||||
subscription.planId === 'gdevelop_indie'
|
||||
? ['gold', 'startup', 'business', 'education']
|
||||
: subscription.planId === 'gdevelop_gold' ||
|
||||
subscription.planId === 'gdevelop_pro'
|
||||
? ['startup', 'business', 'education']
|
||||
: subscription.planId === 'gdevelop_startup'
|
||||
? ['business']
|
||||
: [];
|
||||
return relevantPlans.includes(planRecommendation.id);
|
||||
};
|
||||
|
||||
type TextTutorialsRowProps = {|
|
||||
tutorials: Array<Tutorial>,
|
||||
i18n: I18nType,
|
||||
@@ -186,7 +160,6 @@ const TextTutorialsRow = ({ tutorials, i18n }: TextTutorialsRowProps) => {
|
||||
type Props = {|
|
||||
authenticatedUser: AuthenticatedUser,
|
||||
selectInAppTutorial: (tutorialId: string) => void,
|
||||
subscriptionPlansWithPricingSystems: ?(SubscriptionPlanWithPricingSystems[]),
|
||||
onStartSurvey: null | (() => void),
|
||||
hasFilledSurveyAlready: boolean,
|
||||
onOpenProfile: () => void,
|
||||
@@ -202,14 +175,13 @@ type Props = {|
|
||||
const RecommendationList = ({
|
||||
authenticatedUser,
|
||||
selectInAppTutorial,
|
||||
subscriptionPlansWithPricingSystems,
|
||||
onStartSurvey,
|
||||
hasFilledSurveyAlready,
|
||||
onOpenProfile,
|
||||
onCreateProjectFromExample,
|
||||
askToCloseProject,
|
||||
}: Props) => {
|
||||
const { recommendations, subscription, limits } = authenticatedUser;
|
||||
const { recommendations, limits } = authenticatedUser;
|
||||
const { tutorials } = React.useContext(TutorialContext);
|
||||
const {
|
||||
getTutorialProgress,
|
||||
@@ -251,11 +223,6 @@ const RecommendationList = ({
|
||||
? guidedLessonsRecommendation.lessonsIds
|
||||
: null;
|
||||
|
||||
// $FlowIgnore
|
||||
const planRecommendation: ?PlanRecommendation = recommendations.find(
|
||||
recommendation => recommendation.type === 'plan'
|
||||
);
|
||||
|
||||
const getInAppTutorialPartProgress = ({
|
||||
tutorialId,
|
||||
}: {
|
||||
@@ -373,32 +340,6 @@ const RecommendationList = ({
|
||||
</SectionRow>
|
||||
);
|
||||
}
|
||||
if (planRecommendation) {
|
||||
const shouldDisplayPlanRecommendation =
|
||||
limits &&
|
||||
!(
|
||||
limits.capabilities.classrooms &&
|
||||
limits.capabilities.classrooms.hideUpgradeNotice
|
||||
) &&
|
||||
(!subscription ||
|
||||
isPlanRecommendationRelevant(subscription, planRecommendation));
|
||||
if (
|
||||
shouldDisplayPlanRecommendation &&
|
||||
subscriptionPlansWithPricingSystems
|
||||
) {
|
||||
items.push(
|
||||
<SectionRow key="plan">
|
||||
<PlanRecommendationRow
|
||||
recommendationPlanId={planRecommendation.id}
|
||||
subscriptionPlansWithPricingSystems={
|
||||
subscriptionPlansWithPricingSystems
|
||||
}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</SectionRow>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@@ -23,7 +23,6 @@ import PreferencesContext from '../../../Preferences/PreferencesContext';
|
||||
import RecommendationList from './RecommendationList';
|
||||
import ErrorBoundary from '../../../../UI/ErrorBoundary';
|
||||
import { delay } from '../../../../Utils/Delay';
|
||||
import { type SubscriptionPlanWithPricingSystems } from '../../../../Utils/GDevelopServices/Usage';
|
||||
import Checkbox from '../../../../UI/Checkbox';
|
||||
import { sendUserSurveyCompleted } from '../../../../Utils/Analytics/EventSender';
|
||||
import { type NewProjectSetup } from '../../../../ProjectCreation/NewProjectSetupDialog';
|
||||
@@ -60,7 +59,6 @@ type Props = {|
|
||||
onUserSurveyStarted: () => void,
|
||||
onUserSurveyHidden: () => void,
|
||||
selectInAppTutorial: (tutorialId: string) => void,
|
||||
subscriptionPlansWithPricingSystems: ?(SubscriptionPlanWithPricingSystems[]),
|
||||
onOpenProfile: () => void,
|
||||
onCreateProjectFromExample: (
|
||||
exampleShortHeader: ExampleShortHeader,
|
||||
@@ -75,7 +73,6 @@ const GetStartedSection = ({
|
||||
selectInAppTutorial,
|
||||
onUserSurveyStarted,
|
||||
onUserSurveyHidden,
|
||||
subscriptionPlansWithPricingSystems,
|
||||
onOpenProfile,
|
||||
onCreateProjectFromExample,
|
||||
askToCloseProject,
|
||||
@@ -247,9 +244,6 @@ const GetStartedSection = ({
|
||||
<RecommendationList
|
||||
authenticatedUser={authenticatedUser}
|
||||
selectInAppTutorial={selectInAppTutorial}
|
||||
subscriptionPlansWithPricingSystems={
|
||||
subscriptionPlansWithPricingSystems
|
||||
}
|
||||
onOpenProfile={onOpenProfile}
|
||||
onStartSurvey={
|
||||
profile
|
||||
|
@@ -37,7 +37,11 @@ import { selectMessageByLocale } from '../../../../Utils/i18n/MessageByLocale';
|
||||
|
||||
const styles = {
|
||||
desktopContainer: { display: 'flex', gap: 16 },
|
||||
sideContainer: { maxWidth: 250, position: 'relative' },
|
||||
sideContainer: {
|
||||
width: 250,
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
},
|
||||
sideContent: {
|
||||
position: 'sticky',
|
||||
top: 20,
|
||||
|
@@ -90,6 +90,7 @@ type Props = {|
|
||||
limits: ?Limits,
|
||||
tutorial: Tutorial,
|
||||
onSelectTutorial: (tutorial: Tutorial) => void,
|
||||
onSelectCourse: (courseId: string) => void,
|
||||
index: number,
|
||||
onOpenTemplateFromTutorial: ?(string) => void,
|
||||
isLocked?: boolean,
|
||||
@@ -101,6 +102,7 @@ const EducationCurriculumLesson = ({
|
||||
tutorial,
|
||||
limits,
|
||||
onSelectTutorial,
|
||||
onSelectCourse,
|
||||
index,
|
||||
onOpenTemplateFromTutorial,
|
||||
isLocked,
|
||||
@@ -120,7 +122,7 @@ const EducationCurriculumLesson = ({
|
||||
})}`
|
||||
)
|
||||
: null;
|
||||
const { gameLink } = tutorial;
|
||||
const { gameLink, courseId } = tutorial;
|
||||
|
||||
const title = (
|
||||
<LineStackLayout noMargin alignItems="center">
|
||||
@@ -229,13 +231,23 @@ const EducationCurriculumLesson = ({
|
||||
onClick={onOpenTemplateFromTutorial}
|
||||
/>
|
||||
)}
|
||||
<RaisedButton
|
||||
primary
|
||||
fullWidth={isMobile && !isLandscape}
|
||||
disabled={isLessonLocked}
|
||||
label={<Trans>Open lesson</Trans>}
|
||||
onClick={() => onSelectTutorial(tutorial)}
|
||||
/>
|
||||
{courseId ? (
|
||||
<RaisedButton
|
||||
primary
|
||||
fullWidth={isMobile && !isLandscape}
|
||||
disabled={isLessonLocked}
|
||||
label={<Trans>Open course</Trans>}
|
||||
onClick={() => onSelectCourse(courseId)}
|
||||
/>
|
||||
) : (
|
||||
<RaisedButton
|
||||
primary
|
||||
fullWidth={isMobile && !isLandscape}
|
||||
disabled={isLessonLocked}
|
||||
label={<Trans>Open lesson</Trans>}
|
||||
onClick={() => onSelectTutorial(tutorial)}
|
||||
/>
|
||||
)}
|
||||
</LineStackLayout>
|
||||
</LineStackLayout>
|
||||
)}
|
||||
|
@@ -31,6 +31,7 @@ type EducationCurriculumProps = {|
|
||||
limits: ?Limits,
|
||||
tutorials: Tutorial[],
|
||||
onSelectTutorial: Tutorial => void,
|
||||
onSelectCourse: (courseId: string) => void,
|
||||
onOpenTemplateFromTutorial: string => Promise<void>,
|
||||
isLocked?: boolean,
|
||||
onClickSubscribe?: () => void,
|
||||
@@ -42,6 +43,7 @@ export const EducationCurriculum = ({
|
||||
limits,
|
||||
tutorials,
|
||||
onSelectTutorial,
|
||||
onSelectCourse,
|
||||
onOpenTemplateFromTutorial,
|
||||
isLocked,
|
||||
onClickSubscribe,
|
||||
@@ -84,6 +86,7 @@ export const EducationCurriculum = ({
|
||||
isLocked={isLocked}
|
||||
onClickSubscribe={onClickSubscribe}
|
||||
onSelectTutorial={onSelectTutorial}
|
||||
onSelectCourse={onSelectCourse}
|
||||
index={sectionIndex}
|
||||
onOpenTemplateFromTutorial={
|
||||
tutorial.templateUrl
|
||||
@@ -103,6 +106,7 @@ export const EducationCurriculum = ({
|
||||
i18n,
|
||||
limits,
|
||||
onSelectTutorial,
|
||||
onSelectCourse,
|
||||
onOpenTemplateFromTutorial,
|
||||
renderInterstitialCallout,
|
||||
isLocked,
|
||||
@@ -135,6 +139,7 @@ type Props = {|
|
||||
tutorials: Array<Tutorial>,
|
||||
category: TutorialCategory,
|
||||
onOpenTemplateFromTutorial: string => Promise<void>,
|
||||
onSelectCourse: (courseId: string) => void,
|
||||
|};
|
||||
|
||||
const TutorialsCategoryPage = ({
|
||||
@@ -142,6 +147,7 @@ const TutorialsCategoryPage = ({
|
||||
tutorials,
|
||||
onBack,
|
||||
onOpenTemplateFromTutorial,
|
||||
onSelectCourse,
|
||||
}: Props) => {
|
||||
const { limits } = React.useContext(AuthenticatedUserContext);
|
||||
const texts = TUTORIAL_CATEGORY_TEXTS[category];
|
||||
@@ -167,6 +173,7 @@ const TutorialsCategoryPage = ({
|
||||
<EducationCurriculum
|
||||
tutorials={filteredTutorials}
|
||||
onSelectTutorial={setSelectedTutorial}
|
||||
onSelectCourse={onSelectCourse}
|
||||
i18n={i18n}
|
||||
limits={limits}
|
||||
onOpenTemplateFromTutorial={onOpenTemplateFromTutorial}
|
||||
|