Compare commits

..

119 Commits

Author SHA1 Message Date
Aurélien Vivet
0ee15aabe2 Update index.js 2024-11-26 18:28:35 +01:00
D8H
e6343dfe18 Fix the parameter name field to avoid changing the name when no change was done (#7178) 2024-11-20 18:48:04 +01:00
Florian Rival
ac6b64ba9b Add an option to extend width or height and never crop the game area (#7177) 2024-11-20 16:53:47 +01:00
Florian Rival
44b18cb111 Upload files in batch for web-app previews (#7176)
Only show in developer changelog
2024-11-18 17:36:00 +01:00
Clément Pasteau
c5fc7e08f5 Update marketing boosts display (#7174) 2024-11-18 15:30:22 +01:00
Florian Rival
9eada905f9 Add "Create new folder" menu item inside "Move to folder" menu (#7172) 2024-11-18 11:28:40 +01:00
Clément Pasteau
13aab9a8e8 Improve LAN preview network to detect local ip properly (#7170) 2024-11-18 10:29:17 +01:00
github-actions[bot]
dda85cf630 Update translations [skip ci] (#7163)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-11-15 10:39:57 +01:00
Clément Pasteau
0f81e4c088 Fixes game dashboard (#7166)
Do not show in changelog
2024-11-14 18:27:52 +01:00
Clément Pasteau
5419493349 Fix height when switching to mobile design (#7165) 2024-11-14 16:41:23 +01:00
Clément Pasteau
272766c705 Automatically use a screenshot from the latest preview as the game thumbnail if none are set (#7156) 2024-11-14 12:07:09 +01:00
D8H
a06138b31e Fix sprite scaling factor when a custom size is set (#7164) 2024-11-14 11:09:07 +01:00
Florian Rival
74a7ba5a09 Bump newIDE version 2024-11-14 09:54:25 +01:00
github-actions[bot]
3914d0377f Update translations [skip ci] (#7158)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-11-14 09:50:18 +01:00
D8H
092b29fa0e Fix uninitialized members in default constructor of ProjectScopedContainers (#7162)
Don't show in changelog
2024-11-13 17:39:20 +01:00
D8H
16762960dc Allow legacy scene variable parameters to use extension variables (#7121) 2024-11-13 17:17:02 +01:00
Clément Pasteau
33101ead64 Fix correctly using the app icon when exporting to desktop (#7161) 2024-11-13 17:08:24 +01:00
D8H
de73d617b0 Fix missing error in expressions for missing object variables with children (#7159) 2024-11-13 16:16:46 +01:00
D8H
446b0db05f Reduce the risk of name collisions between objects, variables, parameters and properties (#7148) 2024-11-13 15:16:41 +01:00
Clément Pasteau
66ab7abab7 Fix default color values for shape painter (#7155) 2024-11-13 11:59:53 +01:00
Clément Pasteau
a80b540f06 Fix game dashboard param (#7157)
Don't show in changelog
2024-11-13 11:56:00 +01:00
github-actions[bot]
38761aeec1 Update translations [skip ci] (#7153)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-11-13 11:00:11 +01:00
Clément Pasteau
602dc9d791 Improve login & signup dialogs design (#7152) 2024-11-13 10:23:02 +01:00
Florian Rival
162a70316a Make resource import error dialog less scary
Don't show in changelog
2024-11-12 18:27:37 +01:00
github-actions[bot]
10e8094375 Update translations [skip ci] (#7147)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-11-12 18:26:26 +01:00
AlexandreS
82af8dd7f3 Reduce the number of refreshes of the team when opening the profile dialog (#7150)
Do not show in changelog
2024-11-12 18:16:44 +01:00
Clément Pasteau
8cf739aa78 Merge Community & Play tabs (#7139) 2024-11-12 18:15:19 +01:00
AlexandreS
f3f3d24706 Deprecate google drive support (#7151) 2024-11-12 18:13:04 +01:00
Florian Rival
83f80b2350 Move back up links in Learn page and question block 2024-11-12 17:27:05 +01:00
danvervlad
1172326ae0 Add "dispose" method to RuntimeGame and various classes (#7118)
* This allows to fully release resources, textures and anything else that is linked to a gdjs.RuntimeGame. This can be useful if multiple games must be loaded/unloaded/changed in a single web page.

Only show in developer changelog
2024-11-12 17:19:50 +01:00
AlexandreS
aed09d86b3 Improve landscape mode and responsiveness of the UI on small screens (#7142) 2024-11-12 15:40:43 +01:00
Florian Rival
c2d03050b8 Add sort field (creation date, weekly sessions, all time sessions) for the list of games in the Manage tab 2024-11-11 17:50:59 +01:00
D8H
2e941c5afc Add tests on property renaming in expressions (#7146)
- Don't show in changelog
2024-11-11 17:23:18 +01:00
Florian Rival
a3f80f2607 Fix metrics in the new game dashboard wrongly limited to 7 days
Don't show in changelog
2024-11-11 16:42:20 +01:00
github-actions[bot]
3497eb2945 Update translations [skip ci] (#7127)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-11-11 15:18:16 +01:00
MTSyntho
b9a1f50d13 Add mention to Three.js in README (#7144) 2024-11-10 19:22:18 +01:00
Florian Rival
52d239b60c Add a button to ask any question in the Learn page (#7143) 2024-11-10 15:36:49 +01:00
AlexandreS
c549e277a7 Various fixes for the new game dashboard (#7141)
Don't show in changelog
2024-11-08 17:27:04 +01:00
inspace
afed5d57f7 Only add watermark styles in case showWatermark is true (#7140)
* This avoids using a DOM API if not needed.

Only show in developer changelog
2024-11-08 16:27:59 +01:00
D8H
a3f7176c42 [Physics2] Fix a memory leak on object instances (#7136) 2024-11-08 14:53:38 +01:00
Florian Rival
223268554b Fix Dialogue Tree returning sometimes a string instead of a number in expressions reading variables 2024-11-08 13:46:13 +01:00
AlexandreS
43ef037a07 Display game-related data and services in a dashboard (#7114) 2024-11-08 11:55:43 +01:00
Clément Pasteau
20d2e06fc6 Fix login dialog stuck in loading after trying to use a provider (#7138) 2024-11-07 17:52:55 +01:00
Clément Pasteau
9f795c405a Fix destroying an object even if flagged as "DoNothing" in the multiplayer behavior (#7137) 2024-11-07 14:08:02 +01:00
Florian Rival
0155344ec3 Fix dialogue tree crashing the game when syntax errors are present 2024-11-06 20:09:51 +01:00
Clément Pasteau
71d6d6a165 Fix thumbnail height for screenshots in quick customization (#7134)
Don't show in changelog
2024-11-06 16:04:07 +01:00
Clément Pasteau
edd14b5f8b Fix thumbnail condition in marketing package (#7133)
Do not show in changelog
2024-11-06 15:30:44 +01:00
Clément Pasteau
77d60b699b Prevent opening variables dialog for objects & groups if there is no object (#7132) 2024-11-06 14:49:16 +01:00
Florian Rival
5bc80537b7 Fix leaderboards not properly replaced in projects using them in custom objects (#7131) 2024-11-06 12:46:34 +01:00
Clément Pasteau
f93b850382 Improve object collision masks & points dialog button label (#7130) 2024-11-06 10:13:16 +01:00
Clément Pasteau
da7cae08a1 Fix game thumbnail taking too much space on mobile in Quick Customization dialog (#7128) 2024-11-05 14:46:54 +01:00
Florian Rival
0ae68877b7 Fix centering of "UI layers" after a screen orientation change (#7126)
* This happened often on mobile (iOS notably) and sometimes when resizing the window on desktop too.
2024-11-05 13:45:54 +01:00
github-actions[bot]
32c4e040e0 Update translations [skip ci] (#7120)
Co-authored-by: D8H <2611977+D8H@users.noreply.github.com>
2024-11-05 11:44:03 +01:00
D8H
74034a0ac1 Show object effect drop-down list for parameters in extension editor (#7119)
- Shows object thumbnails in custom objects event functions
2024-10-31 17:02:39 +01:00
github-actions[bot]
1b41225822 Update translations [skip ci] (#7113)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-10-31 11:34:24 +01:00
Clément Pasteau
c620ed75b3 Take automatic game screenshots in Quick Customization previews (#7116) 2024-10-31 11:33:58 +01:00
Florian Rival
edc577067b Add comments to document a risk with ProjectScopedContainers
This is when used with custom objects for editing objects.

Only show in developer changelog
2024-10-31 11:01:41 +01:00
D8H
e00a85909d Replace the "add folder" button by a drop-down menu action (#7117) 2024-10-31 10:51:57 +01:00
D8H
70e6fc7f7f Add a button at the top of the object list to add new objects (#7111) 2024-10-29 16:52:04 +01:00
D8H
b9a899f82e Fix 3D model sizes in the editor when aspect ratio is free (#7115) 2024-10-29 16:46:33 +01:00
Arthur Pacaud (arthuro555)
c38d14ca83 Add gdcore-tools hook (#7112)
Only show in developer changelog
2024-10-29 09:26:48 +01:00
github-actions[bot]
e6b6406a95 Update translations [skip ci] (#7110)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-10-28 09:39:16 +01:00
Florian Rival
ff7c6de660 Fix issues when reworking a quick customization project (#7109)
* Don't duplicate the project multiple times if already saved
* When the game is exported a second time, the game page was not properly updated
2024-10-25 18:34:26 +02:00
github-actions[bot]
8d78ec6070 Update translations [skip ci] (#7104)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-10-25 15:43:53 +02:00
Florian Rival
3c2876e08d Fix adding an object from context menu when the Objects panel is closed (#7107) 2024-10-25 10:21:10 +02:00
Clément Pasteau
a68bac6667 Allow uploading gd.games thumbnail directly from the Game Dashboard (#7106) 2024-10-25 09:39:10 +02:00
Florian Rival
53eafe098c Don't show save reminders in quick customization 2024-10-24 17:17:24 +02:00
AlexandreS
ab519d41a1 Fix instances paste from a scene to another (#7105)
Also:
- Fix instance variable editor opening in CustomObject editor
- Fix tilemap painting in CustomObject editor
2024-10-23 17:53:31 +02:00
D8H
5ea03b83f0 Fix a crash at runtime when an animatable custom object has no animation (#7102) 2024-10-23 15:09:06 +02:00
github-actions[bot]
f2d4778459 Update translations [skip ci] (#7098)
Co-authored-by: ClementPasteau <4895034+ClementPasteau@users.noreply.github.com>
2024-10-23 09:44:39 +02:00
Clément Pasteau
56662fb9b5 Add warnings for variables ownership (#7100)
Do not show in changelog
2024-10-22 18:41:45 +02:00
Clément Pasteau
0788de3d87 UX Improvements across the app (#7099)
* Make New project dialog simpler
* Prevent icons from being always at the end on RTL languages
* Improve number of items displayed on mobile across the app
* Hide tabs in Resources if only 1 is available
2024-10-22 16:29:31 +02:00
AlexandreS
6b7bc361a7 Bump newIDE version (#7097) 2024-10-22 15:05:52 +02:00
github-actions[bot]
32a6e188e7 Update translations [skip ci] (#7088) 2024-10-22 15:03:49 +02:00
D8H
00f67ca7c7 Allow custom objects to use leaderboard properties (#7081) 2024-10-22 14:43:47 +02:00
AlexandreS
bb5291ac6f Improve Max projects callout (#7096)
Don't show in changelog
2024-10-22 11:38:21 +02:00
D8H
a2ea751007 Fix to forbid behavior properties from being used as values in expressions (#7095) 2024-10-21 19:58:32 +02:00
D8H
1a4270195b Fix autocompletion by hiding parameters and properties of type "behavior" (#7094) 2024-10-21 18:46:30 +02:00
D8H
3df42cce3e Fix default parameter of action with operator functions not displaying in the editor (#7093) 2024-10-21 17:48:34 +02:00
D8H
b8de302f7e Optimize custom object layouting in the editor (#7090) 2024-10-21 15:16:06 +02:00
AlexandreS
6a2bc6109c Display Audio Resource parameter field for functions actions (#7092) 2024-10-21 14:54:46 +02:00
D8H
0383f8a7e1 Fix text input visibility when its custom object is hidden (#7089) 2024-10-21 12:41:47 +02:00
D8H
bde7e1896d Fix a crash when an instance with a wrong object name is in a custom object (#7079) 2024-10-21 10:23:46 +02:00
github-actions[bot]
32d855992e Update translations [skip ci] (#7075)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-10-21 09:14:26 +02:00
Florian Rival
d54c1e2f38 Display context menu using an "action sheet" like drawer on mobile/touchscreens (#7087) 2024-10-20 21:47:22 +02:00
D8H
f09a1dd5b2 Fix wrong custom objects duplication when updating an extension (#7086) 2024-10-20 21:26:45 +02:00
Florian Rival
f4e3f2449a Add larger buttons in quick customization and danger buttons in some interfaces 2024-10-19 19:23:46 +02:00
Florian Rival
a6b2cba281 Update wording [skip ci]
Don't show in changelog
2024-10-19 16:37:24 +02:00
Florian Rival
54237114d9 Ensure GDevelop.js build is done using release mode if not specified [skip ci]
Only show in developer changelog
2024-10-19 16:30:23 +02:00
Florian Rival
3c5bcf2762 Add preview analytics to understand if done by new users
Don't show in changelog
2024-10-19 16:27:54 +02:00
D8H
d66ea06a4c Fix resource used in behavior properties not being exported (#7084)
Only show in developer changelog
2024-10-18 18:45:52 +02:00
AlexandreS
228479c81b Fix check on cloud projects count (#7083)
Don't show in changelog
2024-10-18 16:41:15 +02:00
AlexandreS
1e55c359d8 Fix changing the shape painter colors in the properties panel/object editor (#7082) 2024-10-18 14:24:46 +02:00
Clément Pasteau
451d525b36 Fix objects owned by Multiplayer host not being properly migrated when host changes (#7078) 2024-10-17 15:35:41 +02:00
D8H
c755946d42 Add tutorial bubbles on actions replacing deprecated ones (#7077) 2024-10-17 15:12:24 +02:00
D8H
7a6b6fbf7f Update instance renderers when an event-based object is deleted, renamed or pasted (#7076) 2024-10-17 13:22:18 +02:00
github-actions[bot]
730c8283e5 Update translations [skip ci] (#7070)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-10-17 11:14:55 +02:00
D8H
7ea250706c Close custom object tabs when they are deleted from their extension (#7074) 2024-10-17 11:14:26 +02:00
Florian Rival
db7a108354 Reduce time before session analytics to improve bounce detection 2024-10-17 11:06:20 +02:00
AlexandreS
2e15d68bce Change max project warning copy and Add callout in Save to storage provider dialog (#7069)
Don't show in changelog
2024-10-17 11:05:33 +02:00
Florian Rival
f6c9e1408c Bump newIDE version 2024-10-17 00:37:34 +02:00
D8H
0b3d4d048a Fix autocompletion of layers and points for custom object children (#7071) 2024-10-17 00:35:54 +02:00
Clément Pasteau
5d625dd497 Always show instant-build page, even if not linked to a game (#7072) 2024-10-17 00:32:46 +02:00
D8H
079eca829a Fix a regression on the capability check in the editor (#7073)
Don't show in changelog
2024-10-17 00:18:42 +02:00
D8H
35b5f92c59 Fix object lists not always up-to-date in the extension events editor after adding an object in the custom object editor (#7068) 2024-10-16 17:33:35 +02:00
github-actions[bot]
70eb95b132 Update translations [skip ci] (#7064)
Co-authored-by: AlexandreSi <32449369+AlexandreSi@users.noreply.github.com>
2024-10-16 15:15:57 +02:00
AlexandreS
7c7ee8b7fc Warn not enough cloud projects in quick customization flow (#7065) 2024-10-16 14:37:30 +02:00
Clément Pasteau
3556dd2e3c Improvements across the app (#7067)
* Shorter button label for github star
* More in app tutorials on the same row on mobile
* Smaller icon to get app on mobile
* Reduce spacing in Login & Signup dialog to see more
* Redirect store when in a dead end of game templates
* Improve guided lessons descriptions & re-order them
* Allow closing & deleting an opened cloud project
2024-10-16 14:26:26 +02:00
D8H
f74f77f66a Fix custom object editor not closed when the extension is renamed (#7066) 2024-10-16 13:11:25 +02:00
AlexandreS
712eb4b647 Avoid intermittent game crash, due to an audio issue, when resuming the game (when focused back again) (#7063) 2024-10-16 10:57:02 +02:00
D8H
c4474c766d Fix mouse and key parameters for event-functions (#7052) 2024-10-16 09:43:20 +02:00
github-actions[bot]
ad17a21973 Update translations [skip ci] (#7043)
Co-authored-by: 4ian <1280130+4ian@users.noreply.github.com>
2024-10-16 09:41:29 +02:00
D8H
5001411ccb Fix custom object loading when opening a project (#7060) 2024-10-16 09:38:16 +02:00
Florian Rival
05939f5c3e Fix extraction to custom object sometimes using an already used name (#7059) 2024-10-16 09:08:49 +02:00
Florian Rival
91978d4c6e Fix wait action in custom objects (#7056) 2024-10-15 17:15:07 +02:00
AlexandreS
d6433d89f0 Add icons to help understand to scroll during in app tutorial (#7057) 2024-10-15 14:43:37 +02:00
Clément Pasteau
e652ab9f5a Prevent players from claiming a leaderboard score as an anonymous user (#7055) 2024-10-15 14:36:40 +02:00
Florian Rival
e7decc7b92 Fix silent exceptions when resource loading finished after an instance was reset/removed (#7054)
Only show in developer changelog
2024-10-15 12:39:20 +02:00
AlexandreS
33dcc04112 Add support for free trial notification (#7053)
Don't show in changelog
2024-10-14 17:42:44 +02:00
471 changed files with 50966 additions and 9203 deletions

View File

@@ -13,7 +13,6 @@ 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.0.0
jobs:
# Build the **entire** app for macOS.
build-macos:
@@ -175,55 +174,6 @@ jobs:
name: Deploy to S3 (latest)
command: aws s3 sync newIDE/electron-app/dist s3://gdevelop-releases/$(git rev-parse --abbrev-ref HEAD)/latest/
# Build the **entire** app for Windows.
build-windows:
executor:
name: win/default
size: xlarge
steps:
- checkout
- run:
name: Install Node.js
command: nvm install 20; nvm use 20
# TODO: Build GDevelop.js?
- restore_cache:
keys:
- gd-win-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
# fallback to using the latest cache if no exact match is found
- gd-win-nodejs-dependencies---
# GDevelop IDE dependencies.
- run:
name: Install GDevelop IDE dependencies (1)
command: nvm use 20; npm -v; 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-win-nodejs-dependencies-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}
- run:
name: Build the NSIS executable
command: cd newIDE\electron-app; node scripts/build.js --win nsis --publish=never
- run:
name: Build the AppX
command: cd newIDE\electron-app; node scripts/build.js --skip-app-build --win appx --publish=never
- run:
name: Clean artifacts
command: Remove-Item -Path "newIDE\electron-app\dist\win-unpacked" -Recurse -Force
# Upload artifacts (CircleCI)
- store_artifacts:
path: newIDE/electron-app/dist
# Upload artifacts (AWS)
# TODO
# Build the WebAssembly library only (so that it's cached on a S3 and easy to re-use).
build-gdevelop_js-wasm-only:
resource_class: medium+ # Compilation time decrease linearly with the number of CPUs, but not linking (so "large" does not speedup total build time).
@@ -360,7 +310,6 @@ workflows:
- /experimental-build.*/
builds:
jobs:
- build-windows #TODO: filter by branch once it's verified to work.
- build-macos:
filters:
branches:

22
.github/workflows/gdcore-tools-hook.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
# This worflow notifies arthuro555's gdcore-tools repository when a new release is published.
#
# This is used to allow gdcore-tools, a library to use GDCore outside of GDevelop,
# to attempt to automatically build, test, and publish a release for the new
# GDevelop version.
name: Trigger gdcore-tools pipeline
on:
release:
types: [published]
jobs:
dispatch-event:
runs-on: ubuntu-latest
steps:
- name: Repository Dispatch
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.GDCORE_TOOLS_PAT }}
repository: arthuro555/gdcore-tools
event-type: gdevelop-release
client-payload: '{"release": ${{ toJson(github.event.release) }}}'

View File

@@ -338,6 +338,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.AddParameter("expression", _("Camera number (default : 0)"), "", true)
.SetDefaultValue("0");
// Deprecated
extension
.AddCondition(
"PopStartedTouch",
@@ -354,6 +355,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.AddCodeOnlyParameter("currentScene", "")
.SetHidden();
// Deprecated
extension
.AddCondition(
"PopEndedTouch",
@@ -370,6 +372,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
.AddCodeOnlyParameter("currentScene", "")
.SetHidden();
// Deprecated
extension
.AddCondition(
"HasAnyTouchStarted",

View File

@@ -53,27 +53,32 @@ const gd::String &ValueTypeMetadata::GetExpressionPrimitiveValueType(
const gd::String &
ValueTypeMetadata::GetPrimitiveValueType(const gd::String &parameterType) {
if (parameterType == "variable" ||
gd::ValueTypeMetadata::IsTypeExpression("variable", parameterType)) {
return ValueTypeMetadata::variableType;
if (parameterType == "number" ||
gd::ValueTypeMetadata::IsTypeValue("number", parameterType)) {
return ValueTypeMetadata::numberType;
}
if (parameterType == "boolean" || parameterType == "yesorno" ||
parameterType == "trueorfalse") {
return ValueTypeMetadata::booleanType;
}
// These 2 types are not strings from the code generator point of view,
// but it is for event-based extensions.
if (parameterType == "key" || parameterType == "mouse") {
if (parameterType == "string" ||
gd::ValueTypeMetadata::IsTypeValue("string", parameterType)) {
return ValueTypeMetadata::stringType;
}
return GetExpressionPrimitiveValueType(parameterType);
if (parameterType == "variable" ||
gd::ValueTypeMetadata::IsTypeValue("variable", parameterType)) {
return ValueTypeMetadata::variableType;
}
if (parameterType == "boolean" ||
gd::ValueTypeMetadata::IsTypeValue("boolean", parameterType)) {
return ValueTypeMetadata::booleanType;
}
return parameterType;
}
const gd::String ValueTypeMetadata::numberValueType = "number";
const gd::String ValueTypeMetadata::booleanValueType = "boolean";
const gd::String ValueTypeMetadata::stringValueType = "string";
const gd::String ValueTypeMetadata::colorValueType = "color";
const gd::String ValueTypeMetadata::choiceValueType = "stringWithSelector";
const gd::String ValueTypeMetadata::stringValueType = "string";
const gd::String ValueTypeMetadata::behaviorValueType = "behavior";
const gd::String ValueTypeMetadata::leaderboardIdValueType = "leaderboardId";
const gd::String &ValueTypeMetadata::ConvertPropertyTypeToValueType(
const gd::String &propertyType) {
@@ -85,6 +90,10 @@ const gd::String &ValueTypeMetadata::ConvertPropertyTypeToValueType(
return colorValueType;
} else if (propertyType == "Choice") {
return choiceValueType;
} else if (propertyType == "Behavior") {
return behaviorValueType;
} else if (propertyType == "LeaderboardId") {
return leaderboardIdValueType;
}
// For "String" or default
return stringValueType;

View File

@@ -111,21 +111,21 @@ class GD_CORE_API ValueTypeMetadata {
* given type.
*/
bool IsNumber() const {
return gd::ValueTypeMetadata::IsTypeExpression("number", name);
return gd::ValueTypeMetadata::IsTypeValue("number", name);
}
/**
* \brief Return true if the type is a string.
*/
bool IsString() const {
return gd::ValueTypeMetadata::IsTypeExpression("string", name);
return gd::ValueTypeMetadata::IsTypeValue("string", name);
}
/**
* \brief Return true if the type is a boolean.
*/
bool IsBoolean() const {
return gd::ValueTypeMetadata::IsTypeExpression("boolean", name);
return gd::ValueTypeMetadata::IsTypeValue("boolean", name);
}
/**
@@ -135,7 +135,7 @@ class GD_CORE_API ValueTypeMetadata {
* and ExpressionAutocompletion) and in the EventsCodeGenerator.
*/
bool IsVariable() const {
return gd::ValueTypeMetadata::IsTypeExpression("variable", name);
return gd::ValueTypeMetadata::GetPrimitiveValueType(name) == "variable";
}
/**
@@ -175,7 +175,9 @@ class GD_CORE_API ValueTypeMetadata {
}
/**
* \brief Return true if the type is an expression of the given type.
* \brief Return true if the type is an expression of the given type from the
* caller point of view.
*
* \note If you are adding a new type of parameter, also add it in the IDE (
* see EventsFunctionParametersEditor, ParameterRenderingService
* and ExpressionAutocompletion) and in the EventsCodeGenerator.
@@ -186,6 +188,7 @@ class GD_CORE_API ValueTypeMetadata {
return parameterType == "number" || parameterType == "expression" ||
parameterType == "camera" || parameterType == "forceMultiplier";
} else if (type == "string") {
// "key" and "mouse" are not mapped her, see GetPrimitiveValueType.
return parameterType == "string" || parameterType == "layer" ||
parameterType == "color" || parameterType == "file" ||
parameterType == "stringWithSelector" ||
@@ -227,6 +230,26 @@ class GD_CORE_API ValueTypeMetadata {
return false;
}
/**
* \brief Return true if the type is a value of the given primitive type from
* the function events point of view
*/
static bool IsTypeValue(const gd::String &type,
const gd::String &parameterType) {
if (gd::ValueTypeMetadata::IsTypeExpression(type, parameterType)) {
return true;
}
// These 2 parameter types are not strings from the outside of a function as
// the generator add quote around a text, but from the events inside of the
// function the parameter is a string.
//
// See EventsCodeGenerator::GenerateParameterCodes
if (type == "string") {
return parameterType == "key" || parameterType == "mouse";
}
return false;
}
/**
* \brief Return the expression type from the parameter type.
* Declinations of "number" and "string" types (like "forceMultiplier" or
@@ -278,9 +301,11 @@ class GD_CORE_API ValueTypeMetadata {
static const gd::String numberValueType;
static const gd::String booleanValueType;
static const gd::String stringValueType;
static const gd::String colorValueType;
static const gd::String choiceValueType;
static const gd::String stringValueType;
static const gd::String behaviorValueType;
static const gd::String leaderboardIdValueType;
};
} // namespace gd

View File

@@ -0,0 +1,18 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/CaptureOptions.h"
#include "GDCore/String.h"
using namespace std;
namespace gd {
Screenshot::Screenshot() {}
CaptureOptions::CaptureOptions() {}
} // namespace gd

View File

@@ -0,0 +1,50 @@
#pragma once
#include <memory>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class GD_CORE_API Screenshot {
public:
Screenshot();
virtual ~Screenshot() {};
void SetDelayTimeInSeconds(int delayTimeInMs_) {
delayTimeInMs = delayTimeInMs_;
}
int GetDelayTimeInSeconds() const { return delayTimeInMs; }
void SetSignedUrl(const gd::String& signedUrl_) { signedUrl = signedUrl_; }
const gd::String& GetSignedUrl() const { return signedUrl; }
void SetPublicUrl(const gd::String& publicUrl_) { publicUrl = publicUrl_; }
const gd::String& GetPublicUrl() const { return publicUrl; }
private:
int delayTimeInMs = 0;
gd::String signedUrl;
gd::String publicUrl;
};
class GD_CORE_API CaptureOptions {
public:
CaptureOptions();
virtual ~CaptureOptions() {};
bool IsEmpty() const { return screenshots.empty(); }
void AddScreenshot(const Screenshot& screenshot) {
screenshots.push_back(screenshot);
}
const std::vector<Screenshot>& GetScreenshots() const { return screenshots; }
void ClearScreenshots() { screenshots.clear(); }
private:
std::vector<Screenshot> screenshots;
};
} // namespace gd

View File

@@ -1,41 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsLeaderboardsLister.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
namespace gd {
bool EventsLeaderboardsLister::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const gd::InstructionMetadata& instrInfo =
isCondition ? MetadataProvider::GetConditionMetadata(
project.GetCurrentPlatform(), instruction.GetType())
: MetadataProvider::GetActionMetadata(
project.GetCurrentPlatform(), instruction.GetType());
for (int i = 0; i < instruction.GetParametersCount() &&
i < instrInfo.GetParametersCount();
++i)
if (instrInfo.GetParameter(i).GetType() == "leaderboardId") {
leaderboardIds.insert(instruction.GetParameter(i).GetPlainString());
}
return false;
}
EventsLeaderboardsLister::~EventsLeaderboardsLister() {}
} // namespace gd

View File

@@ -1,48 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EventsLeaderboardsLister_H
#define EventsLeaderboardsLister_H
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class Project;
class EventsList;
}
namespace gd {
/**
* \brief List the leaderboard ids in the instructions.
*
* \ingroup IDE
*/
class GD_CORE_API EventsLeaderboardsLister : public ArbitraryEventsWorker {
public:
EventsLeaderboardsLister(gd::Project& project_) : project(project_){};
virtual ~EventsLeaderboardsLister();
/**
* Return the values of all leaderboardIds found in the events.
*/
const std::set<gd::String>& GetLeaderboardIds() { return leaderboardIds; }
private:
virtual bool DoVisitInstruction(gd::Instruction& instruction,
bool isCondition);
std::set<gd::String> leaderboardIds;
gd::Project& project;
};
} // namespace gd
#endif // EventsLeaderboardsLister_H

View File

@@ -1,49 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsLeaderboardsRenamer.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
namespace gd {
bool EventsLeaderboardsRenamer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const gd::InstructionMetadata& instrInfo =
isCondition ? MetadataProvider::GetConditionMetadata(
project.GetCurrentPlatform(), instruction.GetType())
: MetadataProvider::GetActionMetadata(
project.GetCurrentPlatform(), instruction.GetType());
for (int i = 0; i < instruction.GetParametersCount() &&
i < instrInfo.GetParametersCount();
++i) {
const gd::ParameterMetadata parameter = instrInfo.GetParameter(i);
if (parameter.GetType() == "leaderboardId") {
const gd::String leaderboardId =
instruction.GetParameter(i).GetPlainString();
if (leaderboardIdMap.find(leaderboardId) != leaderboardIdMap.end()) {
instruction.SetParameter(i, leaderboardIdMap[leaderboardId]);
}
}
}
return false;
}
EventsLeaderboardsRenamer::~EventsLeaderboardsRenamer() {}
} // namespace gd

View File

@@ -1,46 +0,0 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EventsLeaderboardsRenamer_H
#define EventsLeaderboardsRenamer_H
#include <map>
#include <memory>
#include <set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class Project;
class EventsList;
}
namespace gd {
/**
* \brief Replace the leaderboard ids in the instructions.
*
* \ingroup IDE
*/
class GD_CORE_API EventsLeaderboardsRenamer : public ArbitraryEventsWorker {
public:
EventsLeaderboardsRenamer(
gd::Project& project_,
const std::map<gd::String, gd::String>& leaderboardIdMap_)
: project(project_), leaderboardIdMap(leaderboardIdMap_){};
virtual ~EventsLeaderboardsRenamer();
private:
virtual bool DoVisitInstruction(gd::Instruction& instruction,
bool isCondition);
std::map<gd::String, gd::String> leaderboardIdMap;
gd::Project& project;
};
} // namespace gd
#endif // EventsLeaderboardsRenamer_H

View File

@@ -1066,8 +1066,8 @@ class GD_CORE_API ExpressionCompletionFinder
bool eagerlyCompleteIfExactMatch = false) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
const ObjectConfiguration* objectConfiguration) {
[&](const gd::String &objectName,
const ObjectConfiguration *objectConfiguration) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Object,
location.GetStartPosition(),
@@ -1077,7 +1077,7 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetType(type);
completions.push_back(description);
},
[&](const gd::String& variableName, const gd::Variable& variable) {
[&](const gd::String &variableName, const gd::Variable &variable) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
@@ -1095,23 +1095,29 @@ class GD_CORE_API ExpressionCompletionFinder
variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Property,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(property.GetName());
description.SetType(property.GetType());
completions.push_back(description);
[&](const gd::NamedPropertyDescriptor &property) {
auto propertyType = gd::ValueTypeMetadata::ConvertPropertyTypeToValueType(
property.GetType());
if (gd::ValueTypeMetadata::IsTypeValue("number", propertyType) ||
gd::ValueTypeMetadata::IsTypeValue("string", propertyType)) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Property,
location.GetStartPosition(), location.GetEndPosition());
description.SetCompletion(property.GetName());
description.SetType(property.GetType());
completions.push_back(description);
}
},
[&](const gd::ParameterMetadata& parameter) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Parameter,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(parameter.GetName());
description.SetType(parameter.GetType());
completions.push_back(description);
[&](const gd::ParameterMetadata &parameter) {
if (parameter.GetValueTypeMetadata().IsNumber() ||
parameter.GetValueTypeMetadata().IsString()) {
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Parameter,
location.GetStartPosition(), location.GetEndPosition());
description.SetCompletion(parameter.GetName());
description.SetType(parameter.GetType());
completions.push_back(description);
}
});
}

View File

@@ -72,7 +72,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
child(nullptr) {};
const gd::String &GetType() {
return gd::ParameterMetadata::GetExpressionValueType(type);
return gd::ValueTypeMetadata::GetExpressionPrimitiveValueType(type);
};
void OnVisitSubExpressionNode(SubExpressionNode& node) override {

View File

@@ -68,19 +68,26 @@ size_t GetMaximumParametersNumber(
bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const gd::IdentifierNode& identifier) {
return ValidateObjectVariableOrVariableOrProperty(identifier.identifierName, identifier.identifierNameLocation, identifier.childIdentifierName, identifier.childIdentifierNameLocation);
}
bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const gd::String &identifierName,
const gd::ExpressionParserLocation identifierNameLocation,
const gd::String &childIdentifierName,
const gd::ExpressionParserLocation childIdentifierNameLocation) {
auto validateVariableTypeForExpression =
[this, &identifier](gd::Variable::Type type) {
[this, &identifierNameLocation](gd::Variable::Type type) {
// Collections type can't be used directly in expressions, a child
// must be accessed.
if (type == Variable::Structure) {
RaiseTypeError(_("You need to specify the name of the child variable "
"to access. For example: `MyVariable.child`."),
identifier.identifierNameLocation);
identifierNameLocation);
} else if (type == Variable::Array) {
RaiseTypeError(_("You need to specify the name of the child variable "
"to access. For example: `MyVariable[0]`."),
identifier.identifierNameLocation);
identifierNameLocation);
} else {
// Number, string or boolean variables can be used in expressions.
return;
@@ -96,38 +103,41 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
// we consider this node will be of the type required by the parent.
childType = parentType;
return projectScopedContainers.MatchIdentifierWithName<bool>(identifier.identifierName,
return projectScopedContainers.MatchIdentifierWithName<bool>(identifierName,
[&]() {
// This represents an object.
if (identifier.childIdentifierName.empty()) {
if (childIdentifierName.empty()) {
RaiseTypeError(_("An object variable or expression should be entered."),
identifier.identifierNameLocation);
identifierNameLocation);
return true; // We should have found a variable.
}
auto variableExistence = objectsContainersList.HasObjectOrGroupWithVariableNamed(identifier.identifierName, identifier.childIdentifierName);
auto variableExistence =
objectsContainersList.HasObjectOrGroupWithVariableNamed(
identifierName, childIdentifierName);
if (variableExistence == gd::ObjectsContainersList::DoesNotExist) {
RaiseUndeclaredVariableError(_("This variable does not exist on this object or group."),
identifier.childIdentifierNameLocation, identifier.childIdentifierName, identifier.identifierName);
childIdentifierNameLocation, childIdentifierName, identifierName);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::ExistsOnlyOnSomeObjectsOfTheGroup) {
RaiseUndeclaredVariableError(_("This variable only exists on some objects of the group. It must be declared for all objects."),
identifier.childIdentifierNameLocation, identifier.childIdentifierName, identifier.identifierName);
childIdentifierNameLocation, childIdentifierName, identifierName);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::GroupIsEmpty) {
RaiseUndeclaredVariableError(_("This group is empty. Add an object to this group first."),
identifier.identifierNameLocation, identifier.childIdentifierName, identifier.identifierName);
identifierNameLocation, childIdentifierName, identifierName);
return true; // We should have found a variable.
}
auto variableType = objectsContainersList.GetTypeOfObjectOrGroupVariable(identifier.identifierName, identifier.childIdentifierName);
auto variableType = objectsContainersList.GetTypeOfObjectOrGroupVariable(
identifierName, childIdentifierName);
ReadChildTypeFromVariable(variableType);
return true; // We found a variable.
@@ -137,9 +147,9 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
// Try to identify a declared variable with the name (and maybe the child
// variable).
const gd::Variable& variable =
variablesContainersList.Get(identifier.identifierName);
variablesContainersList.Get(identifierName);
if (identifier.childIdentifierName.empty()) {
if (childIdentifierName.empty()) {
// Just the root variable is accessed, check it can be used in an
// expression.
validateVariableTypeForExpression(variable.GetType());
@@ -148,33 +158,38 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
return true; // We found a variable.
} else {
// A child variable is accessed, check it can be used in an expression.
if (!variable.HasChild(identifier.childIdentifierName)) {
if (!variable.HasChild(childIdentifierName)) {
RaiseTypeError(_("No child variable with this name found."),
identifier.childIdentifierNameLocation);
childIdentifierNameLocation);
return true; // We should have found a variable.
}
const gd::Variable& childVariable =
variable.GetChild(identifier.childIdentifierName);
variable.GetChild(childIdentifierName);
ReadChildTypeFromVariable(childVariable.GetType());
return true; // We found a variable.
}
}, [&]() {
// This is a property.
if (!identifier.childIdentifierName.empty()) {
if (!childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
identifier.childIdentifierNameLocation);
childIdentifierNameLocation);
return true; // We found a property, even if the child is not allowed.
}
const gd::NamedPropertyDescriptor& property = propertiesContainersList.Get(identifier.identifierName).second;
const gd::NamedPropertyDescriptor &property =
propertiesContainersList.Get(identifierName).second;
if (property.GetType() == "Number") {
childType = Type::Number;
childType = Type::Number;
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
// Nothing - we don't know the precise type (this could be used a string
// or as a number)
} else if (property.GetType() == "Behavior") {
RaiseTypeError(_("Behaviors can't be used as a value in expressions."),
identifierNameLocation);
} else {
// Assume type is String or equivalent.
childType = Type::String;
@@ -183,14 +198,14 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
return true; // We found a property.
}, [&]() {
// This is a parameter.
if (!identifier.childIdentifierName.empty()) {
if (!childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
identifier.childIdentifierNameLocation);
childIdentifierNameLocation);
return true; // We found a parameter, even if the child is not allowed.
}
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, identifier.identifierName);
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (valueTypeMetadata.IsNumber()) {
childType = Type::Number;
@@ -200,7 +215,7 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
RaiseTypeError(_("This parameter is not a string, number or boolean - it can't be used in an expression."),
identifier.identifierNameLocation);
identifierNameLocation);
return true; // We found a parameter, even though the type is incompatible.
}

View File

@@ -14,6 +14,7 @@
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/VariablesContainersList.h"
#include "GDCore/Project/VariablesContainer.h"
namespace gd {
class Expression;
@@ -42,10 +43,12 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
const gd::String &extraInfo_ = "")
: platform(platform_),
projectScopedContainers(projectScopedContainers_),
parentType(StringToType(gd::ParameterMetadata::GetExpressionValueType(rootType_))),
parentType(StringToType(gd::ValueTypeMetadata::GetExpressionPrimitiveValueType(rootType_))),
childType(Type::Unknown),
forbidsUsageOfBracketsBecauseParentIsObject(false),
currentParameterExtraInfo(&extraInfo_) {};
currentParameterExtraInfo(&extraInfo_),
variableObjectName(),
variableObjectNameLocation() {};
virtual ~ExpressionValidator(){};
/**
@@ -225,7 +228,8 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
projectScopedContainers.MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
variableObjectName = node.name;
variableObjectNameLocation = node.nameLocation;
// While understood by the parser, it's forbidden to use the bracket notation just after
// an object name (`MyObject["MyVariable"]`).
forbidsUsageOfBracketsBecauseParentIsObject = true;
@@ -264,7 +268,13 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
ReportAnyError(node);
// TODO Also check child-variables existence on a path with only VariableAccessor to raise non-fatal errors.
if (!variableObjectName.empty()) {
ValidateObjectVariableOrVariableOrProperty(variableObjectName,
variableObjectNameLocation,
node.name, node.nameLocation);
variableObjectName = "";
}
// In the case we accessed an object variable (`MyObject.MyVariable`),
// brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
forbidsUsageOfBracketsBecauseParentIsObject = false;
@@ -277,6 +287,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
VariableBracketAccessorNode& node) override {
ReportAnyError(node);
variableObjectName = "";
if (forbidsUsageOfBracketsBecauseParentIsObject) {
RaiseError(gd::ExpressionParserError::ErrorType::BracketsNotAllowedForObjects,
_("You can't use the brackets to access an object variable. "
@@ -369,6 +380,11 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
enum Type {Unknown = 0, Number, String, NumberOrString, Variable, LegacyVariable, Object, Empty};
Type ValidateFunction(const gd::FunctionCallNode& function);
bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
bool ValidateObjectVariableOrVariableOrProperty(
const gd::String &identifierName,
const gd::ExpressionParserLocation identifierNameLocation,
const gd::String &childIdentifierName,
const gd::ExpressionParserLocation childIdentifierNameLocation);
void CheckVariableExistence(const ExpressionParserLocation &location, const gd::String& name) {
if (!currentParameterExtraInfo || *currentParameterExtraInfo != "AllowUndeclaredVariable") {
@@ -505,6 +521,8 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
Type childType; ///< The type "discovered" down the tree and passed up.
Type parentType; ///< The type "required" by the top of the tree.
bool forbidsUsageOfBracketsBecauseParentIsObject;
gd::String variableObjectName;
gd::ExpressionParserLocation variableObjectNameLocation;
const gd::String *currentParameterExtraInfo;
const gd::Platform &platform;
const gd::ProjectScopedContainers &projectScopedContainers;

View File

@@ -0,0 +1,76 @@
#include "LeaderboardIdRenamer.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/String.h"
namespace gd {
void LeaderboardIdRenamer::DoVisitObject(gd::Object &object) {
for (auto &pair : object.GetConfiguration().GetProperties()) {
auto &propertyName = pair.first;
auto &property = pair.second;
if (property.GetType() == "LeaderboardId") {
auto &leaderboardId = property.GetValue();
allLeaderboardIds.insert(leaderboardId);
if (leaderboardIdMap.find(leaderboardId) != leaderboardIdMap.end()) {
object.GetConfiguration().UpdateProperty(
propertyName, leaderboardIdMap[leaderboardId]);
}
}
}
};
void LeaderboardIdRenamer::DoVisitBehavior(gd::Behavior &behavior) {};
bool LeaderboardIdRenamer::DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) {
const gd::InstructionMetadata &instrInfo =
isCondition ? MetadataProvider::GetConditionMetadata(
project.GetCurrentPlatform(), instruction.GetType())
: MetadataProvider::GetActionMetadata(
project.GetCurrentPlatform(), instruction.GetType());
for (int i = 0; i < instruction.GetParametersCount() &&
i < instrInfo.GetParametersCount();
++i) {
const gd::ParameterMetadata parameter = instrInfo.GetParameter(i);
if (parameter.GetType() != "leaderboardId") {
continue;
}
const gd::String leaderboardIdExpression =
instruction.GetParameter(i).GetPlainString();
if (leaderboardIdExpression[0] != '"' ||
leaderboardIdExpression[leaderboardIdExpression.size() - 1] != '"') {
continue;
}
const gd::String leaderboardId =
leaderboardIdExpression.substr(1, leaderboardIdExpression.size() - 2);
allLeaderboardIds.insert(leaderboardId);
if (leaderboardIdMap.find(leaderboardId) != leaderboardIdMap.end()) {
instruction.SetParameter(i,
"\"" + leaderboardIdMap[leaderboardId] + "\"");
}
}
return false;
}
LeaderboardIdRenamer::~LeaderboardIdRenamer() {}
} // namespace gd

View File

@@ -0,0 +1,50 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <map>
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class Object;
class Behavior;
} // namespace gd
namespace gd {
class GD_CORE_API LeaderboardIdRenamer : public ArbitraryObjectsWorker, public ArbitraryEventsWorker {
public:
LeaderboardIdRenamer(gd::Project& project_): project(project_) {};
virtual ~LeaderboardIdRenamer();
/**
* Set the leaderboard identifiers to be replaced.
*/
void SetLeaderboardIdsToReplace(const std::map<gd::String, gd::String>& leaderboardIdMap_) {
leaderboardIdMap = leaderboardIdMap_;
}
/**
* Return the all the leaderboard identifiers found in the project.
*/
const std::set<gd::String>& GetAllLeaderboardIds() const {
return allLeaderboardIds;
}
private:
bool DoVisitInstruction(gd::Instruction& instruction, bool isCondition) override;
void DoVisitObject(gd::Object& object) override;
void DoVisitBehavior(gd::Behavior& behavior) override;
std::map<gd::String, gd::String> leaderboardIdMap;
std::set<gd::String> allLeaderboardIds;
gd::Project& project;
};
}; // namespace gd

View File

@@ -293,7 +293,7 @@ void ResourceWorkerInObjectsWorker::DoVisitObject(gd::Object &object) {
};
void ResourceWorkerInObjectsWorker::DoVisitBehavior(gd::Behavior &behavior){
// TODO Allow behaviors to expose resources
behavior.ExposeResources(worker);
};
gd::ResourceWorkerInObjectsWorker

View File

@@ -247,7 +247,7 @@ bool PropertyFunctionGenerator::CanGenerateGetterAndSetter(
const gd::NamedPropertyDescriptor &property) {
auto &type = property.GetType();
if (type != "Boolean" && type != "Number" && type != "String" &&
type != "Choice" && type != "Color") {
type != "Choice" && type != "Color" && type != "LeaderboardId") {
return false;
}

View File

@@ -28,6 +28,7 @@
#include "GDCore/IDE/Events/InstructionsParameterMover.h"
#include "GDCore/IDE/Events/InstructionsTypeRenamer.h"
#include "GDCore/IDE/Events/LinkEventTargetRenamer.h"
#include "GDCore/IDE/Events/LeaderboardIdRenamer.h"
#include "GDCore/IDE/Events/ProjectElementRenamer.h"
#include "GDCore/IDE/Project/BehaviorObjectTypeRenamer.h"
#include "GDCore/IDE/Project/BehaviorsSharedDataBehaviorTypeRenamer.h"
@@ -949,6 +950,8 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
// Order is important: we first rename the expressions then the
// instructions, to avoid being unable to fetch the metadata (the types of
// parameters) of instructions after they are renamed.
// Rename legacy expressions like: Object.Behavior::PropertyMyPropertyName()
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedBehaviorExpression(
@@ -958,14 +961,16 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
// Rename property names directly used as an identifier.
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsPropertyReplacer);
gd::ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
project, eventsFunctionsExtension, eventsBasedBehavior,
eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -1020,6 +1025,8 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
// Order is important: we first rename the expressions then the
// instructions, to avoid being unable to fetch the metadata (the types of
// parameters) of instructions after they are renamed.
// Rename legacy expressions like: Object.Behavior::SharedPropertyMyPropertyName()
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedBehaviorExpression(
@@ -1029,14 +1036,16 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
EventsBasedBehavior::GetSharedPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
// Rename property names directly used as an identifier.
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsPropertyReplacer);
gd::ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
project, eventsFunctionsExtension, eventsBasedBehavior,
eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -1077,6 +1086,8 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
// Order is important: we first rename the expressions then the
// instructions, to avoid being unable to fetch the metadata (the types of
// parameters) of instructions after they are renamed.
// Rename legacy expressions like: Object.PropertyMyPropertyName()
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedObjectExpression(
@@ -1086,14 +1097,16 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
EventsBasedObject::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
// Rename property names directly used as an identifier.
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {
{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(), properties, oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsPropertyReplacer);
gd::ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
project, eventsFunctionsExtension, eventsBasedObject,
eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
@@ -2144,4 +2157,23 @@ std::vector<gd::String> WholeProjectRefactorer::GetAssociatedExternalEvents(
return results;
}
void WholeProjectRefactorer::RenameLeaderboards(
gd::Project &project,
const std::map<gd::String, gd::String> &leaderboardIdMap) {
gd::LeaderboardIdRenamer leaderboardIdRenamer(project);
leaderboardIdRenamer.SetLeaderboardIdsToReplace(leaderboardIdMap);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, leaderboardIdRenamer);
gd::ProjectBrowserHelper::ExposeProjectObjects(project, leaderboardIdRenamer);
}
std::set<gd::String> WholeProjectRefactorer::FindAllLeaderboardIds(gd::Project &project) {
gd::LeaderboardIdRenamer leaderboardIdRenamer(project);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, leaderboardIdRenamer);
gd::ProjectBrowserHelper::ExposeProjectObjects(project, leaderboardIdRenamer);
return leaderboardIdRenamer.GetAllLeaderboardIds();
}
} // namespace gd

View File

@@ -627,6 +627,18 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String &originLayerName,
const gd::String &targetLayerName);
/**
* \brief Replace the leaderboard ids in the whole project.
*/
static void
RenameLeaderboards(gd::Project &project,
const std::map<gd::String, gd::String> &leaderboardIdMap);
/**
* \brief Find all the leaderboard identifiers in the whole project.
*/
static std::set<gd::String> FindAllLeaderboardIds(gd::Project &project);
/**
* \brief Return the number of instances on the layer named \a layerName and
* all its associated layouts.

View File

@@ -6,6 +6,7 @@
#include "GDCore/Project/BehaviorConfigurationContainer.h"
#include <iostream>
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
namespace gd {
@@ -22,4 +23,48 @@ std::map<gd::String, gd::PropertyDescriptor> BehaviorConfigurationContainer::Get
return nothing;
}
void BehaviorConfigurationContainer::ExposeResources(gd::ArbitraryResourceWorker& worker) {
std::map<gd::String, gd::PropertyDescriptor> properties = GetProperties();
for (auto& property : properties) {
const String& propertyName = property.first;
const gd::PropertyDescriptor& propertyDescriptor = property.second;
if (propertyDescriptor.GetType().LowerCase() == "resource") {
auto& extraInfo = propertyDescriptor.GetExtraInfo();
const gd::String& resourceType = extraInfo.empty() ? "" : extraInfo[0];
const gd::String& oldPropertyValue = propertyDescriptor.GetValue();
gd::String newPropertyValue = oldPropertyValue;
if (resourceType == "image") {
worker.ExposeImage(newPropertyValue);
} else if (resourceType == "audio") {
worker.ExposeAudio(newPropertyValue);
} else if (resourceType == "font") {
worker.ExposeFont(newPropertyValue);
} else if (resourceType == "video") {
worker.ExposeVideo(newPropertyValue);
} else if (resourceType == "json") {
worker.ExposeJson(newPropertyValue);
} else if (resourceType == "tilemap") {
worker.ExposeTilemap(newPropertyValue);
} else if (resourceType == "tileset") {
worker.ExposeTileset(newPropertyValue);
} else if (resourceType == "bitmapFont") {
worker.ExposeBitmapFont(newPropertyValue);
} else if (resourceType == "model3D") {
worker.ExposeModel3D(newPropertyValue);
} else if (resourceType == "atlas") {
worker.ExposeAtlas(newPropertyValue);
} else if (resourceType == "spine") {
worker.ExposeSpine(newPropertyValue);
}
if (newPropertyValue != oldPropertyValue) {
UpdateProperty(propertyName, newPropertyValue);
}
}
}
}
} // namespace gd

View File

@@ -3,8 +3,7 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_BEHAVIORCONFIGURATIONCONTAINER_H
#define GDCORE_BEHAVIORCONFIGURATIONCONTAINER_H
#pragma once
#include <map>
#include <memory>
@@ -19,6 +18,7 @@ class PropertyDescriptor;
class SerializerElement;
class Project;
class Layout;
class ArbitraryResourceWorker;
} // namespace gd
namespace gd {
@@ -158,6 +158,18 @@ class GD_CORE_API BehaviorConfigurationContainer {
return propertiesQuickCustomizationVisibilities;
}
/**
* \brief Called ( e.g. during compilation ) so as to inventory internal
* resources and sometimes update their filename. Implementation example:
* \code
* worker.ExposeImage(myImage);
* worker.ExposeFile(myResourceFile);
* \endcode
*
* \see ArbitraryResourceWorker
*/
void ExposeResources(gd::ArbitraryResourceWorker& worker);
protected:
/**
* \brief Called when the IDE wants to know about the custom properties of the
@@ -209,5 +221,3 @@ class GD_CORE_API BehaviorConfigurationContainer {
};
} // namespace gd
#endif // GDCORE_BEHAVIORCONFIGURATIONCONTAINER_H

View File

@@ -26,7 +26,7 @@ void CustomConfigurationHelper::InitializeContent(
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "Resource") {
propertyType == "Resource" || propertyType == "LeaderboardId") {
element.SetStringValue(property->GetValue());
} else if (propertyType == "Number") {
element.SetDoubleValue(property->GetValue().To<double>());
@@ -53,7 +53,7 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
if (configurationContent.HasChild(propertyName)) {
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "Resource") {
propertyType == "Resource" || propertyType == "LeaderboardId") {
newProperty.SetValue(
configurationContent.GetChild(propertyName).GetStringValue());
} else if (propertyType == "Number") {
@@ -89,7 +89,7 @@ bool CustomConfigurationHelper::UpdateProperty(
if (propertyType == "String" || propertyType == "Choice" ||
propertyType == "Color" || propertyType == "Behavior" ||
propertyType == "Resource") {
propertyType == "Resource" || propertyType == "LeaderboardId") {
element.SetStringValue(newValue);
} else if (propertyType == "Number") {
element.SetDoubleValue(newValue.To<double>());

View File

@@ -72,7 +72,7 @@ gd::ObjectConfiguration &CustomObjectConfiguration::GetChildObjectConfiguration(
}
if (!eventsBasedObject->GetObjects().HasObjectNamed(objectName)) {
gd::LogError("Tried to get the configuration of a child-object:" + objectName
gd::LogError("Tried to get the configuration of a child-object: " + objectName
+ " that doesn't exist in the event-based object: " + GetType());
return badObjectConfiguration;
}
@@ -158,7 +158,9 @@ bool CustomObjectConfiguration::UpdateInitialInstanceProperty(
void CustomObjectConfiguration::DoSerializeTo(SerializerElement& element) const {
element.AddChild("content") = objectContent;
if (!animations.HasNoAnimations()) {
const auto *eventsBasedObject = GetEventsBasedObject();
if (!animations.HasNoAnimations() ||
(eventsBasedObject && eventsBasedObject->IsAnimatable())) {
auto &animatableElement = element.AddChild("animatable");
animations.SerializeTo(animatableElement);
}
@@ -202,8 +204,8 @@ void CustomObjectConfiguration::ExposeResources(gd::ArbitraryResourceWorker& wor
for (auto& property : properties) {
const String& propertyName = property.first;
const gd::PropertyDescriptor& propertyDescriptor = property.second;
if (propertyDescriptor.GetType() == "resource") {
const gd::PropertyDescriptor &propertyDescriptor = property.second;
if (propertyDescriptor.GetType().LowerCase() == "resource") {
auto& extraInfo = propertyDescriptor.GetExtraInfo();
const gd::String& resourceType = extraInfo.empty() ? "" : extraInfo[0];
const gd::String& oldPropertyValue = propertyDescriptor.GetValue();

View File

@@ -10,6 +10,7 @@
#include "EventsFunction.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Extensions/PlatformExtension.h"
namespace gd {
@@ -98,8 +99,11 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
void EventsFunctionsExtension::UnserializeFrom(
gd::Project& project, const SerializerElement& element) {
UnserializeExtensionDeclarationFrom(project, element);
UnserializeExtensionImplementationFrom(project, element);
// Unserialize first the "declaration" (everything but objects content)
// so that objects can be then unserialized in proper order (they can depend
// on each others)
UnserializeExtensionDeclarationFrom(project, element);
UnserializeExtensionImplementationFrom(project, element);
}
void EventsFunctionsExtension::UnserializeExtensionDeclarationFrom(
@@ -162,6 +166,7 @@ void EventsFunctionsExtension::UnserializeExtensionDeclarationFrom(
// As event based objects can contains objects using CustomBehavior and/or
// CustomObject, this allows them to reference EventBasedBehavior and
// EventBasedObject respectively.
eventsBasedBehaviors.Clear();
auto &behaviorsElement = element.GetChild("eventsBasedBehaviors");
behaviorsElement.ConsiderAsArrayOf("eventsBasedBehavior");
for (std::size_t i = 0; i < behaviorsElement.GetChildrenCount(); ++i) {
@@ -169,6 +174,7 @@ void EventsFunctionsExtension::UnserializeExtensionDeclarationFrom(
behaviorsElement.GetChild(i).GetStringAttribute("name");
eventsBasedBehaviors.InsertNew(behaviorName, eventsBasedBehaviors.GetCount());
}
eventsBasedObjects.Clear();
auto &objectsElement = element.GetChild("eventsBasedObjects");
objectsElement.ConsiderAsArrayOf("eventsBasedObject");
for (std::size_t i = 0; i < objectsElement.GetChildrenCount(); ++i) {
@@ -185,11 +191,93 @@ void EventsFunctionsExtension::UnserializeExtensionImplementationFrom(
eventsBasedBehaviors.UnserializeElementsFrom(
"eventsBasedBehavior", project, element.GetChild("eventsBasedBehaviors"));
// It's important to load the objects without erasing them first as each object
// might reference other objects, and so need to know if a custom object exists
// in the project or not.
eventsBasedObjects.ProgressivelyUnserializeElementsFrom(
"eventsBasedObject", project, element.GetChild("eventsBasedObjects"));
auto &eventsBasedObjectsElement = element.GetChild("eventsBasedObjects");
eventsBasedObjectsElement.ConsiderAsArrayOf("eventsBasedObject");
for (gd::String &eventsBasedObjectName :
GetUnserializingOrderEventsBasedObjectNames(eventsBasedObjectsElement)) {
size_t extensionIndex = eventsBasedObjects.GetPosition(
eventsBasedObjects.Get(eventsBasedObjectName));
const SerializerElement &eventsBasedObjectElement =
eventsBasedObjectsElement.GetChild(extensionIndex);
eventsBasedObjects.at(extensionIndex)
.UnserializeFrom(project, eventsBasedObjectElement);
}
}
std::vector<gd::String>
EventsFunctionsExtension::GetUnserializingOrderEventsBasedObjectNames(
const gd::SerializerElement &eventsBasedObjectsElement) {
// Child-objects need the event-based objects they use to be loaded completely
// before they are unserialized.
// At the beginning, everything is yet to be loaded.
std::vector<gd::String> remainingEventsBasedObjectNames(
eventsBasedObjects.size());
for (std::size_t i = 0; i < eventsBasedObjects.size(); ++i) {
remainingEventsBasedObjectNames[i] = eventsBasedObjects.at(i).GetName();
}
// Helper allowing to find if an object depends on at least one other object from
// the extension that is not loaded yet.
auto &extensionName = name;
auto isDependentFromRemainingEventsBasedObjects =
[&remainingEventsBasedObjectNames,
&extensionName](const gd::SerializerElement &eventsBasedObjectElement) {
auto &objectsElement = eventsBasedObjectElement.GetChild("objects");
objectsElement.ConsiderAsArrayOf("object");
for (std::size_t objectIndex = 0;
objectIndex < objectsElement.GetChildrenCount(); ++objectIndex) {
const gd::String &objectType =
objectsElement.GetChild(objectIndex).GetStringAttribute("type");
gd::String usedExtensionName =
PlatformExtension::GetExtensionFromFullObjectType(objectType);
if (usedExtensionName != extensionName) {
// The object comes from another extension: the project is already responsible
// for loading extensions in the proper order.
continue;
}
gd::String eventsBasedObjectName =
gd::PlatformExtension::GetObjectNameFromFullObjectType(
objectType);
if (std::find(remainingEventsBasedObjectNames.begin(),
remainingEventsBasedObjectNames.end(),
eventsBasedObjectName) !=
remainingEventsBasedObjectNames.end()) {
return true;
}
}
return false;
};
// Find the order of loading so that the objects are loaded when all the objects
// they depend on are already loaded.
std::vector<gd::String> loadOrderEventsBasedObjectNames;
bool foundAnyEventsBasedObject = true;
while (foundAnyEventsBasedObject) {
foundAnyEventsBasedObject = false;
for (std::size_t i = 0; i < remainingEventsBasedObjectNames.size(); ++i) {
auto eventsBasedObjectName = remainingEventsBasedObjectNames[i];
size_t extensionIndex = eventsBasedObjects.GetPosition(
eventsBasedObjects.Get(eventsBasedObjectName));
const SerializerElement &eventsBasedObjectElement =
eventsBasedObjectsElement.GetChild(extensionIndex);
if (!isDependentFromRemainingEventsBasedObjects(
eventsBasedObjectElement)) {
loadOrderEventsBasedObjectNames.push_back(eventsBasedObjectName);
remainingEventsBasedObjectNames.erase(
remainingEventsBasedObjectNames.begin() + i);
i--;
foundAnyEventsBasedObject = true;
}
}
}
return loadOrderEventsBasedObjectNames;
}
bool EventsFunctionsExtension::IsExtensionLifecycleEventsFunction(

View File

@@ -314,6 +314,9 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
return dependency;
}
std::vector<gd::String> GetUnserializingOrderEventsBasedObjectNames(
const gd::SerializerElement &eventsBasedObjectsElement);
gd::String version;
gd::String extensionNamespace;
gd::String shortDescription;

View File

@@ -530,6 +530,32 @@ std::vector<gd::String> ObjectsContainersList::GetBehaviorsOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetBehaviorNamesInObjectOrGroup(
const gd::String &objectOrGroupName, const gd::String &behaviorType, bool searchInGroups) const {
if (objectsContainers.size() > 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetBehaviorNamesInObjectOrGroup called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
if (objectsContainers.size() == 0) {
gd::LogWarning("ObjectsContainersList::GetBehaviorNamesInObjectOrGroup called without any "
"objectsContainer");
std::vector<gd::String> behaviors;
return behaviors;
}
if (objectsContainers.size() == 1) {
gd::ObjectsContainer emptyObjectsContainer;
return gd::GetBehaviorNamesInObjectOrGroup(emptyObjectsContainer,
*objectsContainers[0], objectOrGroupName, behaviorType,
searchInGroups);
}
return gd::GetBehaviorNamesInObjectOrGroup(
*objectsContainers[0], *objectsContainers[1], objectOrGroupName, behaviorType, searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetAnimationNamesOfObject(
const gd::String &objectOrGroupName) const {
std::vector<gd::String> animationNames;

View File

@@ -50,6 +50,11 @@ class GD_CORE_API ObjectsContainersList {
*/
bool HasObjectOrGroupNamed(const gd::String& name) const;
/**
* \brief Check if the specified object exists ignoring groups.
*/
bool HasObjectNamed(const gd::String& name) const;
enum VariableExistence {
DoesNotExist,
Exists,
@@ -128,6 +133,18 @@ class GD_CORE_API ObjectsContainersList {
std::vector<gd::String> GetBehaviorsOfObject(
const gd::String& objectName, bool searchInGroups = true) const;
/**
* \brief Get behaviors of an object/group of a given behavior type.
* \note The behaviors of a group are the behaviors which are found in common
* when looking all the objects of the group.
*
* @return Vector containing names of behaviors
*/
std::vector<gd::String>
GetBehaviorNamesInObjectOrGroup(const gd::String &objectOrGroupName,
const gd::String &behaviorType,
bool searchInGroups = true) const;
/**
* \brief Get the animation names of an object/group.
* \note The animation names of a group are the animation names common to
@@ -175,6 +192,13 @@ class GD_CORE_API ObjectsContainersList {
/**
* \brief Return a the objects container at position \a index.
*
* \warning The returned `ObjectsContainer` may contain cloned objects (in the case of
* `ProjectScopedContainers::MakeNewProjectScopedContainersForEventsBasedObject`)
* or "fake" objects used by events like parameters. They must not be used to
* edit the objects.
* Search for "ProjectScopedContainers wrongly containing temporary objects containers or objects"
* in the codebase.
*/
const gd::ObjectsContainer &GetObjectsContainer(std::size_t index) const;
@@ -188,8 +212,6 @@ class GD_CORE_API ObjectsContainersList {
ObjectsContainersList(){};
private:
bool HasObjectNamed(const gd::String& name) const;
const gd::Object* GetObject(const gd::String& name) const;
bool HasObjectWithVariableNamed(const gd::String& objectName,

View File

@@ -830,7 +830,7 @@ void Project::UnserializeFrom(const SerializerElement& element) {
eventsFunctionsExtensionsElement.ConsiderAsArrayOf(
"eventsFunctionsExtension");
// First, only unserialize behaviors and objects names.
// As event based objects can contains CustomObject and Custom Object,
// As event based objects can contains custom behaviors and custom objects,
// this allows them to reference EventBasedBehavior and EventBasedObject
// respectively.
for (std::size_t i = 0;
@@ -845,15 +845,17 @@ void Project::UnserializeFrom(const SerializerElement& element) {
newEventsFunctionsExtension.UnserializeExtensionDeclarationFrom(
*this, eventsFunctionsExtensionElement);
}
// Then unserialize functions, behaviors and objects content.
for (std::size_t i = 0;
i < eventsFunctionsExtensionsElement.GetChildrenCount();
++i) {
const SerializerElement& eventsFunctionsExtensionElement =
eventsFunctionsExtensionsElement.GetChild(i);
eventsFunctionsExtensions.at(i)->UnserializeExtensionImplementationFrom(
*this, eventsFunctionsExtensionElement);
// Then unserialize functions, behaviors and objects content.
for (gd::String &extensionName :
GetUnserializingOrderExtensionNames(eventsFunctionsExtensionsElement)) {
size_t extensionIndex = GetEventsFunctionsExtensionPosition(extensionName);
const SerializerElement &eventsFunctionsExtensionElement =
eventsFunctionsExtensionsElement.GetChild(extensionIndex);
eventsFunctionsExtensions.at(extensionIndex)
->UnserializeExtensionImplementationFrom(
*this, eventsFunctionsExtensionElement);
}
objectsContainer.GetObjectGroups().UnserializeFrom(
@@ -922,6 +924,83 @@ void Project::UnserializeFrom(const SerializerElement& element) {
}
}
std::vector<gd::String> Project::GetUnserializingOrderExtensionNames(
const gd::SerializerElement &eventsFunctionsExtensionsElement) {
// Some extension have custom objects, which have child objects coming from other extension.
// These child objects must be loaded completely before the parent custom obejct can be unserialized.
// This implies: an order on the extension unserialization (and no cycles).
// At the beginning, everything is yet to be loaded.
std::vector<gd::String> remainingExtensionNames(
eventsFunctionsExtensions.size());
for (std::size_t i = 0; i < eventsFunctionsExtensions.size(); ++i) {
remainingExtensionNames[i] = eventsFunctionsExtensions.at(i)->GetName();
}
// Helper allowing to find if an extension has an object that depends on
// at least one other object from another extension that is not loaded yet.
auto isDependentFromRemainingExtensions =
[&remainingExtensionNames](
const gd::SerializerElement &eventsFunctionsExtensionElement) {
auto &eventsBasedObjectsElement =
eventsFunctionsExtensionElement.GetChild("eventsBasedObjects");
eventsBasedObjectsElement.ConsiderAsArrayOf("eventsBasedObject");
for (std::size_t eventsBasedObjectsIndex = 0;
eventsBasedObjectsIndex <
eventsBasedObjectsElement.GetChildrenCount();
++eventsBasedObjectsIndex) {
auto &objectsElement =
eventsBasedObjectsElement.GetChild(eventsBasedObjectsIndex)
.GetChild("objects");
objectsElement.ConsiderAsArrayOf("object");
for (std::size_t objectIndex = 0;
objectIndex < objectsElement.GetChildrenCount(); ++objectIndex) {
const gd::String &objectType =
objectsElement.GetChild(objectIndex).GetStringAttribute("type");
gd::String extensionName =
eventsFunctionsExtensionElement.GetStringAttribute("name");
gd::String usedExtensionName =
gd::PlatformExtension::GetExtensionFromFullObjectType(objectType);
if (usedExtensionName != extensionName &&
std::find(remainingExtensionNames.begin(),
remainingExtensionNames.end(),
usedExtensionName) != remainingExtensionNames.end()) {
return true;
}
}
}
return false;
};
// Find the order of loading so that the extensions are loaded when all the other
// extensions they depend on are already loaded.
std::vector<gd::String> loadOrderExtensionNames;
bool foundAnyExtension = true;
while (foundAnyExtension) {
foundAnyExtension = false;
for (std::size_t i = 0; i < remainingExtensionNames.size(); ++i) {
auto extensionName = remainingExtensionNames[i];
size_t extensionIndex =
GetEventsFunctionsExtensionPosition(extensionName);
const SerializerElement &eventsFunctionsExtensionElement =
eventsFunctionsExtensionsElement.GetChild(extensionIndex);
if (!isDependentFromRemainingExtensions(
eventsFunctionsExtensionElement)) {
loadOrderExtensionNames.push_back(extensionName);
remainingExtensionNames.erase(remainingExtensionNames.begin() + i);
i--;
foundAnyExtension = true;
}
}
}
return loadOrderExtensionNames;
}
void Project::SerializeTo(SerializerElement& element) const {
SerializerElement& versionElement = element.AddChild("gdVersion");
versionElement.SetAttribute("major", gd::VersionWrapper::Major());

View File

@@ -1076,6 +1076,14 @@ class GD_CORE_API Project {
*/
void Init(const gd::Project& project);
/**
* @brief Get the project extensions names in the order they have to be unserialized.
*
* Child-objects need the event-based objects they use to be loaded completely
* before they are unserialized.
*/
std::vector<gd::String> GetUnserializingOrderExtensionNames(const gd::SerializerElement &eventsFunctionsExtensionsElement);
gd::String name; ///< Game name
gd::String description; ///< Game description
gd::String version; ///< Game version number (used for some exports)

View File

@@ -4,12 +4,55 @@
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Extensions/PlatformExtension.h"
namespace gd {
ProjectScopedContainers
ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(
const gd::Project &project, const gd::Layout &layout) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForProjectAndLayout(
project, layout),
VariablesContainersList::
MakeNewVariablesContainersListForProjectAndLayout(project, layout),
&project.GetVariables(), &layout.GetVariables(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
ProjectScopedContainers
ProjectScopedContainers::MakeNewProjectScopedContainersForProject(
const gd::Project &project) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForProject(project),
VariablesContainersList::MakeNewVariablesContainersListForProject(
project),
&project.GetVariables(), nullptr,
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
ProjectScopedContainers
ProjectScopedContainers::MakeNewProjectScopedContainersFor(
const gd::ObjectsContainer &globalObjectsContainers,
const gd::ObjectsContainer &objectsContainers) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForContainers(
globalObjectsContainers, objectsContainers),
VariablesContainersList::MakeNewEmptyVariablesContainersList(),
nullptr, nullptr,
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
};
ProjectScopedContainers
ProjectScopedContainers::MakeNewProjectScopedContainersForEventsFunctionsExtension(
const gd::Project &project, const gd::EventsFunctionsExtension &eventsFunctionsExtension) {
@@ -18,6 +61,8 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForEventsFunctionsExtensi
ObjectsContainersList::MakeNewEmptyObjectsContainersList(),
VariablesContainersList::
MakeNewVariablesContainersListForEventsFunctionsExtension(eventsFunctionsExtension),
&eventsFunctionsExtension.GetGlobalVariables(),
&eventsFunctionsExtension.GetSceneVariables(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
@@ -37,6 +82,8 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForFreeEventsFunction(
parameterObjectsContainer),
VariablesContainersList::
MakeNewVariablesContainersListForEventsFunctionsExtension(eventsFunctionsExtension),
&eventsFunctionsExtension.GetGlobalVariables(),
&eventsFunctionsExtension.GetSceneVariables(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
projectScopedContainers.AddParameters(
@@ -63,6 +110,8 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForBehaviorEventsFunction
parameterObjectsContainer),
VariablesContainersList::
MakeNewVariablesContainersListForEventsFunctionsExtension(eventsFunctionsExtension),
&eventsFunctionsExtension.GetGlobalVariables(),
&eventsFunctionsExtension.GetSceneVariables(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
projectScopedContainers.AddPropertiesContainer(
@@ -92,6 +141,8 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForObjectEventsFunction(
VariablesContainersList::
MakeNewVariablesContainersListForEventsFunctionsExtension(
eventsFunctionsExtension),
&eventsFunctionsExtension.GetGlobalVariables(),
&eventsFunctionsExtension.GetSceneVariables(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
projectScopedContainers.AddPropertiesContainer(
@@ -109,13 +160,31 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForEventsBasedObject(
const gd::EventsBasedObject &eventsBasedObject,
gd::ObjectsContainer &outputObjectsContainer) {
// TODO: We should avoid to use a single `outputObjectsContainer` and instead
// use multiple, stable objects container pointed by the `ProjectScopedContainers`
// created below.
// Search for "ProjectScopedContainers wrongly containing temporary objects containers or objects"
// in the codebase.
outputObjectsContainer.GetObjects().clear();
outputObjectsContainer.GetObjectGroups().Clear();
// This object named "Object" represents the parent and is used by events.
// TODO: Use a dedicated `ObjectsContainer` with only this "Object" and check in
// the codebase that this container is not assumed as a
// "globalObjectsContainer".
// Search for "ProjectScopedContainers wrongly containing temporary objects containers or objects"
// in the codebase.
outputObjectsContainer.InsertNewObject(
project,
gd::PlatformExtension::GetObjectFullType(
eventsFunctionsExtension.GetName(), eventsBasedObject.GetName()),
"Object", outputObjectsContainer.GetObjectsCount());
// TODO: We should avoid to do a copy of the objects container here - as this results
// in an objects container that contains temporary objects. This can create issues in the
// UI (for example, a tree view that keeps references on objects).
// Search for "ProjectScopedContainers wrongly containing temporary objects containers or objects"
// in the codebase.
gd::EventsFunctionTools::CopyEventsBasedObjectChildrenToObjectsContainer(
eventsBasedObject, outputObjectsContainer);
@@ -125,6 +194,8 @@ ProjectScopedContainers::MakeNewProjectScopedContainersForEventsBasedObject(
VariablesContainersList::
MakeNewVariablesContainersListForEventsFunctionsExtension(
eventsFunctionsExtension),
&eventsFunctionsExtension.GetGlobalVariables(),
&eventsFunctionsExtension.GetSceneVariables(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
projectScopedContainers.AddPropertiesContainer(

View File

@@ -5,13 +5,11 @@
#include "ObjectsContainersList.h"
#include "PropertiesContainersList.h"
#include "VariablesContainersList.h"
#include "VariablesContainer.h"
namespace gd {
class Project;
class ObjectsContainer;
class ObjectsContainersList;
class VariablesContainersList;
class PropertiesContainersList;
class NamedPropertyDescriptor;
class ParameterMetadataContainer;
class BaseEvent;
@@ -19,7 +17,7 @@ class EventsFunctionsExtension;
class EventsFunction;
class EventsBasedBehavior;
class EventsBasedObject;
} // namespace gd
} // namespace gd
namespace gd {
@@ -38,51 +36,29 @@ class ProjectScopedContainers {
ProjectScopedContainers(
const gd::ObjectsContainersList &objectsContainersList_,
const gd::VariablesContainersList &variablesContainersList_,
const gd::VariablesContainer *legacyGlobalVariables_,
const gd::VariablesContainer *legacySceneVariables_,
const gd::PropertiesContainersList &propertiesContainersList_)
: objectsContainersList(objectsContainersList_),
variablesContainersList(variablesContainersList_),
legacyGlobalVariables(legacyGlobalVariables_),
legacySceneVariables(legacySceneVariables_),
propertiesContainersList(propertiesContainersList_){};
virtual ~ProjectScopedContainers(){};
static ProjectScopedContainers
MakeNewProjectScopedContainersForProjectAndLayout(const gd::Project &project,
const gd::Layout &layout) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForProjectAndLayout(
project, layout),
VariablesContainersList::
MakeNewVariablesContainersListForProjectAndLayout(project, layout),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
const gd::Layout &layout);
static ProjectScopedContainers
MakeNewProjectScopedContainersForProject(const gd::Project &project) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForProject(
project),
VariablesContainersList::
MakeNewVariablesContainersListForProject(project),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
MakeNewProjectScopedContainersForProject(const gd::Project &project);
/**
* @deprecated Use another method for an explicit context instead.
*/
static ProjectScopedContainers MakeNewProjectScopedContainersFor(
const gd::ObjectsContainer &globalObjectsContainers,
const gd::ObjectsContainer &objectsContainers) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForContainers(
globalObjectsContainers, objectsContainers),
VariablesContainersList::MakeNewEmptyVariablesContainersList(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
};
const gd::ObjectsContainer &objectsContainers);
static ProjectScopedContainers
MakeNewProjectScopedContainersForEventsFunctionsExtension(
@@ -221,6 +197,24 @@ class ProjectScopedContainers {
return variablesContainersList;
};
/**
* @brief Return the global variables of the current scene or the current
* extension. It allows legacy "globalvar" parameters to accept extension
* variables.
*/
const gd::VariablesContainer *GetLegacyGlobalVariables() const {
return legacyGlobalVariables;
};
/**
* @brief Return the scene variables of the current scene or the current
* extension. It allows legacy "scenevar" parameters to accept extension
* variables.
*/
const gd::VariablesContainer *GetLegacySceneVariables() const {
return legacySceneVariables;
};
const gd::PropertiesContainersList &GetPropertiesContainersList() const {
return propertiesContainersList;
};
@@ -231,11 +225,14 @@ class ProjectScopedContainers {
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
ProjectScopedContainers(){};
ProjectScopedContainers()
: legacyGlobalVariables(nullptr), legacySceneVariables(nullptr){};
private:
private:
gd::ObjectsContainersList objectsContainersList;
gd::VariablesContainersList variablesContainersList;
const gd::VariablesContainer *legacyGlobalVariables;
const gd::VariablesContainer *legacySceneVariables;
gd::PropertiesContainersList propertiesContainersList;
std::vector<const ParameterMetadataContainer *> parametersVectorsList;
};

View File

@@ -151,6 +151,37 @@ TEST_CASE("ArbitraryResourceWorker", "[common][resources]") {
REQUIRE(worker.images[0] == "res1");
}
SECTION("Can find resource usages in behavior configurations") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
project.GetResourcesManager().AddResource(
"res1", "path/to/file1.png", "image");
project.GetResourcesManager().AddResource(
"res2", "path/to/file2.png", "image");
project.GetResourcesManager().AddResource(
"res3", "path/to/file3.png", "image");
project.GetResourcesManager().AddResource(
"res4", "path/to/file4.png", "audio");
ArbitraryResourceWorkerTest worker(project.GetResourcesManager());
auto &layout = project.InsertNewLayout("Scene", 0);
auto &object = layout.GetObjects().InsertNewObject(
project, "MyExtension::Sprite", "MyObject", 0);
auto *behavior = object.AddNewBehavior(
project, "MyExtension::BehaviorWithRequiredBehaviorProperty",
"BehaviorWithResource");
behavior->UpdateProperty("resourceProperty", "res1");
worker.files.clear();
worker.images.clear();
gd::ResourceExposer::ExposeWholeProjectResources(project, worker);
REQUIRE(worker.files.size() == 4);
REQUIRE(worker.images.size() == 1);
REQUIRE(worker.images[0] == "res1");
}
SECTION("Can find resource usages in layout events") {
gd::Platform platform;
gd::Project project;

View File

@@ -39,20 +39,31 @@ class BehaviorWithRequiredBehaviorProperty : public gd::Behavior {
behaviorContent.GetStringAttribute("requiredBehaviorProperty"))
.SetType("Behavior")
.AddExtraInfo("MyExtension::MyBehavior");
properties["resourceProperty"]
.SetLabel("A resource")
.SetValue(
behaviorContent.GetStringAttribute("resourceProperty"))
.SetType("Resource")
.AddExtraInfo("image");
return properties;
}
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value) override {
if (name == _("requiredBehaviorProperty")) {
if (name == "requiredBehaviorProperty") {
behaviorContent.SetAttribute("requiredBehaviorProperty", value);
return true;
}
if (name == "resourceProperty") {
behaviorContent.SetAttribute("resourceProperty", value);
return true;
}
return false;
}
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override {
behaviorContent.SetAttribute("requiredBehaviorProperty", "");
behaviorContent.SetAttribute("resourceProperty", "");
}
};
@@ -687,6 +698,14 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Property name"));
extension
->AddAction("DisplayLeaderboard",
"Display leaderboard",
"Display the specified leaderboard on top of the game.",
"Display leaderboard _PARAM1_",
"Display leaderboard", "", "")
.AddParameter("leaderboardId", "Leaderboard", "", false);
}
{

View File

@@ -1737,10 +1737,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 2);
REQUIRE(validator.GetFatalErrors().size() == 3);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
"An object variable or expression should be entered.");
REQUIRE(validator.GetFatalErrors()[2]->GetMessage() ==
"A name should be entered after the dot.");
}
}
@@ -1817,6 +1819,19 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Invalid object variables (non existing variable with child)") {
{
auto node =
parser.ParseExpression("MySpriteObject.MyNonExistingVariable.MyNonExistingChild");
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"This variable does not exist on this object or group.");
}
}
SECTION("Invalid object (object entered without any variable)") {
{
auto node =
@@ -1962,6 +1977,25 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
}
}
SECTION("Invalid property (required behavior)") {
{
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
auto projectScopedContainersWithProperties = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout1);
projectScopedContainersWithProperties.AddPropertiesContainer(propertiesContainer);
propertiesContainer.InsertNew("MyRequiredBehavior").SetType("Behavior");
auto node =
parser.ParseExpression("\"abc\" + MyRequiredBehavior");
gd::ExpressionValidator validator(platform, projectScopedContainersWithProperties, "number|string");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 1);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() == "Behaviors can't be used as a value in expressions.");
}
}
SECTION("Invalid property (unsupported child syntax, 1 level)") {
{
gd::PropertiesContainer propertiesContainer(gd::EventsFunctionsContainer::Extension);
@@ -2357,10 +2391,12 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
node->Visit(validator);
REQUIRE(validator.GetFatalErrors().size() == 2);
REQUIRE(validator.GetFatalErrors().size() == 3);
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
"A name should be entered after the dot.");
REQUIRE(validator.GetFatalErrors()[1]->GetMessage() ==
"An object variable or expression should be entered.");
REQUIRE(validator.GetFatalErrors()[2]->GetMessage() ==
"A name should be entered after the dot.");
}

View File

@@ -262,4 +262,153 @@ TEST_CASE("ObjectSerialization", "[common]") {
auto &behaviorElement = behaviorsElement.GetChild(0);
REQUIRE(behaviorElement.GetStringAttribute("name") == "MyBehavior");
}
}
// Event-based object dependency cycles are not tested because they are forbidden by the editor.
SECTION("Save and load a project with custom object dependencies from different extensions") {
gd::Platform platform;
gd::Project writtenProject;
SetupProjectWithDummyPlatform(writtenProject, platform);
{
// The extension with the dependency is added first to make the
// implementation change the order in which extensions are loaded.
auto &eventsExtensionWithDependency =
writtenProject.InsertNewEventsFunctionsExtension(
"MyEventsExtensionWithDependency", 0);
auto &eventsExtension = writtenProject.InsertNewEventsFunctionsExtension(
"MyEventsExtension", 1);
{
auto &eventsBasedObject =
eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
writtenProject, "MyExtension::Sprite", "MyChildSprite", 0);
}
// An event-based object with a custom object child that overrides its
// configuration.
{
auto &eventsBasedObject =
eventsExtensionWithDependency.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObjectWithDependency", 0);
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
writtenProject, "MyEventsExtension::MyEventsBasedObject",
"MyChildCustomObject", 0);
auto *customObjectConfiguration =
dynamic_cast<gd::CustomObjectConfiguration *>(
&childObject.GetConfiguration());
auto &spriteConfiguration =
customObjectConfiguration->GetChildObjectConfiguration(
"MyChildSprite");
SetupSpriteConfiguration(spriteConfiguration);
}
}
SerializerElement projectElement;
writtenProject.SerializeTo(projectElement);
gd::Project readProject;
readProject.AddPlatform(platform);
readProject.UnserializeFrom(projectElement);
REQUIRE(readProject.GetEventsFunctionsExtensionsCount() == 2);
auto &eventsExtensionWithDependency =
readProject.GetEventsFunctionsExtension(0);
REQUIRE(eventsExtensionWithDependency.GetEventsBasedObjects().GetCount() ==
1);
auto &eventsBasedObject =
eventsExtensionWithDependency.GetEventsBasedObjects().Get(0);
REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1);
auto &childObject = eventsBasedObject.GetObjects().GetObject(0);
REQUIRE(childObject.GetName() == "MyChildCustomObject");
REQUIRE(childObject.GetType() == "MyEventsExtension::MyEventsBasedObject");
auto *customObjectConfiguration =
dynamic_cast<gd::CustomObjectConfiguration *>(
&childObject.GetConfiguration());
REQUIRE(customObjectConfiguration != nullptr);
auto &spriteConfiguration =
customObjectConfiguration->GetChildObjectConfiguration("MyChildSprite");
CheckSpriteConfiguration(spriteConfiguration);
}
SECTION("Save and load a project with custom object dependencies inside an extension") {
gd::Platform platform;
gd::Project writtenProject;
SetupProjectWithDummyPlatform(writtenProject, platform);
{
auto &eventsExtension = writtenProject.InsertNewEventsFunctionsExtension(
"MyEventsExtension", 0);
{
auto &eventsBasedObject =
eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
writtenProject, "MyExtension::Sprite", "MyChildSprite", 0);
}
// An event-based object with a custom object child that overrides its
// configuration.
// The extension with the dependency is added first to make the
// implementation change the order in which extensions are loaded.
{
auto &eventsBasedObject =
eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObjectWithDependency", 0);
auto &childObject = eventsBasedObject.GetObjects().InsertNewObject(
writtenProject, "MyEventsExtension::MyEventsBasedObject",
"MyChildCustomObject", 0);
auto *customObjectConfiguration =
dynamic_cast<gd::CustomObjectConfiguration *>(
&childObject.GetConfiguration());
auto &spriteConfiguration =
customObjectConfiguration->GetChildObjectConfiguration(
"MyChildSprite");
SetupSpriteConfiguration(spriteConfiguration);
}
}
SerializerElement projectElement;
writtenProject.SerializeTo(projectElement);
gd::Project readProject;
readProject.AddPlatform(platform);
readProject.UnserializeFrom(projectElement);
REQUIRE(readProject.GetEventsFunctionsExtensionsCount() == 1);
auto &eventsExtension =
readProject.GetEventsFunctionsExtension(0);
REQUIRE(eventsExtension.GetEventsBasedObjects().GetCount() == 2);
auto &eventsBasedObject =
eventsExtension.GetEventsBasedObjects().Get(0);
REQUIRE(eventsBasedObject.GetObjects().GetObjectsCount() == 1);
auto &childObject = eventsBasedObject.GetObjects().GetObject(0);
REQUIRE(childObject.GetName() == "MyChildCustomObject");
REQUIRE(childObject.GetType() == "MyEventsExtension::MyEventsBasedObject");
auto *customObjectConfiguration =
dynamic_cast<gd::CustomObjectConfiguration *>(
&childObject.GetConfiguration());
REQUIRE(customObjectConfiguration != nullptr);
auto &spriteConfiguration =
customObjectConfiguration->GetChildObjectConfiguration("MyChildSprite");
CheckSpriteConfiguration(spriteConfiguration);
}
SECTION("Can unserialize over an existing extension without duplicating its event-based objects") {
gd::Platform platform;
gd::Project project;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension =
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
{
auto &eventsBasedObject =
eventsExtension.GetEventsBasedObjects().InsertNew(
"MyEventsBasedObject", 0);
}
SerializerElement extensionElement;
eventsExtension.SerializeTo(extensionElement);
eventsExtension.UnserializeFrom(project, extensionElement);
REQUIRE(eventsExtension.GetEventsBasedObjects().GetCount() == 1);
}
}

View File

@@ -77,6 +77,20 @@ const gd::String &GetEventFirstActionType(const gd::BaseEvent &event) {
return actions.Get(0).GetType();
}
const gd::Instruction &
CreateInstructionWithNumberParameter(gd::Project &project,
gd::EventsList &events,
const gd::String &expression) {
gd::StandardEvent &event = dynamic_cast<gd::StandardEvent &>(
events.InsertNewEvent(project, "BuiltinCommonInstructions::Standard"));
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(0, expression);
return event.GetActions().Insert(instruction);
}
enum TestEvent {
FreeFunctionAction,
FreeFunctionWithExpression,
@@ -3038,6 +3052,30 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
}
}
SECTION("(Events based behavior) property renamed (in expressions)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
auto &behaviorAction =
eventsBasedBehavior.GetEventsFunctions().InsertNewEventsFunction(
"MyBehaviorEventsFunction", 0);
gd::WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters(
eventsExtension, eventsBasedBehavior);
auto &instruction = CreateInstructionWithNumberParameter(
project, behaviorAction.GetEvents(), "MyProperty");
gd::WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
project, eventsExtension, eventsBasedBehavior, "MyProperty",
"MyRenamedProperty");
REQUIRE(instruction.GetParameter(0).GetPlainString() ==
"MyRenamedProperty");
}
SECTION("(Events based behavior) shared property renamed") {
gd::Project project;
gd::Platform platform;
@@ -3089,6 +3127,30 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
}
}
SECTION("(Events based behavior) shared property renamed (in expressions)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
auto &behaviorAction =
eventsBasedBehavior.GetEventsFunctions().InsertNewEventsFunction(
"MyBehaviorEventsFunction", 0);
gd::WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters(
eventsExtension, eventsBasedBehavior);
auto &instruction = CreateInstructionWithNumberParameter(
project, behaviorAction.GetEvents(), "MySharedProperty");
gd::WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
project, eventsExtension, eventsBasedBehavior, "MySharedProperty",
"MyRenamedSharedProperty");
REQUIRE(instruction.GetParameter(0).GetPlainString() ==
"MyRenamedSharedProperty");
}
SECTION("(Events based object) property renamed") {
gd::Project project;
gd::Platform platform;
@@ -3118,6 +3180,30 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"MyCustomObject.PropertyMyRenamedProperty()");
}
}
SECTION("(Events based object) property renamed (in expressions)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
auto &eventsBasedObject =
eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject");
auto &behaviorAction =
eventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
"MyObjectEventsFunction", 0);
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
eventsExtension, eventsBasedObject);
auto &instruction = CreateInstructionWithNumberParameter(
project, behaviorAction.GetEvents(), "MyProperty");
gd::WholeProjectRefactorer::RenameEventsBasedObjectProperty(
project, eventsExtension, eventsBasedObject, "MyProperty",
"MyRenamedProperty");
REQUIRE(instruction.GetParameter(0).GetPlainString() ==
"MyRenamedProperty");
}
}
// TODO: Check that this works when behaviors are attached to a child-object.
TEST_CASE("WholeProjectRefactorer (FindInvalidRequiredBehaviorProperties)",
@@ -4451,4 +4537,33 @@ TEST_CASE("MergeLayers", "[common]") {
// Other layers from the same layout are untouched.
REQUIRE(initialInstances.GetLayerInstancesCount("My other layer") == 1);
}
// TODO: ideally, a test should also cover objects having `leaderboardId` as property.
SECTION("Can rename a leaderboard in scene events") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout = project.InsertNewLayout("My layout", 0);
gd::StandardEvent &event = dynamic_cast<gd::StandardEvent &>(
layout.GetEvents().InsertNewEvent(project, "BuiltinCommonInstructions::Standard"));
gd::Instruction action;
action.SetType("MyExtension::DisplayLeaderboard");
action.SetParametersCount(1);
action.SetParameter(0, gd::Expression("\"12345678-9abc-def0-1234-56789abcdef0\""));
event.GetActions().Insert(action);
std::set<gd::String> allLeaderboardIds = gd::WholeProjectRefactorer::FindAllLeaderboardIds(project);
REQUIRE(allLeaderboardIds.size() == 1);
REQUIRE(allLeaderboardIds.count("12345678-9abc-def0-1234-56789abcdef0") == 1);
std::map<gd::String, gd::String> leaderboardIdMap;
leaderboardIdMap["12345678-9abc-def0-1234-56789abcdef0"] = "87654321-9abc-def0-1234-56789abcdef0";
gd::WholeProjectRefactorer::RenameLeaderboards(project, leaderboardIdMap);
REQUIRE(GetEventFirstActionFirstParameterString(layout.GetEvents().GetEvent(0)) ==
"\"87654321-9abc-def0-1234-56789abcdef0\"");
}
}

View File

@@ -2203,6 +2203,8 @@ module.exports = {
if (!texture.baseTexture.valid) {
// Post pone texture update if texture is not loaded.
texture.once('update', () => {
if (this._wasDestroyed) return;
this.updateTexture();
this.updatePIXISprite();
});
@@ -2375,6 +2377,7 @@ module.exports = {
getFaceMaterial(this._project, materialIndexToFaceIndex[4]),
getFaceMaterial(this._project, materialIndexToFaceIndex[5]),
]);
if (this._wasDestroyed) return;
this._threeObject.material[0] = materials[0];
this._threeObject.material[1] = materials[1];
@@ -2823,6 +2826,7 @@ module.exports = {
this._pixiResourcesLoader
.get3DModel(project, modelResourceName)
.then((model3d) => {
if (this._wasDestroyed) return;
const clonedModel3D = THREE_ADDONS.SkeletonUtils.clone(
model3d.scene
);
@@ -3227,9 +3231,9 @@ module.exports = {
: this._originalDepth / modelDepth;
const minScaleRatio = Math.min(widthRatio, heightRatio, depthRatio);
if (!Number.isFinite(minScaleRatio)) {
this._defaultWidth = modelWidth;
this._defaultHeight = modelHeight;
this._defaultDepth = modelDepth;
this._defaultWidth = this._originalWidth;
this._defaultHeight = this._originalHeight;
this._defaultDepth = this._originalDepth;
} else {
if (widthRatio === minScaleRatio) {
this._defaultWidth = this._originalWidth;
@@ -3270,6 +3274,10 @@ module.exports = {
this._defaultDepth = this._originalDepth;
}
}
} else {
this._defaultWidth = this._originalWidth;
this._defaultHeight = this._originalHeight;
this._defaultDepth = this._originalDepth;
}
this._threeObject.add(this._threeModelGroup);
@@ -3338,6 +3346,7 @@ module.exports = {
this._pixiResourcesLoader
.get3DModel(this._project, modelResourceName)
.then((model3d) => {
if (this._wasDestroyed) return;
this._clonedModel3D = THREE_ADDONS.SkeletonUtils.clone(
model3d.scene
);

View File

@@ -697,6 +697,8 @@ module.exports = {
this._currentBitmapFontResourceName,
this._currentTextureAtlasResourceName
).then((bitmapFont) => {
if (this._wasDestroyed) return;
this._pixiObject.fontName = bitmapFont.font;
this._pixiObject.fontSize = bitmapFont.size;
this._pixiObject.dirty = true;

View File

@@ -34,18 +34,18 @@ module.exports = {
extension
.addAction(
'LoadDialogueFromSceneVariable',
_('Load dialogue Tree from a scene variable'),
_('Load dialogue tree from a scene variable'),
_(
'Load a dialogue data object - Yarn json format, stored in a scene variable. Use this command to load all the Dialogue data at the beginning of the game.'
'Load a dialogue data object - Yarn JSON format, stored in a scene variable. Use this command to load all the Dialogue data at the beginning of the game.'
),
_('Load dialogue data from Scene variable _PARAM0_'),
_('Load dialogue data from scene variable _PARAM0_'),
'',
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter(
'scenevar',
_('Scene variable that holds the Yarn Json data'),
_('Scene variable that holds the Yarn JSON data'),
'',
false
)
@@ -57,11 +57,11 @@ module.exports = {
extension
.addAction(
'LoadDialogueFromJsonFile',
_('Load dialogue Tree from a Json File'),
_('Load dialogue tree from a JSON file'),
_(
'Load a dialogue data object - Yarn json format, stored in a Json file. Use this command to load all the Dialogue data at the beginning of the game.'
'Load a dialogue data object - Yarn JSON format, stored in a JSON file. Use this command to load all the Dialogue data at the beginning of the game.'
),
_('Load dialogue data from json file _PARAM1_'),
_('Load dialogue data from JSON file _PARAM1_'),
'',
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
@@ -69,7 +69,7 @@ module.exports = {
.addCodeOnlyParameter('currentScene', '')
.addParameter(
'jsonResource',
_('Json file that holds the Yarn Json data'),
_('JSON file that holds the Yarn JSON data'),
'',
false
)
@@ -125,11 +125,11 @@ module.exports = {
extension
.addAction(
'ConfirmSelectOption',
_('Confirm selected Option'),
_('Confirm selected option'),
_(
'Set the selected option as confirmed, which will validate it and go forward to the next node. Use other actions to select options (see "select next option" and "Select previous option").'
),
_('Confirm selected Option'),
_('Confirm selected option'),
'',
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
@@ -140,11 +140,11 @@ module.exports = {
extension
.addAction(
'SelectNextOption',
_('Select next Option'),
_('Select next option'),
_(
'Select next Option (add 1 to selected option number). Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
'Select next option (add 1 to selected option number). Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
),
_('Select next Option'),
_('Select next option'),
'',
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
@@ -155,11 +155,11 @@ module.exports = {
extension
.addAction(
'SelectPreviousOption',
_('Select previous Option'),
_('Select previous option'),
_(
'Select previous Option (subtract 1 from selected option number). Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
'Select previous option (subtract 1 from selected option number). Use this when the dialogue line is of type "options" and the player has pressed a button to change selected option.'
),
_('Select previous Option'),
_('Select previous option'),
'',
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
@@ -225,8 +225,8 @@ module.exports = {
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter('string', _('State Variable Name'), '', false)
.addParameter('string', _('Variable string value'), '', false)
.addParameter('string', _('State variable name'), '', false)
.addParameter('string', _('New value'), '', false)
.getCodeExtraInformation()
.setFunctionName('gdjs.dialogueTree.setVariable');
@@ -242,8 +242,8 @@ module.exports = {
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter('string', _('State Variable Name'), '', false)
.addParameter('expression', _('Variable number value'), '', true)
.addParameter('string', _('State variable name'), '', false)
.addParameter('expression', _('New value'), '', true)
.getCodeExtraInformation()
.setFunctionName('gdjs.dialogueTree.setVariable');
@@ -259,8 +259,8 @@ module.exports = {
'JsPlatform/Extensions/yarn32.png',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter('string', _('State Variable Name'), '', false)
.addParameter('trueorfalse', _('Variable boolean value'), '', false)
.addParameter('string', _('State variable name'), '', false)
.addParameter('trueorfalse', _('New value'), '', false)
.getCodeExtraInformation()
.setFunctionName('gdjs.dialogueTree.setVariable');
@@ -336,9 +336,9 @@ module.exports = {
extension
.addStrExpression(
'Option',
_('Get the text of an option from an Options line type'),
_('Get the text of an option from an options line type'),
_(
"Get the text of an option from an Options line type, using the option's Number. The numbers start from 0."
"Get the text of an option from an options line type, using the option's Number. The numbers start from 0."
),
'',
'JsPlatform/Extensions/yarn32.png'
@@ -350,9 +350,9 @@ module.exports = {
extension
.addStrExpression(
'HorizontalOptionsList',
_('Get a Horizontal list of options from the Options line type'),
_('Get a Horizontal list of options from the options line type'),
_(
"Get the text of all available options from an Options line type as a horizontal list. You can also pass the selected option's cursor string, which by default is ->"
"Get the text of all available options from an options line type as a horizontal list. You can also pass the selected option's cursor string, which by default is ->"
),
'',
'JsPlatform/Extensions/yarn32.png'
@@ -365,9 +365,9 @@ module.exports = {
extension
.addStrExpression(
'VerticalOptionsList',
_('Get a Vertical list of options from the Options line type'),
_('Get a Vertical list of options from the options line type'),
_(
"Get the text of all available options from an Options line type as a vertical list. You can also pass the selected option's cursor string, which by default is ->"
"Get the text of all available options from an options line type as a vertical list. You can also pass the selected option's cursor string, which by default is ->"
),
'',
'JsPlatform/Extensions/yarn32.png'
@@ -507,14 +507,26 @@ module.exports = {
extension
.addExpression(
'Variable',
_('Get dialogue state value'),
_('Get dialogue state value'),
_('Get the number stored in a dialogue state variable'),
_('Get the number stored in a dialogue state variable'),
'',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter('string', _('Variable Name'), '', false)
.addParameter('string', _('Dialogue state variable name'), '', false)
.getCodeExtraInformation()
.setFunctionName('gdjs.dialogueTree.getVariable');
.setFunctionName('gdjs.dialogueTree.getVariableAsNumber');
extension
.addStrExpression(
'VariableString',
_('Get the string stored in a dialogue state variable'),
_('Get the string stored in a dialogue state variable'),
'',
'JsPlatform/Extensions/yarn32.png'
)
.addParameter('string', _('Dialogue state variable name'), '', false)
.getCodeExtraInformation()
.setFunctionName('gdjs.dialogueTree.getVariableAsString');
extension
.addCondition(

View File

@@ -362,7 +362,15 @@ namespace gdjs {
this.commandCalls = [];
try {
this.dialogueData.select(this.selectedOption);
this.dialogueData = this.dialogue.next().value;
try {
this.dialogueData = this.dialogue.next().value;
} catch (error) {
logger.error(
'Error while confirming in the dialogue tree. Verify if there is a syntax error? Full error is: ',
error
);
return;
}
gdjs.dialogueTree.goToNextDialogueLine();
} catch (error) {
logger.error(
@@ -511,13 +519,29 @@ namespace gdjs {
this.optionsCount = 0;
this.options = [];
this.tagParameters = [];
this.dialogue = this.runner.run(startDialogueNode);
try {
this.dialogue = this.runner.run(startDialogueNode);
} catch (error) {
logger.error(
'Error while setting up the dialogue tree. Verify if there is a syntax error? Full error is: ',
error
);
return;
}
this.dialogueText = '';
this.clipTextEnd = 0;
this.commandCalls = [];
this.commandParameters = [];
this.pauseScrolling = false;
this.dialogueData = this.dialogue.next().value;
try {
this.dialogueData = this.dialogue.next().value;
} catch (error) {
logger.error(
'Error while starting the dialogue tree. Verify if there is a syntax error? Full error is: ',
error
);
return;
}
this.dialogueBranchTags = this.dialogueData.data.tags;
this.dialogueBranchTitle = this.dialogueData.data.title;
this.dialogueBranchBody = this.dialogueData.data.body;
@@ -583,7 +607,15 @@ namespace gdjs {
this.dialogueBranchBody = this.dialogueData.data.body;
this.lineNum = this.dialogueData.lineNum;
this.dialogueDataType = 'text';
this.dialogueData = this.dialogue.next().value;
try {
this.dialogueData = this.dialogue.next().value;
} catch (error) {
logger.error(
'Error while progressing the dialogue tree. Verify if there is a syntax error? Full error is: ',
error
);
return;
}
} else {
if (gdjs.dialogueTree._isLineTypeOptions()) {
this.commandCalls = [];
@@ -609,7 +641,15 @@ namespace gdjs {
params: command,
time: this.dialogueText.length + offsetTime,
});
this.dialogueData = this.dialogue.next().value;
try {
this.dialogueData = this.dialogue.next().value;
} catch (error) {
logger.error(
'Error while progressing the dialogue tree. Verify if there is a syntax error? Full error is: ',
error
);
return;
}
gdjs.dialogueTree.goToNextDialogueLine();
} else {
this.dialogueDataType = 'unknown';
@@ -734,16 +774,45 @@ namespace gdjs {
};
/**
* Get the value of a variable that was created by the Dialogue parses.
* @param key The name of the variable you want to get the value of
* Get the value of a variable stored in the dialogue state.
* @param key The variable name
*/
gdjs.dialogueTree.getVariable = function (key: string) {
gdjs.dialogueTree.getVariable = function (
key: string
): string | float | boolean {
if (this.runner.variables && key in this.runner.variables.data) {
return this.runner.variables.get(key);
}
return '';
};
/**
* Get the value of a variable stored in the dialogue state.
* @param key The variable name
*/
gdjs.dialogueTree.getVariableAsNumber = function (key: string): float {
if (this.runner.variables && key in this.runner.variables.data) {
const value = this.runner.variables.get(key);
if (typeof value !== 'number') {
return parseFloat(value) || 0;
}
return isFinite(value) ? value : 0;
}
return 0;
};
/**
* Get the value of a variable stored in the dialogue state.
* @param key The variable name
*/
gdjs.dialogueTree.getVariableAsString = function (key: string): string {
if (this.runner.variables && key in this.runner.variables.data) {
return '' + this.runner.variables.get(key);
}
return '';
};
/**
* Check if a specific variable created by the Dialogue parses exists and is equal to a specific value.
* @param key The name of the variable you want to check the value of

View File

@@ -14,9 +14,12 @@ class RenderedInstance {
_associatedObjectConfiguration: gd.ObjectConfiguration;
_pixiContainer: PIXI.Container;
_pixiResourcesLoader: Class<PixiResourcesLoader>;
_pixiObject: PIXI.DisplayObject;
_pixiObject: PIXI.DisplayObject | null;
wasUsed: boolean;
/** Set to true when onRemovedFromScene is called. Allows to cancel promises/asynchronous operations (notably: waiting for a resource load). */
_wasDestroyed: boolean;
constructor(
project: gdProject,
instance: gdInitialInstance,
@@ -89,10 +92,13 @@ class Rendered3DInstance {
_pixiContainer: PIXI.Container;
_threeGroup: THREE.Group;
_pixiResourcesLoader: Class<PixiResourcesLoader>;
_pixiObject: PIXI.DisplayObject;
_pixiObject: PIXI.DisplayObject | null;
_threeObject: THREE.Object3D | null;
wasUsed: boolean;
/** Set to true when onRemovedFromScene is called. Allows to cancel promises/asynchronous operations (notably: waiting for a resource load). */
_wasDestroyed: boolean;
constructor(
project: gdProject,
instance: gdInitialInstance,

View File

@@ -588,7 +588,7 @@ namespace gdjs {
break;
case 'openPlayerAuthentication':
gdjs.playerAuthentication
.openAuthenticationWindow(runtimeScene)
.openAuthenticationWindow(runtimeScene, event.data.options)
.promise.then(({ status }) => {
if (
!_leaderboardViewIframe ||

View File

@@ -882,6 +882,11 @@ module.exports = {
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('variable', _('Variable'), '', false)
.setParameterLongDescription(
_(
'Only root variables can change ownership. Arrays and structures children are synchronized with their parent.'
)
)
.useStandardParameters(
'number',
gd.ParameterOptions.makeNewOptions().setDescription(_('Player number'))
@@ -916,6 +921,11 @@ module.exports = {
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('variable', _('Variable'), '', false)
.setParameterLongDescription(
_(
'Only root variables can change ownership. Arrays and structures children are synchronized with their parent.'
)
)
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')
@@ -947,6 +957,11 @@ module.exports = {
)
.addCodeOnlyParameter('currentScene', '')
.addParameter('variable', _('Variable'), '', false)
.setParameterLongDescription(
_(
'Only root variables can change ownership. Arrays and structures children are synchronized with their parent.'
)
)
.setHelpPath('/all-features/multiplayer')
.getCodeExtraInformation()
.setIncludeFile('Extensions/Multiplayer/peer.js')

View File

@@ -917,12 +917,12 @@ namespace gdjs {
// As we are the host, we do not cancel the message if it times out.
shouldCancelMessageIfTimesOut: false,
});
for (const peerId of otherPeerIds) {
debugLogger.info(
`Relaying ownership change of variable with Id ${variableNetworkId} to ${peerId}.`
);
sendDataTo(otherPeerIds, messageName, messageData);
}
debugLogger.info(
`Relaying ownership change of variable with Id ${variableNetworkId} to ${otherPeerIds.join(
', '
)}.`
);
sendDataTo(otherPeerIds, messageName, messageData);
}
});
});
@@ -1361,6 +1361,11 @@ namespace gdjs {
// As we are the host, we do not cancel the message if it times out.
shouldCancelMessageIfTimesOut: false,
});
debugLogger.info(
`Relaying instance destroyed message for object ${objectName} with instance network ID ${instanceNetworkId} to ${otherPeerIds.join(
', '
)}.`
);
sendDataTo(otherPeerIds, messageName, messageData);
}
});

View File

@@ -120,10 +120,11 @@ namespace gdjs {
private _isOwnerAsPlayerOrHost() {
const currentPlayerNumber = gdjs.multiplayer.getCurrentPlayerNumber();
const isHost = gdjs.multiplayer.isCurrentPlayerHost();
const isOwnerOfObject =
currentPlayerNumber === this.playerNumber || // Player as owner.
(currentPlayerNumber === 1 && this.playerNumber === 0); // Host as owner.
(isHost && this.playerNumber === 0); // Host as owner.
return isOwnerOfObject;
}
@@ -253,6 +254,7 @@ namespace gdjs {
// If game is running and the object belongs to a player who is not connected, destroy the object.
// As the game may create objects before the lobby game starts, we don't want to destroy them if it's not running.
if (
this.actionOnPlayerDisconnect !== 'DoNothing' && // Should not delete if flagged as such.
this.playerNumber !== 0 && // Host is always connected.
!gdjs.multiplayerMessageManager.isPlayerConnected(this.playerNumber)
) {
@@ -455,8 +457,8 @@ namespace gdjs {
// Before sending the destroy message, we set up the object representing the peers
// that we need an acknowledgment from.
// If we are player 1, we are connected to everyone, so we expect an acknowledgment from everyone.
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
// If we are the host, we are connected to everyone, so we expect an acknowledgment from everyone.
// If we are another player, we are only connected to the host, so we expect an acknowledgment from the host.
// In both cases, this represents the list of peers the current user is connected to.
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
const {
@@ -508,7 +510,7 @@ namespace gdjs {
// Update the ownership locally, so the object can be used immediately.
// This is a prediction to allow snappy interactions.
// If we are player 1 or host, we will have the ownership immediately anyway.
// If we are host, we will have the ownership immediately anyway.
// If we are another player, we will have the ownership as soon as the host acknowledges the change.
// If the host does not send an acknowledgment, we will revert the ownership.
const previousObjectPlayerNumber = this.playerNumber;
@@ -564,8 +566,8 @@ namespace gdjs {
});
// Before sending the changeOwner message, if we are becoming the new owner,
// we want to ensure this message is acknowledged, by everyone we're connected to.
// If we are player 1, we are connected to everyone, so we expect an acknowledgment from everyone.
// If we are another player, we are only connected to player 1, so we expect an acknowledgment from player 1.
// If we are the host, we are connected to everyone, so we expect an acknowledgment from everyone.
// If we are another player, we are only connected to the host, so we expect an acknowledgment from the host.
// In both cases, this represents the list of peers the current user is connected to.
if (newObjectPlayerNumber === currentPlayerNumber) {
const otherPeerIds = gdjs.multiplayerPeerJsHelper.getAllPeers();
@@ -581,7 +583,7 @@ namespace gdjs {
expectedMessageName: changeOwnerAcknowledgedMessageName,
otherPeerIds,
// If we are not the host, we should revert the ownership if the host does not acknowledge the change.
shouldCancelMessageIfTimesOut: currentPlayerNumber !== 1,
shouldCancelMessageIfTimesOut: !gdjs.multiplayer.isCurrentPlayerHost(),
});
}

View File

@@ -770,6 +770,7 @@ namespace gdjs {
// When the countdown starts, if we are player number 1, we are chosen as the host.
// We then send the peerId to others so they can connect via P2P.
// TODO: this should be sent by the backend, in case the lobby starts without a player 1.
if (getCurrentPlayerNumber() === 1) {
sendPeerId();
}

View File

@@ -35,19 +35,23 @@ namespace gdjs {
invScaleX: float;
/** @deprecated Use `worldInvScale` instead */
invScaleY: float;
timeStep: float;
frameTime: float = 0;
stepped: boolean = false;
timeScale: float = 1;
world: Box2D.b2World;
staticBody: Box2D.b2Body;
/** Contact listener to keep track of current collisions */
contactListener: Box2D.JSContactListener;
_nextJointId: number = 1;
/** Start with 1 so the user is safe from default variables value (0) */
joints: { [key: string]: Box2D.b2Joint } = {};
/** Avoid creating new vectors all the time */
_tempb2Vec2 = new Box2D.b2Vec2(0, 0);
/** Sometimes two vectors are needed on the same function call */
_tempb2Vec2Sec = new Box2D.b2Vec2(0, 0);
/**
* List of physics behavior in the runtimeScene. It should be updated
@@ -68,9 +72,7 @@ namespace gdjs {
sharedData.worldScale || Math.sqrt(this.scaleX * this.scaleY);
this.worldInvScale = 1 / this.worldScale;
this.timeStep = 1 / 60;
this.world = new Box2D.b2World(
new Box2D.b2Vec2(this.gravityX, this.gravityY)
);
this.world = new Box2D.b2World(this.b2Vec2(this.gravityX, this.gravityY));
this.world.SetAutoClearForces(false);
this.staticBody = this.world.CreateBody(new Box2D.b2BodyDef());
this.contactListener = new Box2D.JSContactListener();
@@ -135,6 +137,13 @@ namespace gdjs {
this.world.SetContactListener(this.contactListener);
}
b2Vec2(x: float, y: float): Box2D.b2Vec2 {
const tempb2Vec2 = this._tempb2Vec2;
tempb2Vec2.set_x(x);
tempb2Vec2.set_y(y);
return tempb2Vec2;
}
// (string)jointId -> (b2Joint)b2Joint
static getSharedData(
runtimeScene: gdjs.RuntimeScene,
@@ -300,14 +309,11 @@ namespace gdjs {
}
}
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
if (
// @ts-ignore
runtimeScene.physics2SharedData &&
// @ts-ignore
runtimeScene.physics2SharedData.world
) {
// @ts-ignore
Box2D.destroy(runtimeScene.physics2SharedData.world);
const physics2SharedData = runtimeScene.physics2SharedData;
if (physics2SharedData && physics2SharedData.world) {
Box2D.destroy(physics2SharedData.world);
Box2D.destroy(physics2SharedData._tempb2Vec2);
Box2D.destroy(physics2SharedData._tempb2Vec2Sec);
}
});
@@ -358,8 +364,6 @@ namespace gdjs {
currentContacts: Array<Physics2RuntimeBehavior>;
destroyedDuringFrameLogic: boolean;
_body: Box2D.b2Body | null = null;
/** Avoid creating new vectors all the time */
_tempb2Vec2: Box2D.b2Vec2;
/**
* sharedData is a reference to the shared data of the scene, that registers
@@ -367,8 +371,6 @@ namespace gdjs {
* before stepping the world.
*/
_sharedData: Physics2SharedData;
/** Sometimes two vectors are needed on the same function call */
_tempb2Vec2Sec: Box2D.b2Vec2;
_objectOldX: number = 0;
_objectOldY: number = 0;
@@ -414,22 +416,22 @@ namespace gdjs {
instanceContainer.getScene(),
behaviorData.name
);
this._tempb2Vec2 = new Box2D.b2Vec2();
this._tempb2Vec2Sec = new Box2D.b2Vec2();
this._sharedData.addToBehaviorsList(this);
}
// Stores a Box2D pointer of created vertices
b2Vec2(x: float, y: float): Box2D.b2Vec2 {
this._tempb2Vec2.set_x(x);
this._tempb2Vec2.set_y(y);
return this._tempb2Vec2;
const tempb2Vec2 = this._sharedData._tempb2Vec2;
tempb2Vec2.set_x(x);
tempb2Vec2.set_y(y);
return tempb2Vec2;
}
b2Vec2Sec(x: float, y: float): Box2D.b2Vec2 {
this._tempb2Vec2Sec.set_x(x);
this._tempb2Vec2Sec.set_y(y);
return this._tempb2Vec2Sec;
const tempb2Vec2Sec = this._sharedData._tempb2Vec2Sec;
tempb2Vec2Sec.set_x(x);
tempb2Vec2Sec.set_y(y);
return tempb2Vec2Sec;
}
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {

View File

@@ -34,6 +34,7 @@ namespace gdjs {
let _websocket: WebSocket | null = null;
type AuthenticationWindowStatus = 'logged' | 'errored' | 'dismissed';
type AuthenticationWindowOptions = { disableGuestLogin: boolean };
const handleAutomaticGamesPlatformAuthentication = (
runtimeScene: gdjs.RuntimeScene
@@ -119,19 +120,33 @@ namespace gdjs {
runtimeGame,
gameId,
connectionId,
authWindowOptions,
}: {
runtimeGame: gdjs.RuntimeGame;
gameId: string;
connectionId?: string;
authWindowOptions?: AuthenticationWindowOptions;
}) => {
// Uncomment to test the case of a failing loading:
// return 'https://gd.games.wronglink';
return `https://gd.games/auth?gameId=${gameId}${
connectionId ? `&connectionId=${connectionId}` : ''
}${
runtimeGame.isUsingGDevelopDevelopmentEnvironment() ? '&dev=true' : ''
}&allowLoginProviders=true`;
const baseUrl = 'https://gd.games';
// const baseUrl = 'http://localhost:4000';
const searchParams = new URLSearchParams();
searchParams.set('gameId', gameId);
if (connectionId) searchParams.set('connectionId', connectionId);
if (runtimeGame.isUsingGDevelopDevelopmentEnvironment()) {
searchParams.set('dev', 'true');
}
searchParams.set('allowLoginProviders', 'true');
if (authWindowOptions) {
for (const [key, value] of Object.entries(authWindowOptions)) {
searchParams.set(key, value.toString());
}
}
return `${baseUrl}/auth?${searchParams.toString()}`;
};
/**
@@ -447,6 +462,7 @@ namespace gdjs {
onDone?: (status: 'logged' | 'errored' | 'dismissed') => void;
}) {
const allowedOrigins = ['https://liluo.io', 'https://gd.games'];
// const allowedOrigins = ['localhost:4000'];
// Check origin of message.
if (checkOrigin && !allowedOrigins.includes(event.origin)) {
@@ -715,7 +731,8 @@ namespace gdjs {
*/
const openAuthenticationWindowForElectron = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
gameId: string,
authWindowOptions: AuthenticationWindowOptions
) =>
setupWebsocketForAuthenticationWindow(
runtimeScene,
@@ -725,6 +742,7 @@ namespace gdjs {
runtimeGame: runtimeScene.getGame(),
gameId,
connectionId,
authWindowOptions,
});
const electron = runtimeScene.getGame().getRenderer().getElectron();
@@ -748,7 +766,8 @@ namespace gdjs {
*/
const openAuthenticationWindowForCordovaWithWebSocket = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
gameId: string,
authWindowOptions: AuthenticationWindowOptions
) =>
setupWebsocketForAuthenticationWindow(
runtimeScene,
@@ -758,6 +777,7 @@ namespace gdjs {
runtimeGame: runtimeScene.getGame(),
gameId,
connectionId,
authWindowOptions,
});
SafariViewController.isAvailable(function (available: boolean) {
@@ -798,13 +818,15 @@ namespace gdjs {
*/
const openAuthenticationWindowForWeb = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
gameId: string,
authWindowOptions: AuthenticationWindowOptions
) =>
new Promise<AuthenticationWindowStatus>((resolve) => {
// If we're on a browser, open a new window.
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
authWindowOptions,
});
// Listen to messages posted by the authentication window, so that we can
@@ -856,7 +878,8 @@ namespace gdjs {
*/
const openAuthenticationIframeForWeb = (
runtimeScene: gdjs.RuntimeScene,
gameId: string
gameId: string,
authWindowOptions: AuthenticationWindowOptions
) =>
new Promise<AuthenticationWindowStatus>((resolve) => {
if (
@@ -873,6 +896,7 @@ namespace gdjs {
const targetUrl = getAuthWindowUrl({
runtimeGame: runtimeScene.getGame(),
gameId,
authWindowOptions,
});
// Listen to messages posted by the authentication window, so that we can
@@ -903,7 +927,10 @@ namespace gdjs {
* Action to display the authentication window to the user.
*/
export const openAuthenticationWindow = (
runtimeScene: gdjs.RuntimeScene
runtimeScene: gdjs.RuntimeScene,
authWindowOptions: AuthenticationWindowOptions = {
disableGuestLogin: false,
}
): gdjs.PromiseTask<{ status: 'logged' | 'errored' | 'dismissed' }> =>
new gdjs.PromiseTask(
new Promise((resolve) => {
@@ -1000,14 +1027,16 @@ namespace gdjs {
// - Desktop game running on Electron.
status = await openAuthenticationWindowForElectron(
runtimeScene,
_gameId
_gameId,
authWindowOptions
);
break;
case 'cordova-websocket':
// The game is an iOS app.
status = await openAuthenticationWindowForCordovaWithWebSocket(
runtimeScene,
_gameId
_gameId,
authWindowOptions
);
break;
case 'web-iframe':
@@ -1015,7 +1044,8 @@ namespace gdjs {
// - Preview in GDevelop mobile app (iOS only)
status = await openAuthenticationIframeForWeb(
runtimeScene,
_gameId
_gameId,
authWindowOptions
);
break;
case 'web':
@@ -1027,7 +1057,8 @@ namespace gdjs {
// - Or a web game accessed via a mobile browser (Android/iOS).
status = await openAuthenticationWindowForWeb(
runtimeScene,
_gameId
_gameId,
authWindowOptions
);
break;
}

View File

@@ -11,16 +11,18 @@ This project is released under the MIT License.
#include "GDCore/Project/InitialInstance.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Project/PropertyDescriptor.h"
using namespace std;
ShapePainterObjectBase::ShapePainterObjectBase()
: fillOpacity(255),
fillColor("255;255;255"),
outlineSize(1),
outlineOpacity(255),
outlineColor("0;0;0"),
clearBetweenFrames(true),
absoluteCoordinates(false),
antialiasing("none") {}
@@ -37,7 +39,6 @@ void ShapePainterObjectBase::DoUnserializeFrom(
.GetValue()
.GetInt();
const auto& fillColorElement = element.GetChild("fillColor", 0, "FillColor");
if (fillColorElement.GetValue().IsString()) {
fillColor = fillColorElement.GetStringValue();
@@ -46,11 +47,14 @@ void ShapePainterObjectBase::DoUnserializeFrom(
int fillColorR = fillColorElement.GetIntAttribute("r");
int fillColorG = fillColorElement.GetIntAttribute("g");
int fillColorB = fillColorElement.GetIntAttribute("b");
fillColor = gd::String::From(fillColorR) + ";" + gd::String::From(fillColorG) + ";" + gd::String::From(fillColorB);
fillColor = gd::String::From(fillColorR) + ";" +
gd::String::From(fillColorG) + ";" +
gd::String::From(fillColorB);
// end of compatibility code
}
const auto& outlineColorElement = element.GetChild("outlineColor", 0, "OutlineColor");
const auto& outlineColorElement =
element.GetChild("outlineColor", 0, "OutlineColor");
if (outlineColorElement.GetValue().IsString()) {
outlineColor = outlineColorElement.GetStringValue();
} else {
@@ -58,7 +62,9 @@ void ShapePainterObjectBase::DoUnserializeFrom(
int outlineColorR = outlineColorElement.GetIntAttribute("r");
int outlineColorG = outlineColorElement.GetIntAttribute("g");
int outlineColorB = outlineColorElement.GetIntAttribute("b");
outlineColor = gd::String::From(outlineColorR) + ";" + gd::String::From(outlineColorG) + ";" + gd::String::From(outlineColorB);
outlineColor = gd::String::From(outlineColorR) + ";" +
gd::String::From(outlineColorG) + ";" +
gd::String::From(outlineColorB);
// end of compatibility code
}
@@ -94,8 +100,9 @@ void ShapePainterObjectBase::DoSerializeTo(
auto rgb = fillColor.Split(';');
auto& fillColorElement = element.AddChild("fillColor");
if (rgb.size() == 3) {
// Still serialize the old particle color components for compatibility with GDevelop <= 5.4.212.
// Remove this in a few releases (or when hex strings are accepted for the color).
// Still serialize the old particle color components for compatibility
// with GDevelop <= 5.4.212. Remove this in a few releases (or when hex
// strings are accepted for the color).
fillColorElement.AddChild("r").SetValue(rgb[0].To<double>());
fillColorElement.AddChild("g").SetValue(rgb[1].To<double>());
fillColorElement.AddChild("b").SetValue(rgb[2].To<double>());
@@ -108,8 +115,9 @@ void ShapePainterObjectBase::DoSerializeTo(
auto rgb = outlineColor.Split(';');
auto& outlineColorElement = element.AddChild("outlineColor");
if (rgb.size() == 3) {
// Still serialize the old particle color components for compatibility with GDevelop <= 5.4.212.
// Remove this in a few releases (or when hex strings are accepted for the color).
// Still serialize the old particle color components for compatibility
// with GDevelop <= 5.4.212. Remove this in a few releases (or when hex
// strings are accepted for the color).
outlineColorElement.AddChild("r").SetValue(rgb[0].To<double>());
outlineColorElement.AddChild("g").SetValue(rgb[1].To<double>());
outlineColorElement.AddChild("b").SetValue(rgb[2].To<double>());
@@ -143,7 +151,7 @@ void ShapePainterObjectBase::SetOutlineOpacity(double val) {
}
bool ShapePainterObject::UpdateProperty(const gd::String& propertyName,
const gd::String& newValue) {
const gd::String& newValue) {
if (propertyName == "fillOpacity") {
SetFillOpacity(newValue.To<double>());
return true;
@@ -186,8 +194,8 @@ bool ShapePainterObject::UpdateProperty(const gd::String& propertyName,
return false;
}
std::map<gd::String, gd::PropertyDescriptor>
ShapePainterObject::GetProperties() const {
std::map<gd::String, gd::PropertyDescriptor> ShapePainterObject::GetProperties()
const {
std::map<gd::String, gd::PropertyDescriptor> objectProperties;
objectProperties["fillColor"]
@@ -231,7 +239,9 @@ ShapePainterObject::GetProperties() const {
.SetType("boolean")
.SetLabel(_("Clear drawing at each frame"))
.SetGroup(_("Drawing"))
.SetDescription(_("When activated, clear the previous render at each frame. Otherwise, shapes are staying on the screen until you clear manually the object in events."));
.SetDescription(_("When activated, clear the previous render at each "
"frame. Otherwise, shapes are staying on the screen "
"until you clear manually the object in events."));
objectProperties["antialiasing"]
.SetValue(GetAntialiasing())

View File

@@ -32,13 +32,13 @@ class GD_EXTENSION_API ShapePainterObjectBase {
void SetOutlineOpacity(double val);
inline double GetOutlineOpacity() const { return outlineOpacity; };
void SetOutlineColor(const gd::String& color);
void SetOutlineColor(const gd::String& color) { outlineColor = color; };
const gd::String& GetOutlineColor() const { return outlineColor; };
void SetFillOpacity(double val);
inline double GetFillOpacity() const { return fillOpacity; };
void SetFillColor(const gd::String& color);
void SetFillColor(const gd::String& color) { fillColor = color; };
const gd::String& GetFillColor() const { return fillColor; };
inline void SetCoordinatesAbsolute() { absoluteCoordinates = true; }

View File

@@ -323,6 +323,7 @@ module.exports = {
this._pixiResourcesLoader
.getSpineData(this._project, this._spineResourceName)
.then((spineDataOrLoadingError) => {
if (this._wasDestroyed) return;
if (this._spine) this._pixiObject.removeChild(this._spine);
if (!spineDataOrLoadingError.skeleton) {

View File

@@ -194,5 +194,13 @@ namespace gdjs {
? resource
: null;
}
/**
* To be called when the game is disposed.
* Clear the Spine Atlases loaded in this manager.
*/
dispose(): void {
this._loadedSpineAtlases.clear();
this._loadingSpineAtlases.clear();
}
}
}

View File

@@ -115,5 +115,13 @@ namespace gdjs {
? resource
: null;
}
/**
* To be called when the game is disposed.
* Clear the Spine skeleton data loaded in this manager.
*/
dispose(): void {
this._loadedSpines.clear();
}
}
}

View File

@@ -711,6 +711,7 @@ module.exports = {
this._pixiResourcesLoader
.loadFontFamily(this._project, fontResourceName)
.then((fontFamily) => {
if (this._wasDestroyed) return;
this._pixiText.style.fontFamily = fontFamily;
this._pixiText.dirty = true;
})

View File

@@ -129,10 +129,27 @@ namespace gdjs {
// Hide the input entirely if the layer is not visible.
// Because this object is rendered as a DOM element (and not part of the PixiJS
// scene graph), we have to do this manually.
const layer = this._instanceContainer.getLayer(this._object.getLayer());
if (!layer.isVisible()) {
this._input.style.display = 'none';
return;
{
let instanceContainer = this._instanceContainer;
let object: gdjs.RuntimeObject = this._object;
let hasParent = true;
do {
const layer = instanceContainer.getLayer(object.getLayer());
if (!layer.isVisible() || !object.isVisible()) {
this._input.style.display = 'none';
return;
}
// TODO Declare an interface to move up in the object tree.
if (
instanceContainer instanceof
gdjs.CustomRuntimeObjectInstanceContainer
) {
object = instanceContainer.getOwner();
instanceContainer = object.getInstanceContainer();
} else {
hasParent = false;
}
} while (hasParent);
}
const workingPoint: FloatPoint = gdjs.staticArray(
@@ -141,6 +158,7 @@ namespace gdjs {
const runtimeGame = this._instanceContainer.getGame();
const runtimeGameRenderer = runtimeGame.getRenderer();
const layer = this._instanceContainer.getLayer(this._object.getLayer());
const topLeftCanvasCoordinates = layer.convertInverseCoords(
this._object.x,
this._object.y,

View File

@@ -1760,6 +1760,7 @@ module.exports = {
levelIndex,
pako,
(tileMap) => {
if (this._wasDestroyed) return;
if (!tileMap) {
this._onLoadingError();
// _loadTileMapWithCallback already log errors
@@ -1781,6 +1782,7 @@ module.exports = {
tilesetJsonFile,
levelIndex,
(textureCache) => {
if (this._wasDestroyed) return;
if (!textureCache) {
this._onLoadingError();
// getOrLoadTextureCache already log warns and errors.
@@ -1809,6 +1811,7 @@ module.exports = {
} else {
// Wait for the atlas image to load.
atlasTexture.once('update', () => {
if (this._wasDestroyed) return;
loadTileMap();
});
}
@@ -1855,6 +1858,7 @@ module.exports = {
tilesetJsonFile,
levelIndex,
(textureCache) => {
if (this._wasDestroyed) return;
if (!textureCache) {
this._onLoadingError();
// getOrLoadTextureCache already log warns and errors.
@@ -2208,6 +2212,7 @@ module.exports = {
columnCount,
rowCount,
(tileMap) => {
if (this._wasDestroyed) return;
if (!tileMap) {
this._onLoadingError();
console.error('Could not parse tilemap.');
@@ -2230,6 +2235,7 @@ module.exports = {
/** @type {TileMapHelper.TileTextureCache | null} */
textureCache
) => {
if (this._wasDestroyed) return;
this._onLoadingSuccess();
if (!this._editableTileMap) return;
@@ -2257,6 +2263,7 @@ module.exports = {
} else {
// Wait for the atlas image to load.
atlasTexture.once('update', () => {
if (this._wasDestroyed) return;
loadTileMap();
});
}
@@ -2293,6 +2300,7 @@ module.exports = {
/** @type {TileMapHelper.TileTextureCache | null} */
textureCache
) => {
if (this._wasDestroyed) return;
this._onLoadingSuccess();
if (!this._editableTileMap) return;
@@ -2536,6 +2544,7 @@ module.exports = {
0, // levelIndex
pako,
(tileMap) => {
if (this._wasDestroyed) return;
if (!tileMap) {
this._onLoadingError();
// _loadTiledMapWithCallback already log errors

View File

@@ -609,6 +609,7 @@ module.exports = {
that._pixiObject.texture.on('error', function () {
that._pixiObject.texture.off('error', this);
if (this._wasDestroyed) return;
that._pixiObject.texture = that._pixiResourcesLoader.getInvalidPIXITexture();
});

View File

@@ -204,7 +204,9 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the behavior
// it from the behavior.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._runtimeScene;\n" +
// By convention of Behavior Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
@@ -284,7 +286,9 @@ gd::String EventsCodeGenerator::GenerateObjectEventsFunctionCode(
gd::String fullPreludeCode =
preludeCode + "\n" + "var that = this;\n" +
// runtimeScene is supposed to be always accessible, read
// it from the object
// it from the object.
// TODO: this should be renamed to "instanceContainer" and have the code generation
// adapted for this (rely less on `gdjs.RuntimeScene`, and more on `RuntimeInstanceContainer`).
"var runtimeScene = this._instanceContainer;\n" +
// By convention of Object Events Function, the object is accessible
// as a parameter called "Object", and thisObjectList is an array
@@ -1386,14 +1390,22 @@ gd::String EventsCodeGenerator::GenerateGetVariable(
} else if (scope == LAYOUT_VARIABLE) {
output = "runtimeScene.getScene().getVariables()";
const auto *legacySceneVariables = GetProjectScopedContainers().GetLegacySceneVariables();
if (HasProjectAndLayout()) {
variables = &GetLayout().GetVariables();
} else if (legacySceneVariables && legacySceneVariables->Has(variableName)) {
variables = legacySceneVariables;
output = "eventsFunctionContext.sceneVariablesForExtension";
}
} else if (scope == PROJECT_VARIABLE) {
output = "runtimeScene.getGame().getVariables()";
const auto *legacyGlobalVariables = GetProjectScopedContainers().GetLegacyGlobalVariables();
if (HasProjectAndLayout()) {
variables = &GetProject().GetVariables();
} else if (legacyGlobalVariables && legacyGlobalVariables->Has(variableName)) {
variables = legacyGlobalVariables;
output = "eventsFunctionContext.globalVariablesForExtension";
}
} else {
std::vector<gd::String> realObjects =

View File

@@ -102,6 +102,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
/*includeWebsocketDebuggerClient=*/false,
/*includeWindowMessageDebuggerClient=*/false,
/*includeMinimalDebuggerClient=*/false,
/*includeCaptureManager*/ false,
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
@@ -119,8 +120,11 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
helper.ExportEffectIncludes(exportedProject, includesFiles);
// Export events
if (!helper.ExportEventsCode(exportedProject, codeOutputDir, includesFiles,
wholeProjectDiagnosticReport, false)) {
if (!helper.ExportEventsCode(exportedProject,
codeOutputDir,
includesFiles,
wholeProjectDiagnosticReport,
false)) {
gd::LogError(_("Error during exporting! Unable to export events:\n") +
lastError);
return false;
@@ -139,11 +143,11 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
gd::SceneResourcesFinder::FindProjectResources(exportedProject);
std::unordered_map<gd::String, std::set<gd::String>> scenesUsedResources;
for (std::size_t layoutIndex = 0;
layoutIndex < exportedProject.GetLayoutsCount(); layoutIndex++) {
layoutIndex < exportedProject.GetLayoutsCount();
layoutIndex++) {
auto &layout = exportedProject.GetLayout(layoutIndex);
scenesUsedResources[layout.GetName()] =
gd::SceneResourcesFinder::FindSceneResources(exportedProject,
layout);
gd::SceneResourcesFinder::FindSceneResources(exportedProject, layout);
}
// Strip the project (*after* generating events as the events may use
@@ -152,8 +156,11 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
//...and export it
gd::SerializerElement noRuntimeGameOptions;
helper.ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
noRuntimeGameOptions, projectUsedResources,
helper.ExportProjectData(fs,
exportedProject,
codeOutputDir + "/data.js",
noRuntimeGameOptions,
projectUsedResources,
scenesUsedResources);
includesFiles.push_back(codeOutputDir + "/data.js");
@@ -196,6 +203,13 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
if (!helper.ExportElectronFiles(
exportedProject, options.exportPath, usedExtensions))
return false;
if (!helper.ExportBuildResourcesElectronFiles(
// It's important to use the original project here, as the exported
// project can have its resources modified.
options.project,
options.exportPath))
return false;
} else if (options.target == "facebookInstantGames") {
if (!exportProject(options.exportPath)) return false;

View File

@@ -24,6 +24,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/AbstractFileSystem.h"
#include "GDCore/IDE/CaptureOptions.h"
#include "GDCore/IDE/Events/UsedExtensionsFinder.h"
#include "GDCore/IDE/ExportedDependencyResolver.h"
#include "GDCore/IDE/Project/ProjectResourcesCopier.h"
@@ -156,6 +157,8 @@ bool ExporterHelper::ExportProjectForPixiPreview(
options.useWindowMessageDebuggerClient,
/*includeMinimalDebuggerClient=*/
options.useMinimalDebuggerClient,
/*includeCaptureManager=*/
!options.captureOptions.IsEmpty(),
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
includesFiles);
@@ -269,6 +272,22 @@ bool ExporterHelper::ExportProjectForPixiPreview(
.SetStringValue(options.sourceGameId);
}
if (!options.captureOptions.IsEmpty()) {
auto &captureOptionsElement = runtimeGameOptions.AddChild("captureOptions");
const auto &screenshots = options.captureOptions.GetScreenshots();
if (!screenshots.empty()) {
auto &screenshotsElement = captureOptionsElement.AddChild("screenshots");
screenshotsElement.ConsiderAsArrayOf("screenshot");
for (const auto &screenshot : screenshots) {
screenshotsElement.AddChild("screenshot")
.SetIntAttribute("delayTimeInSeconds",
screenshot.GetDelayTimeInSeconds())
.SetStringAttribute("signedUrl", screenshot.GetSignedUrl())
.SetStringAttribute("publicUrl", screenshot.GetPublicUrl());
}
}
}
// Pass in the options the list of scripts files - useful for hot-reloading.
auto &scriptFilesElement = runtimeGameOptions.AddChild("scriptFiles");
scriptFilesElement.ConsiderAsArrayOf("scriptFile");
@@ -698,6 +717,11 @@ bool ExporterHelper::ExportElectronFiles(const gd::Project &project,
}
}
return true;
}
bool ExporterHelper::ExportBuildResourcesElectronFiles(
const gd::Project &project, gd::String exportDir) {
auto &platformSpecificAssets = project.GetPlatformSpecificAssets();
auto &resourceManager = project.GetResourcesManager();
@@ -705,6 +729,7 @@ bool ExporterHelper::ExportElectronFiles(const gd::Project &project,
resourceManager
.GetResource(platformSpecificAssets.Get("desktop", "icon-512"))
.GetFile();
auto projectDirectory = gd::AbstractFileSystem::NormalizeSeparator(
fs.DirNameFrom(project.GetProjectFile()));
fs.MakeAbsolute(iconFilename, projectDirectory);
@@ -756,6 +781,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
bool includeWebsocketDebuggerClient,
bool includeWindowMessageDebuggerClient,
bool includeMinimalDebuggerClient,
bool includeCaptureManager,
gd::String gdevelopLogoStyle,
std::vector<gd::String> &includesFiles) {
// First, do not forget common includes (they must be included before events
@@ -872,6 +898,9 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles,
"Extensions/3D/CustomRuntimeObject3DRenderer.js");
}
if (includeCaptureManager) {
InsertUnique(includesFiles, "capturemanager.js");
}
}
void ExporterHelper::RemoveIncludes(bool pixiRenderers,

View File

@@ -11,6 +11,7 @@
#include <unordered_map>
#include <vector>
#include "GDCore/IDE/CaptureOptions.h"
#include "GDCore/String.h"
namespace gd {
class Project;
@@ -20,6 +21,8 @@ class SerializerElement;
class AbstractFileSystem;
class ResourcesManager;
class WholeProjectDiagnosticReport;
class CaptureOptions;
class Screenshot;
} // namespace gd
class wxProgressDialog;
@@ -48,7 +51,7 @@ struct PreviewExportOptions {
playerId(""),
playerUsername(""),
playerToken(""),
allowAuthenticationUsingIframeForPreview(false){};
allowAuthenticationUsingIframeForPreview(false) {};
/**
* \brief Set the address of the debugger server that the game should reach
@@ -216,7 +219,7 @@ struct PreviewExportOptions {
* \brief Set the level of crash reports to be sent to GDevelop APIs.
*/
PreviewExportOptions &SetCrashReportUploadLevel(
const gd::String& crashReportUploadLevel_) {
const gd::String &crashReportUploadLevel_) {
crashReportUploadLevel = crashReportUploadLevel_;
return *this;
}
@@ -224,8 +227,7 @@ struct PreviewExportOptions {
/**
* \brief Set the context of the preview.
*/
PreviewExportOptions &SetPreviewContext(
const gd::String& previewContext_) {
PreviewExportOptions &SetPreviewContext(const gd::String &previewContext_) {
previewContext = previewContext_;
return *this;
}
@@ -234,7 +236,7 @@ struct PreviewExportOptions {
* \brief Set the GDevelop version so the game is aware of it.
*/
PreviewExportOptions &SetGDevelopVersionWithHash(
const gd::String& gdevelopVersionWithHash_) {
const gd::String &gdevelopVersionWithHash_) {
gdevelopVersionWithHash = gdevelopVersionWithHash_;
return *this;
}
@@ -243,7 +245,7 @@ struct PreviewExportOptions {
* \brief Set the template slug that was used to create the project.
*/
PreviewExportOptions &SetProjectTemplateSlug(
const gd::String& projectTemplateSlug_) {
const gd::String &projectTemplateSlug_) {
projectTemplateSlug = projectTemplateSlug_;
return *this;
}
@@ -251,11 +253,26 @@ struct PreviewExportOptions {
/**
* \brief Set the source game id that was used to create the project.
*/
PreviewExportOptions &SetSourceGameId(const gd::String& sourceGameId_) {
PreviewExportOptions &SetSourceGameId(const gd::String &sourceGameId_) {
sourceGameId = sourceGameId_;
return *this;
}
/**
* \brief Set the capture options to be used for taking screenshots or videos
* of the preview.
*/
PreviewExportOptions &AddScreenshotCapture(int delayTimeInSeconds,
const gd::String &signedUrl,
const gd::String &publicUrl) {
gd::Screenshot screenshot;
screenshot.SetDelayTimeInSeconds(delayTimeInSeconds);
screenshot.SetSignedUrl(signedUrl);
screenshot.SetPublicUrl(publicUrl);
captureOptions.AddScreenshot(screenshot);
return *this;
}
gd::Project &project;
gd::String exportPath;
gd::String websocketDebuggerServerAddress;
@@ -283,6 +300,7 @@ struct PreviewExportOptions {
gd::String gdevelopVersionWithHash;
gd::String projectTemplateSlug;
gd::String sourceGameId;
gd::CaptureOptions captureOptions;
};
/**
@@ -298,7 +316,7 @@ struct ExportOptions {
exportPath(exportPath_),
target(""),
fallbackAuthorId(""),
fallbackAuthorUsername(""){};
fallbackAuthorUsername("") {};
/**
* \brief Set the fallback author info (if info not present in project
@@ -338,7 +356,7 @@ class ExporterHelper {
ExporterHelper(gd::AbstractFileSystem &fileSystem,
gd::String gdjsRoot_,
gd::String codeOutputDir);
virtual ~ExporterHelper(){};
virtual ~ExporterHelper() {};
/**
* \brief Return the error that occurred during the last export.
@@ -386,6 +404,7 @@ class ExporterHelper {
bool includeWebsocketDebuggerClient,
bool includeWindowMessageDebuggerClient,
bool includeMinimalDebuggerClient,
bool includeCaptureManager,
gd::String gdevelopLogoStyle,
std::vector<gd::String> &includesFiles);
@@ -523,6 +542,16 @@ class ExporterHelper {
gd::String exportDir,
std::set<gd::String> usedExtensions);
/**
* \brief Generate the Build Resources files for Electron (mainly for the
* icons) for packaging and save it to the export directory.
*
* \param project The original (non modified) project to be used.
* \param exportDir The directory where the files must be created.
*/
bool ExportBuildResourcesElectronFiles(const gd::Project &project,
gd::String exportDir);
/**
* \brief Generate the Facebook Instant Games files for packaging and save it
* to the export directory.

View File

@@ -290,6 +290,14 @@ namespace gdjs {
return this._runtimeScene;
}
getOwner(): gdjs.CustomRuntimeObject {
return this._customObject;
}
getAsyncTasksManager(): AsyncTasksManager {
return this._runtimeScene.getAsyncTasksManager();
}
getUnrotatedViewportMinX(): float {
return this._customObject.getInnerAreaMinX();
}

View File

@@ -143,5 +143,25 @@ namespace gdjs {
this._loadedThreeModels.getFromName(resourceName) || this._invalidModel
);
}
/**
* To be called when the game is disposed.
* Clear the models, resources loaded and destroy 3D models loaders in this manager.
*/
dispose(): void {
this._loadedThreeModels.clear();
this._downloadedArrayBuffers.clear();
this._loader = null;
this._dracoLoader = null;
if (this._invalidModel) {
this._invalidModel.cameras = [];
this._invalidModel.animations = [];
this._invalidModel.scenes = [];
this._invalidModel.userData = {};
this._invalidModel.asset = {};
this._invalidModel.scene.clear();
}
}
}
}

View File

@@ -454,6 +454,16 @@ namespace gdjs {
});
}
/**
* To be called when the game is disposed.
* Dispose all the resource managers.
*/
dispose(): void {
for (const resourceManager of this._resourceManagersMap.values()) {
resourceManager.dispose();
}
}
/**
* Put a given scene at the end of the queue.
*

View File

@@ -29,5 +29,11 @@ namespace gdjs {
* Return the kind of resources handled by this manager.
*/
getResourceKinds(): Array<ResourceKind>;
/**
* Should clear all resources, data, loaders stored by this manager.
* Using the manager after calling this method is undefined behavior.
*/
dispose(): void;
}
}

View File

@@ -78,6 +78,8 @@ namespace gdjs {
*/
abstract getScene(): gdjs.RuntimeScene;
abstract getAsyncTasksManager(): gdjs.AsyncTasksManager;
/**
* Convert a point from the canvas coordinates (for example,
* the mouse position) to the container coordinates.

View File

@@ -0,0 +1,83 @@
/*
* GDevelop JS Platform
* Copyright 2013-2016 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
export type CaptureOptions = {
screenshots?: Array<{
delayTimeInSeconds: number;
signedUrl: string;
publicUrl: string;
}>;
};
/**
* Helper function to convert a base64 string to a Blob, which can be uploaded to a server.
*/
const base64ToBlob = (base64) => {
const byteString = atob(base64.split(',')[1]);
const mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
const arrayBuffer = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++) {
arrayBuffer[i] = byteString.charCodeAt(i);
}
return new Blob([arrayBuffer], { type: mimeString });
};
/**
* Manage the captures (screenshots, videos, etc...) that need to be taken during the game.
*/
export class CaptureManager {
_gameRenderer: gdjs.RuntimeGameRenderer;
_captureOptions: CaptureOptions;
constructor(
renderer: gdjs.RuntimeGameRenderer,
captureOptions: CaptureOptions
) {
this._gameRenderer = renderer;
this._captureOptions = captureOptions;
}
/**
* To be called when the scene has started rendering.
*/
setupCaptureOptions(isPreview: boolean): void {
if (!isPreview || !this._captureOptions.screenshots) {
return;
}
for (const screenshotCaptureOption of this._captureOptions.screenshots) {
setTimeout(async () => {
await this.takeAndUploadScreenshot(screenshotCaptureOption.signedUrl);
}, screenshotCaptureOption.delayTimeInSeconds);
}
}
/**
* Take a screenshot and upload it to the server.
*/
async takeAndUploadScreenshot(signedUrl: string) {
const canvas = this._gameRenderer.getCanvas();
if (canvas) {
try {
const base64Data = canvas.toDataURL('image/png');
const blobData = base64ToBlob(base64Data);
await fetch(signedUrl, {
method: 'PUT',
body: blobData,
headers: {
'Content-Type': 'image/png',
},
});
} catch (error) {
console.error('Error while uploading screenshot:', error);
}
}
}
}
}

View File

@@ -197,6 +197,15 @@ namespace gdjs {
);
}
}
/**
* To be called when the game is disposed.
* Clear caches of loaded font families.
*/
dispose(): void {
this._loadedFontFamily.clear();
this._loadedFontFamilySet.clear();
}
}
//Register the class to let the engine use it.

View File

@@ -419,10 +419,25 @@ namespace gdjs {
document.addEventListener(
'resume',
function () {
for (let i = 0; i < that._pausedSounds.length; i++) {
const sound = that._pausedSounds[i];
if (!sound.stopped()) {
sound.play();
try {
for (let i = 0; i < that._pausedSounds.length; i++) {
const sound = that._pausedSounds[i];
if (!sound.stopped()) {
sound.play();
}
}
} catch (error) {
if (
error.message &&
typeof error.message === 'string' &&
error.message.startsWith('Maximum call stack size exceeded')
) {
console.warn(
'An error occurred when resuming paused sounds while the game was in background:',
error
);
} else {
throw error;
}
}
that._pausedSounds.length = 0;
@@ -853,6 +868,14 @@ namespace gdjs {
}
}
}
/**
* To be called when the game is disposed.
* Unloads all audio from memory, clear Howl cache and stop all audio.
*/
dispose(): void {
this.unloadAll();
}
}
// Register the class to let the engine use it.

View File

@@ -200,5 +200,14 @@ namespace gdjs {
getLoadedJson(resourceName: string): Object | null {
return this._loadedJsons.getFromName(resourceName) || null;
}
/**
* To be called when the game is disposed.
* Clear the JSONs loaded in this manager.
*/
dispose(): void {
this._loadedJsons.clear();
this._callbacks.clear();
}
}
}

View File

@@ -51,13 +51,15 @@ namespace gdjs {
): void {
// Adapt position of the camera center only if the camera has never moved as:
// * When the camera follows a player/object, it will rarely be at the default position.
// (and if is, it will be moved again by the behavior/events).
// * Cameras not following a player/object are usually UIs which are intuitively
// expected not to "move". Not adapting the center position would make the camera
// move from its initial position (which is centered in the screen) - and anchor
// behavior would behave counterintuitively.
// expected not to "move". Not adapting the center position would make the camera
// move from its initial position (which is centered on the screen) - and anchor
// behavior would behave counterintuitively.
if (
this._cameraX === oldGameResolutionOriginX &&
this._cameraY === oldGameResolutionOriginY &&
// Have a safety margin of 1 pixel to avoid rounding errors.
Math.abs(this._cameraX - oldGameResolutionOriginX) < 1 &&
Math.abs(this._cameraY - oldGameResolutionOriginY) < 1 &&
this._zoomFactor === 1
) {
this._cameraX +=
@@ -65,6 +67,7 @@ namespace gdjs {
this._cameraY +=
this._runtimeScene.getViewportOriginY() - oldGameResolutionOriginY;
}
this._renderer.updatePosition();
this._renderer.updateResolution();
}

View File

@@ -289,6 +289,24 @@ namespace gdjs {
);
}
}
/**
* To be called when the game is disposed.
* Uninstall all the fonts from memory and clear cache of loaded fonts.
*/
dispose(): void {
for (const bitmapFontInstallKey in this._pixiBitmapFontsInUse) {
PIXI.BitmapFont.uninstall(bitmapFontInstallKey);
}
for (const bitmapFontInstallKey of this._pixiBitmapFontsToUninstall) {
PIXI.BitmapFont.uninstall(bitmapFontInstallKey);
}
this._pixiBitmapFontsInUse = {};
this._pixiBitmapFontsToUninstall.length = 0;
this._loadedFontsData.clear();
}
}
// Register the class to let the engine use it.

View File

@@ -463,6 +463,55 @@ namespace gdjs {
}
return particleTexture;
}
/**
* To be called when the game is disposed.
* Clear caches of loaded textures and materials.
*/
dispose(): void {
this._loadedTextures.clear();
const threeTextures: THREE.Texture[] = [];
this._loadedThreeTextures.values(threeTextures);
this._loadedThreeTextures.clear();
for (const threeTexture of threeTextures) {
threeTexture.dispose();
}
const threeMaterials: THREE.Material[] = [];
this._loadedThreeMaterials.values(threeMaterials);
this._loadedThreeMaterials.clear();
for (const threeMaterial of threeMaterials) {
threeMaterial.dispose();
}
for (const pixiTexture of this._diskTextures.values()) {
if (pixiTexture.destroyed) {
continue;
}
pixiTexture.destroy();
}
this._diskTextures.clear();
for (const pixiTexture of this._rectangleTextures.values()) {
if (pixiTexture.destroyed) {
continue;
}
pixiTexture.destroy();
}
this._rectangleTextures.clear();
for (const pixiTexture of this._scaledTextures.values()) {
if (pixiTexture.destroyed) {
continue;
}
pixiTexture.destroy();
}
this._scaledTextures.clear();
}
}
//Register the class to let the engine use it.

View File

@@ -44,6 +44,8 @@ namespace gdjs {
_nextFrameId: integer = 0;
_wasDisposed: boolean = false;
/**
* @param game The game that is being rendered
* @param forceFullscreen If fullscreen should be always activated
@@ -62,6 +64,8 @@ namespace gdjs {
*
*/
createStandardCanvas(parentElement: HTMLElement) {
this._throwIfDisposed();
let gameCanvas: HTMLCanvasElement;
if (typeof THREE !== 'undefined') {
gameCanvas = document.createElement('canvas');
@@ -71,6 +75,7 @@ namespace gdjs {
this._game.getAntialiasingMode() !== 'none' &&
(this._game.isAntialisingEnabledOnMobile() ||
!gdjs.evtTools.common.isMobile()),
preserveDrawingBuffer: true, // Keep to true to allow screenshots.
});
this._threeRenderer.useLegacyLights = true;
this._threeRenderer.autoClear = false;
@@ -89,7 +94,7 @@ namespace gdjs {
// @ts-ignore - reuse the context from Three.js.
context: this._threeRenderer.getContext(),
clearBeforeRender: false,
preserveDrawingBuffer: true,
preserveDrawingBuffer: true, // Keep to true to allow screenshots.
antialias: false,
backgroundAlpha: 0,
// TODO (3D): add a setting for pixel ratio (`resolution: window.devicePixelRatio`)
@@ -281,19 +286,17 @@ namespace gdjs {
if (isFullPage && !this._keepRatio) {
canvasWidth = maxWidth;
canvasHeight = maxHeight;
} else {
if (
(isFullPage && this._keepRatio) ||
canvasWidth > maxWidth ||
canvasHeight > maxHeight
) {
let factor = maxWidth / canvasWidth;
if (canvasHeight * factor > maxHeight) {
factor = maxHeight / canvasHeight;
}
canvasWidth *= factor;
canvasHeight *= factor;
} else if (
(isFullPage && this._keepRatio) ||
canvasWidth > maxWidth ||
canvasHeight > maxHeight
) {
let factor = maxWidth / canvasWidth;
if (canvasHeight * factor > maxHeight) {
factor = maxHeight / canvasHeight;
}
canvasWidth *= factor;
canvasHeight *= factor;
}
// Apply the calculations to the canvas element...
@@ -335,6 +338,7 @@ namespace gdjs {
* Change the margin that must be preserved around the game canvas.
*/
setMargins(top, right, bottom, left): void {
this._throwIfDisposed();
if (
this._marginTop === top &&
this._marginRight === right &&
@@ -356,6 +360,7 @@ namespace gdjs {
* @param height The new height, in pixels.
*/
setWindowSize(width: float, height: float): void {
this._throwIfDisposed();
const remote = this.getElectronRemote();
if (remote) {
const browserWindow = remote.getCurrentWindow();
@@ -378,6 +383,7 @@ namespace gdjs {
* Center the window on screen.
*/
centerWindow() {
this._throwIfDisposed();
const remote = this.getElectronRemote();
if (remote) {
const browserWindow = remote.getCurrentWindow();
@@ -397,6 +403,7 @@ namespace gdjs {
* De/activate fullscreen for the game.
*/
setFullScreen(enable): void {
this._throwIfDisposed();
if (this._forceFullscreen) {
return;
}
@@ -520,6 +527,7 @@ namespace gdjs {
window: Window,
document: Document
) {
this._throwIfDisposed();
const canvas = this._gameCanvas;
if (!canvas) return;
@@ -832,6 +840,7 @@ namespace gdjs {
}
startGameLoop(fn) {
this._throwIfDisposed();
let oldTime = 0;
const gameLoop = (time: float) => {
// Schedule the next frame now to be sure it's called as soon
@@ -849,6 +858,10 @@ namespace gdjs {
requestAnimationFrame(gameLoop);
}
stopGameLoop(): void {
cancelAnimationFrame(this._nextFrameId);
}
getPIXIRenderer() {
return this._pixiRenderer;
}
@@ -924,6 +937,19 @@ namespace gdjs {
// HTML5 games on mobile/browsers don't have a way to close their window/page.
}
/**
* Dispose PixiRenderer, ThreeRenderer and remove canvas from DOM.
*/
dispose() {
this._pixiRenderer?.destroy(true);
this._threeRenderer?.dispose();
this._pixiRenderer = null;
this._threeRenderer = null;
this._gameCanvas = null;
this._domElementsContainer = null;
this._wasDisposed = true;
}
/**
* Get the canvas DOM element.
*/
@@ -980,6 +1006,12 @@ namespace gdjs {
getGame() {
return this._game;
}
private _throwIfDisposed(): void {
if (this._wasDisposed) {
throw 'The RuntimeGameRenderer has been disposed and should not be used anymore.';
}
}
}
//Register the class to let the engine use it.

View File

@@ -100,6 +100,9 @@ namespace gdjs {
/** The source game id that was used to create the project. */
sourceGameId?: string;
/** Any capture that should be done during the preview. */
captureOptions?: CaptureOptions;
/**
* If set, this data is used to authenticate automatically when launching the game.
* This is only useful during previews.
@@ -185,6 +188,14 @@ namespace gdjs {
_disableMetrics: boolean = false;
_isPreview: boolean;
/**
* The capture manager, used to manage captures (screenshots, videos, etc...).
*/
_captureManager: CaptureManager | null;
/** True if the RuntimeGame has been disposed and should not be used anymore. */
_wasDisposed: boolean = false;
/**
* @param data The object (usually stored in data.json) containing the full project data
* @param
@@ -243,6 +254,12 @@ namespace gdjs {
this._debuggerClient = gdjs.DebuggerClient
? new gdjs.DebuggerClient(this)
: null;
this._captureManager = gdjs.CaptureManager
? new gdjs.CaptureManager(
this._renderer,
this._options.captureOptions || {}
)
: null;
this._isPreview = this._options.isPreview || false;
this._sessionId = null;
this._playerId = null;
@@ -549,6 +566,8 @@ namespace gdjs {
* @param height The new height
*/
setGameResolutionSize(width: float, height: float): void {
this._throwIfDisposed();
this._gameResolutionWidth = width;
this._gameResolutionHeight = height;
if (this._adaptGameResolutionAtRuntime) {
@@ -565,15 +584,27 @@ namespace gdjs {
this._gameResolutionWidth =
(this._gameResolutionHeight * windowInnerWidth) /
windowInnerHeight;
} else {
if (this._resizeMode === 'adaptHeight') {
this._gameResolutionHeight =
(this._gameResolutionWidth * windowInnerHeight) /
windowInnerWidth;
} else if (this._resizeMode === 'adaptHeight') {
this._gameResolutionHeight =
(this._gameResolutionWidth * windowInnerHeight) /
windowInnerWidth;
} else if (this._resizeMode === 'scaleOuter') {
const widthFactor = windowInnerWidth / this._originalWidth;
const heightFactor = windowInnerHeight / this._originalHeight;
if (widthFactor < heightFactor) {
this._gameResolutionWidth = this._originalWidth;
this._gameResolutionHeight = Math.floor(
windowInnerHeight / widthFactor
);
} else {
this._gameResolutionWidth = Math.floor(
windowInnerWidth / heightFactor
);
this._gameResolutionHeight = this._originalHeight;
}
}
}
} else {
}
// Don't alter the game resolution. The renderer
@@ -732,6 +763,7 @@ namespace gdjs {
callback: () => void,
progressCallback?: (progress: float) => void
) {
this._throwIfDisposed();
this.loadFirstAssetsAndStartBackgroundLoading(
this._getFirstSceneName(),
progressCallback
@@ -855,6 +887,7 @@ namespace gdjs {
* Start the game loop, to be called once assets are loaded.
*/
startGameLoop() {
this._throwIfDisposed();
try {
if (!this.hasScene()) {
logger.error('The game has no scene.');
@@ -929,7 +962,10 @@ namespace gdjs {
});
setTimeout(() => {
this._setupSessionMetrics();
}, 10000);
}, 4000);
if (this._captureManager) {
this._captureManager.setupCaptureOptions(this._isPreview);
}
} catch (e) {
if (this._debuggerClient) this._debuggerClient.onUncaughtException(e);
@@ -937,6 +973,19 @@ namespace gdjs {
}
}
/**
* Stop game loop, unload all scenes, dispose renderer and resources.
* After calling this method, the RuntimeGame should not be used anymore.
*/
dispose(): void {
this._renderer.stopGameLoop();
this._sceneStack.dispose();
this._renderer.dispose();
this._resourcesLoader.dispose();
this._wasDisposed = true;
}
/**
* Set if the session should be registered.
*/
@@ -1166,7 +1215,7 @@ namespace gdjs {
/**
* Enlarge/reduce the width (or the height) of the game to fill the inner window.
*/
_forceGameResolutionUpdate() {
private _forceGameResolutionUpdate() {
this.setGameResolutionSize(
this._gameResolutionWidth,
this._gameResolutionHeight
@@ -1180,6 +1229,7 @@ namespace gdjs {
startCurrentSceneProfiler(
onProfilerStopped: (oldProfiler: Profiler) => void
) {
this._throwIfDisposed();
const currentScene = this._sceneStack.getCurrentScene();
if (!currentScene) {
return false;
@@ -1192,6 +1242,7 @@ namespace gdjs {
* Stop the profiler for the currently running scene.
*/
stopCurrentSceneProfiler() {
this._throwIfDisposed();
const currentScene = this._sceneStack.getCurrentScene();
if (!currentScene) {
return;
@@ -1313,6 +1364,7 @@ namespace gdjs {
}
updateFromNetworkSyncData(syncData: GameNetworkSyncData) {
this._throwIfDisposed();
if (syncData.var) {
this._variables.updateFromNetworkSyncData(syncData.var);
}
@@ -1336,5 +1388,11 @@ namespace gdjs {
}
}
}
private _throwIfDisposed(): void {
if (this._wasDisposed) {
throw 'The RuntimeGame has been disposed and should not be used anymore.';
}
}
}
}

View File

@@ -56,7 +56,9 @@ namespace gdjs {
this._placement = watermarkData.placement;
this._showAtStartup = watermarkData.showWatermark;
this._isDevEnvironment = game.isUsingGDevelopDevelopmentEnvironment();
this.addStyle();
if (watermarkData.showWatermark) {
this.addStyle();
}
}
displayAtStartup() {

View File

@@ -11,6 +11,7 @@ namespace gdjs {
_wasFirstSceneLoaded: boolean = false;
_isNextLayoutLoading: boolean = false;
_sceneStackSyncDataToApply: SceneStackNetworkSyncData | null = null;
_wasDisposed: boolean = false;
/**
* @param runtimeGame The runtime game that is using the scene stack
@@ -34,6 +35,7 @@ namespace gdjs {
}
step(elapsedTime: float): boolean {
this._throwIfDisposed();
if (this._isNextLayoutLoading || this._stack.length === 0) {
return false;
}
@@ -74,6 +76,8 @@ namespace gdjs {
}
renderWithoutStep(): boolean {
this._throwIfDisposed();
if (this._stack.length === 0) {
return false;
}
@@ -83,6 +87,8 @@ namespace gdjs {
}
pop(popCount = 1): void {
this._throwIfDisposed();
let hasDoneAnyChanges = false;
for (let i = 0; i < popCount; ++i) {
if (this._stack.length <= 1) {
@@ -115,6 +121,8 @@ namespace gdjs {
newSceneName: string,
externalLayoutName?: string
): gdjs.RuntimeScene | null {
this._throwIfDisposed();
// Tell the scene it's being paused
const currentScene = this._stack[this._stack.length - 1];
if (currentScene) {
@@ -139,6 +147,8 @@ namespace gdjs {
newSceneName: string,
externalLayoutName?: string
): gdjs.RuntimeScene {
this._throwIfDisposed();
// Load the new one
const newScene = new gdjs.RuntimeScene(this._runtimeGame);
newScene.loadFromScene(
@@ -171,6 +181,7 @@ namespace gdjs {
* If `clear` is set to true, all running scenes are also removed from the stack of scenes.
*/
replace(newSceneName: string, clear?: boolean): gdjs.RuntimeScene | null {
this._throwIfDisposed();
if (!!clear) {
// Unload all the scenes
while (this._stack.length !== 0) {
@@ -195,6 +206,7 @@ namespace gdjs {
* Return the current gdjs.RuntimeScene being played, or null if none is run.
*/
getCurrentScene(): gdjs.RuntimeScene | null {
this._throwIfDisposed();
if (this._stack.length === 0) {
return null;
}
@@ -209,6 +221,7 @@ namespace gdjs {
}
getAllSceneNames(): Array<string> {
this._throwIfDisposed();
return this._stack.map((scene) => scene.getName());
}
@@ -248,6 +261,7 @@ namespace gdjs {
}
applyUpdateFromNetworkSyncDataIfAny(): boolean {
this._throwIfDisposed();
const sceneStackSyncData = this._sceneStackSyncDataToApply;
let hasMadeChangeToStack = false;
if (!sceneStackSyncData) return hasMadeChangeToStack;
@@ -354,5 +368,23 @@ namespace gdjs {
return hasMadeChangeToStack;
}
/**
* Unload all the scenes and clear the stack.
*/
dispose(): void {
for (const item of this._stack) {
item.unloadScene();
}
this._stack.length = 0;
this._wasDisposed = true;
}
private _throwIfDisposed(): void {
if (this._wasDisposed) {
throw 'The scene stack has been disposed and should not be used anymore.';
}
}
}
}

View File

@@ -831,7 +831,7 @@ namespace gdjs {
}
const unscaledWidth = this._renderer.getUnscaledWidth();
if (unscaledWidth !== 0) {
this.setScaleX(newWidth / unscaledWidth);
this.setScaleX(newWidth / (unscaledWidth * this._preScale));
}
}
@@ -841,7 +841,7 @@ namespace gdjs {
}
const unscaledHeight = this._renderer.getUnscaledHeight();
if (unscaledHeight !== 0) {
this.setScaleY(newHeight / unscaledHeight);
this.setScaleY(newHeight / (unscaledHeight * this._preScale));
}
}

View File

@@ -64,6 +64,7 @@ module.exports = function (config) {
'./newIDE/app/resources/GDJS/Runtime/RuntimeCustomObjectLayer.js',
'./newIDE/app/resources/GDJS/Runtime/timer.js',
'./newIDE/app/resources/GDJS/Runtime/inputmanager.js',
'./newIDE/app/resources/GDJS/Runtime/capturemanager.js',
'./newIDE/app/resources/GDJS/Runtime/runtimegame.js',
'./newIDE/app/resources/GDJS/Runtime/runtimewatermark.js',
'./newIDE/app/resources/GDJS/Runtime/variable.js',

View File

@@ -54,6 +54,11 @@ interface VectorObjectFolderOrObject {
[Const] ObjectFolderOrObject at(unsigned long index);
};
interface VectorScreenshot {
unsigned long size();
[Const, Ref] Screenshot at(unsigned long index);
};
interface MapStringString {
void MapStringString();
@@ -658,10 +663,15 @@ interface ObjectsContainersList {
[Const, Value] DOMString GetTypeOfObject([Const] DOMString objectName);
[Const, Value] DOMString GetTypeOfBehavior([Const] DOMString name, boolean searchInGroups);
[Value] VectorString GetBehaviorsOfObject([Const] DOMString name, boolean searchInGroups);
[Value] VectorString GetBehaviorsOfObject([Const] DOMString objectOrGroupName, boolean searchInGroups);
[Value] VectorString GetBehaviorNamesInObjectOrGroup(
[Const] DOMString objectOrGroupName,
[Const] DOMString behaviorType,
boolean searchInGroups);
[Value] VectorString GetAnimationNamesOfObject([Const] DOMString name);
[Const, Value] DOMString GetTypeOfBehaviorInObjectOrGroup([Const] DOMString objectOrGroupName, [Const] DOMString behaviorName, boolean searchInGroups);
boolean HasObjectOrGroupNamed([Const] DOMString name);
boolean HasObjectNamed([Const] DOMString name);
ObjectsContainersList_VariableExistence HasObjectOrGroupWithVariableNamed([Const] DOMString objectName, [Const] DOMString variableName);
[Const, Ref] ObjectsContainer GetObjectsContainer(unsigned long index);
@@ -1928,6 +1938,21 @@ interface QuickCustomizationVisibilitiesContainer {
QuickCustomization_Visibility Get([Const] DOMString name);
};
interface Screenshot {
long GetDelayTimeInSeconds();
void SetDelayTimeInSeconds(long delayTimeInSeconds);
[Const, Ref] DOMString GetSignedUrl();
void SetSignedUrl([Const] DOMString signedUrl);
[Const, Ref] DOMString GetPublicUrl();
void SetPublicUrl([Const] DOMString publicUrl);
};
interface CaptureOptions {
void AddScreenshot([Const, Ref] Screenshot screenshot);
void ClearScreenshots();
[Const, Ref] VectorScreenshot GetScreenshots();
};
interface BehaviorMetadata {
[Const, Ref] DOMString GetName();
[Const, Ref] DOMString GetFullName();
@@ -2680,6 +2705,8 @@ interface WholeProjectRefactorer {
[Const] DOMString originLayerName,
[Const] DOMString targetLayerName);
unsigned long STATIC_GetLayoutAndExternalLayoutLayerInstancesCount([Ref] Project project, [Ref] Layout layout, [Const] DOMString layerName);
void STATIC_RenameLeaderboards([Ref] Project project, [Const, Ref] MapStringString leaderboardIdMap);
[Value] SetString STATIC_FindAllLeaderboardIds([Ref] Project project);
};
interface EventsBasedObjectDependencyFinder {
@@ -3171,21 +3198,6 @@ interface ArbitraryObjectsWorker {
void Launch([Ref] ObjectsContainer container);
};
interface EventsLeaderboardsLister {
void EventsLeaderboardsLister([Ref] Project project);
[Const, Ref] SetString GetLeaderboardIds();
//Inherited from ArbitraryEventsWorker
void Launch([Ref] EventsList events);
};
interface EventsLeaderboardsRenamer {
void EventsLeaderboardsRenamer([Ref] Project project, [Const, Ref] MapStringString leaderboardIdMap);
//Inherited from ArbitraryEventsWorker
void Launch([Ref] EventsList events);
};
interface EventsParametersLister {
void EventsParametersLister([Ref] Project project);
[Const, Ref] MapStringString GetParametersAndTypes();
@@ -3811,6 +3823,7 @@ interface PreviewExportOptions {
[Ref] PreviewExportOptions SetGDevelopVersionWithHash([Const] DOMString gdevelopVersionWithHash);
[Ref] PreviewExportOptions SetProjectTemplateSlug([Const] DOMString projectTemplateSlug);
[Ref] PreviewExportOptions SetSourceGameId([Const] DOMString sourceGameId);
[Ref] PreviewExportOptions AddScreenshotCapture(long delayTimeInSeconds, [Const] DOMString signedUrl, [Const] DOMString publicUrl);
};
[Prefix="gdjs::"]

View File

@@ -28,8 +28,6 @@
#include <GDCore/IDE/Events/EventsContextAnalyzer.h>
#include <GDCore/IDE/Events/EventsFunctionSelfCallChecker.h>
#include <GDCore/IDE/Events/EventsIdentifiersFinder.h>
#include <GDCore/IDE/Events/EventsLeaderboardsLister.h>
#include <GDCore/IDE/Events/EventsLeaderboardsRenamer.h>
#include <GDCore/IDE/Events/EventsListUnfolder.h>
#include <GDCore/IDE/Events/EventsParametersLister.h>
#include <GDCore/IDE/Events/EventsPositionFinder.h>
@@ -459,6 +457,7 @@ typedef std::shared_ptr<SerializerElement> SharedPtrSerializerElement;
typedef std::vector<UnfilledRequiredBehaviorPropertyProblem>
VectorUnfilledRequiredBehaviorPropertyProblem;
typedef std::vector<const gd::ObjectFolderOrObject*> VectorObjectFolderOrObject;
typedef std::vector<gd::Screenshot> VectorScreenshot;
typedef QuickCustomization::Visibility
QuickCustomization_Visibility;
typedef CustomObjectConfiguration::EdgeAnchor
@@ -665,6 +664,8 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
#define STATIC_MergeLayersInEventsBasedObject MergeLayersInEventsBasedObject
#define STATIC_GetLayoutAndExternalLayoutLayerInstancesCount \
GetLayoutAndExternalLayoutLayerInstancesCount
#define STATIC_RenameLeaderboards RenameLeaderboards
#define STATIC_FindAllLeaderboardIds FindAllLeaderboardIds
#define STATIC_GenerateBehaviorGetterAndSetter GenerateBehaviorGetterAndSetter
#define STATIC_GenerateObjectGetterAndSetter GenerateObjectGetterAndSetter

View File

@@ -7,12 +7,13 @@ module.exports = function (grunt) {
const useMinGW = grunt.option('use-MinGW') || false;
const possibleVariants = [
'release',
'dev',
'debug',
'debug-assertions',
'debug-sanitizers',
];
const variant = grunt.option('variant') || (grunt.option('dev') ? 'dev' : '');
const variant = grunt.option('variant') || (grunt.option('dev') ? 'dev' : 'release');
if (variant && possibleVariants.indexOf(variant) === -1) {
console.error(

View File

@@ -132,6 +132,11 @@ export class VectorObjectFolderOrObject extends EmscriptenObject {
at(index: number): ObjectFolderOrObject;
}
export class VectorScreenshot extends EmscriptenObject {
size(): number;
at(index: number): Screenshot;
}
export class MapStringString extends EmscriptenObject {
constructor();
get(name: string): string;
@@ -602,10 +607,12 @@ export class ObjectsContainersList extends EmscriptenObject {
static makeNewObjectsContainersListForContainers(globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer): ObjectsContainersList;
getTypeOfObject(objectName: string): string;
getTypeOfBehavior(name: string, searchInGroups: boolean): string;
getBehaviorsOfObject(name: string, searchInGroups: boolean): VectorString;
getBehaviorsOfObject(objectOrGroupName: string, searchInGroups: boolean): VectorString;
getBehaviorNamesInObjectOrGroup(objectOrGroupName: string, behaviorType: string, searchInGroups: boolean): VectorString;
getAnimationNamesOfObject(name: string): VectorString;
getTypeOfBehaviorInObjectOrGroup(objectOrGroupName: string, behaviorName: string, searchInGroups: boolean): string;
hasObjectOrGroupNamed(name: string): boolean;
hasObjectNamed(name: string): boolean;
hasObjectOrGroupWithVariableNamed(objectName: string, variableName: string): ObjectsContainersList_VariableExistence;
getObjectsContainer(index: number): ObjectsContainer;
getObjectsContainersCount(): number;
@@ -1542,6 +1549,21 @@ export class QuickCustomizationVisibilitiesContainer extends EmscriptenObject {
get(name: string): QuickCustomization_Visibility;
}
export class Screenshot extends EmscriptenObject {
getDelayTimeInSeconds(): number;
setDelayTimeInSeconds(delayTimeInSeconds: number): void;
getSignedUrl(): string;
setSignedUrl(signedUrl: string): void;
getPublicUrl(): string;
setPublicUrl(publicUrl: string): void;
}
export class CaptureOptions extends EmscriptenObject {
addScreenshot(screenshot: Screenshot): void;
clearScreenshots(): void;
getScreenshots(): VectorScreenshot;
}
export class BehaviorMetadata extends EmscriptenObject {
getName(): string;
getFullName(): string;
@@ -1918,6 +1940,8 @@ export class WholeProjectRefactorer extends EmscriptenObject {
static removeLayerInEventsBasedObject(eventsBasedObject: EventsBasedObject, layerName: string): void;
static mergeLayersInEventsBasedObject(eventsBasedObject: EventsBasedObject, originLayerName: string, targetLayerName: string): void;
static getLayoutAndExternalLayoutLayerInstancesCount(project: Project, layout: Layout, layerName: string): number;
static renameLeaderboards(project: Project, leaderboardIdMap: MapStringString): void;
static findAllLeaderboardIds(project: Project): SetString;
}
export class EventsBasedObjectDependencyFinder extends EmscriptenObject {
@@ -2328,17 +2352,6 @@ export class ArbitraryObjectsWorker extends EmscriptenObject {
launch(container: ObjectsContainer): void;
}
export class EventsLeaderboardsLister extends EmscriptenObject {
constructor(project: Project);
getLeaderboardIds(): SetString;
launch(events: EventsList): void;
}
export class EventsLeaderboardsRenamer extends EmscriptenObject {
constructor(project: Project, leaderboardIdMap: MapStringString);
launch(events: EventsList): void;
}
export class EventsParametersLister extends EmscriptenObject {
constructor(project: Project);
getParametersAndTypes(): MapStringString;
@@ -2832,6 +2845,7 @@ export class PreviewExportOptions extends EmscriptenObject {
setGDevelopVersionWithHash(gdevelopVersionWithHash: string): PreviewExportOptions;
setProjectTemplateSlug(projectTemplateSlug: string): PreviewExportOptions;
setSourceGameId(sourceGameId: string): PreviewExportOptions;
addScreenshotCapture(delayTimeInSeconds: number, signedUrl: string, publicUrl: string): PreviewExportOptions;
}
export class ExportOptions extends EmscriptenObject {

View File

@@ -0,0 +1,8 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdCaptureOptions {
addScreenshot(screenshot: gdScreenshot): void;
clearScreenshots(): void;
getScreenshots(): gdVectorScreenshot;
delete(): void;
ptr: number;
};

View File

@@ -1,8 +0,0 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdEventsLeaderboardsLister {
constructor(project: gdProject): void;
getLeaderboardIds(): gdSetString;
launch(events: gdEventsList): void;
delete(): void;
ptr: number;
};

View File

@@ -1,7 +0,0 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdEventsLeaderboardsRenamer {
constructor(project: gdProject, leaderboardIdMap: gdMapStringString): void;
launch(events: gdEventsList): void;
delete(): void;
ptr: number;
};

View File

@@ -9,10 +9,12 @@ declare class gdObjectsContainersList {
static makeNewObjectsContainersListForContainers(globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer): gdObjectsContainersList;
getTypeOfObject(objectName: string): string;
getTypeOfBehavior(name: string, searchInGroups: boolean): string;
getBehaviorsOfObject(name: string, searchInGroups: boolean): gdVectorString;
getBehaviorsOfObject(objectOrGroupName: string, searchInGroups: boolean): gdVectorString;
getBehaviorNamesInObjectOrGroup(objectOrGroupName: string, behaviorType: string, searchInGroups: boolean): gdVectorString;
getAnimationNamesOfObject(name: string): gdVectorString;
getTypeOfBehaviorInObjectOrGroup(objectOrGroupName: string, behaviorName: string, searchInGroups: boolean): string;
hasObjectOrGroupNamed(name: string): boolean;
hasObjectNamed(name: string): boolean;
hasObjectOrGroupWithVariableNamed(objectName: string, variableName: string): ObjectsContainersList_VariableExistence;
getObjectsContainer(index: number): gdObjectsContainer;
getObjectsContainersCount(): number;

View File

@@ -22,6 +22,7 @@ declare class gdPreviewExportOptions {
setGDevelopVersionWithHash(gdevelopVersionWithHash: string): gdPreviewExportOptions;
setProjectTemplateSlug(projectTemplateSlug: string): gdPreviewExportOptions;
setSourceGameId(sourceGameId: string): gdPreviewExportOptions;
addScreenshotCapture(delayTimeInSeconds: number, signedUrl: string, publicUrl: string): gdPreviewExportOptions;
delete(): void;
ptr: number;
};

View File

@@ -0,0 +1,11 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdScreenshot {
getDelayTimeInSeconds(): number;
setDelayTimeInSeconds(delayTimeInSeconds: number): void;
getSignedUrl(): string;
setSignedUrl(signedUrl: string): void;
getPublicUrl(): string;
setPublicUrl(publicUrl: string): void;
delete(): void;
ptr: number;
};

View File

@@ -0,0 +1,7 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdVectorScreenshot {
size(): number;
at(index: number): gdScreenshot;
delete(): void;
ptr: number;
};

View File

@@ -56,6 +56,8 @@ declare class gdWholeProjectRefactorer {
static removeLayerInEventsBasedObject(eventsBasedObject: gdEventsBasedObject, layerName: string): void;
static mergeLayersInEventsBasedObject(eventsBasedObject: gdEventsBasedObject, originLayerName: string, targetLayerName: string): void;
static getLayoutAndExternalLayoutLayerInstancesCount(project: gdProject, layout: gdLayout, layerName: string): number;
static renameLeaderboards(project: gdProject, leaderboardIdMap: gdMapStringString): void;
static findAllLeaderboardIds(project: gdProject): gdSetString;
delete(): void;
ptr: number;
};

View File

@@ -46,6 +46,7 @@ declare class libGDevelop {
VectorInt: Class<gdVectorInt>;
VectorVariable: Class<gdVectorVariable>;
VectorObjectFolderOrObject: Class<gdVectorObjectFolderOrObject>;
VectorScreenshot: Class<gdVectorScreenshot>;
MapStringString: Class<gdMapStringString>;
MapStringBoolean: Class<gdMapStringBoolean>;
MapStringDouble: Class<gdMapStringDouble>;
@@ -152,6 +153,8 @@ declare class libGDevelop {
QuickCustomization_Visibility: Class<QuickCustomization_Visibility>;
QuickCustomization: Class<gdQuickCustomization>;
QuickCustomizationVisibilitiesContainer: Class<gdQuickCustomizationVisibilitiesContainer>;
Screenshot: Class<gdScreenshot>;
CaptureOptions: Class<gdCaptureOptions>;
BehaviorMetadata: Class<gdBehaviorMetadata>;
EffectMetadata: Class<gdEffectMetadata>;
EventMetadata: Class<gdEventMetadata>;
@@ -221,8 +224,6 @@ declare class libGDevelop {
ProjectResourcesAdder: Class<gdProjectResourcesAdder>;
ArbitraryEventsWorker: Class<gdArbitraryEventsWorker>;
ArbitraryObjectsWorker: Class<gdArbitraryObjectsWorker>;
EventsLeaderboardsLister: Class<gdEventsLeaderboardsLister>;
EventsLeaderboardsRenamer: Class<gdEventsLeaderboardsRenamer>;
EventsParametersLister: Class<gdEventsParametersLister>;
EventsPositionFinder: Class<gdEventsPositionFinder>;
EventsTypesLister: Class<gdEventsTypesLister>;

View File

@@ -1,6 +1,6 @@
# GDevelop IDE
This is the GDevelop 5 editor. It is based on [React](https://facebook.github.io/react/), [Material-UI](http://www.material-ui.com), [Pixi.js](https://github.com/pixijs/pixi.js) and [Electron](https://electron.atom.io/) for the desktop app.
This is the GDevelop 5 editor. It is based on [React](https://facebook.github.io/react/), [Material-UI](http://www.material-ui.com), [Pixi.js](https://github.com/pixijs/pixi.js), [Three.js](https://github.com/mrdoob/three.js) and [Electron](https://electron.atom.io/) for the desktop app.
It uses GDevelop [core C++ classes compiled to Javascript](https://github.com/4ian/GDevelop.js) to work with GDevelop games.
![GDevelop editor](https://raw.githubusercontent.com/4ian/GDevelop/master/newIDE/GDevelop%20screenshot.png "GDevelop editor")

35311
newIDE/app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -85,7 +85,7 @@
"react-test-renderer": "16.14.0",
"react-virtualized": "9.21.1",
"react-window": "1.8.9",
"recharts": "^2.1.10",
"recharts": "^2.10.0",
"remark-gfm": "^3.0.1",
"remark-parse": "^10.0.2",
"semver": "7.0.0",

View File

@@ -1,67 +0,0 @@
<svg width="117" height="162" viewBox="0 0 117 162" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M46.7106 56.0097C59.7298 26.391 93.7243 6.32877 109.094 0C111.128 13.9685 111.128 41.0918 95.5326 76.0809C83.0558 104.072 49.875 130.644 35.8613 140.77L28.1311 137.108C28.8996 122.417 33.6914 85.6283 46.7106 56.0097Z" fill="url(#paint0_linear_2212_486)"/>
<path d="M71.1334 23.8685C85.6777 10.074 99.4695 3.97836 109.131 0C110.488 9.31715 110.94 24.4869 106.462 43.9398C105.408 48.5174 92.0097 51.7354 81.7368 47.8727C70.7054 43.7247 66.1758 28.826 71.1334 23.8685Z" fill="url(#paint1_linear_2212_486)"/>
<path d="M26.3759 66.7921C33.1025 60.4994 40.1223 63.7071 42.699 66.0578C42.699 66.0578 39.5594 75.3511 38.4291 79.2443C37.2989 83.1374 35.4622 90.3893 35.4622 90.3893C30.8512 90.7962 29.2239 100.018 16.3403 99.34C3.4567 98.6619 14.9841 82.1167 16.3403 80.4893C17.6964 78.8619 17.9677 74.6578 26.3759 66.7921Z" fill="url(#paint2_linear_2212_486)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M89.7901 98.8125C90.1563 98.8906 90.3899 99.2509 90.3118 99.6171C89.6287 102.819 89.9916 110.929 97.1451 117.136L97.5275 117.468L97.318 117.929C97.0027 118.622 96.6563 119.665 96.5626 120.774C96.4689 121.883 96.632 122.997 97.2594 123.909C97.4716 124.218 97.3934 124.64 97.0848 124.852C96.7762 125.064 96.3541 124.986 96.1419 124.678C95.2775 123.42 95.1016 121.958 95.2113 120.659C95.3014 119.593 95.5867 118.597 95.8867 117.833C88.6408 111.282 88.2345 102.854 88.9855 99.3342C89.0636 98.9679 89.4238 98.7343 89.7901 98.8125Z" fill="#CF0A80"/>
<path d="M10.3837 69.9033L19.4484 42.9224L27.9799 35.8839L45.5762 40.2563L35.6583 68.8368L10.3837 69.9033Z" fill="url(#paint3_linear_2212_486)" fill-opacity="0.5"/>
<path d="M81.9136 94.6471C82.5918 92.6129 94.1192 75.1851 95.8821 74.4402C115.14 66.3032 117.581 87.0526 116.903 91.6635L116.887 91.7695C116.206 96.4081 114.93 105.091 105.24 105.225C95.4753 105.361 99.8151 90.1717 98.7301 90.1717C97.6452 90.1717 99.1369 92.3416 91.678 99.8005C84.2191 107.259 81.304 96.4756 81.9136 94.6471Z" fill="url(#paint4_linear_2212_486)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M88.9243 59.8322C89.3359 59.6739 89.798 59.8792 89.9563 60.2908C93.823 70.3444 95.7503 82.1737 96.2337 86.8465C96.2791 87.2852 95.9603 87.6776 95.5216 87.723C95.0829 87.7683 94.6905 87.4495 94.6452 87.0108C94.1703 82.4208 92.2647 70.7416 88.4657 60.8641C88.3074 60.4525 88.5127 59.9905 88.9243 59.8322Z" fill="#CF0A80"/>
<ellipse cx="104.787" cy="75.4573" rx="0.950311" ry="1.40985" transform="rotate(-64.5138 104.787 75.4573)" fill="#3B314F"/>
<ellipse cx="100.315" cy="80.8869" rx="0.950311" ry="1.40985" transform="rotate(-64.5138 100.315 80.8869)" fill="#3B314F"/>
<path d="M27.8799 67.3139L35.5787 40.4212L42.8246 33.4058L57.7693 37.7639L49.3459 66.2509L27.8799 67.3139Z" fill="url(#paint5_linear_2212_486)" fill-opacity="0.5"/>
<path d="M57.8648 36.8733C57.7831 37.9767 54.069 42.6283 50.8071 47.1781C49.5166 48.9781 44.7606 51.7545 40.5098 50.0953C35.1962 48.0214 38.0808 39.3275 35.9274 39.5946C33.7741 39.8617 25.1504 51.864 20.1749 48.1082C15.1993 44.3524 25.3577 28.1967 32.9309 24.2509C40.5041 20.305 51.2355 20.887 54.9653 25.6172C58.6952 30.3475 57.9671 35.4941 57.8648 36.8733Z" fill="url(#paint6_linear_2212_486)"/>
<ellipse cx="34.5627" cy="27.7762" rx="0.971528" ry="1.43597" transform="rotate(-91.6962 34.5627 27.7762)" fill="#3B314F"/>
<ellipse cx="46.2513" cy="24.5223" rx="0.806979" ry="1.19721" transform="rotate(-81.6632 46.2513 24.5223)" fill="#3B314F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M52.9466 34.2484C51.3488 36.3704 50.2605 39.1498 49.7548 41.0625C49.6591 41.4245 49.288 41.6404 48.926 41.5447C48.5639 41.449 48.348 41.0779 48.4437 40.7159C48.9778 38.6957 50.1271 35.7384 51.8632 33.4326C53.5893 31.1401 56.0487 29.3124 59.1575 30.0586C62.1643 30.7802 63.6094 32.8427 64.1662 35.0585C64.5965 36.7712 64.506 38.5897 64.2385 40.0178L67.9526 39.275C68.3198 39.2015 68.6771 39.4397 68.7505 39.8069C68.824 40.1741 68.5858 40.5313 68.2186 40.6048L62.3815 41.7722L62.687 40.7029C63.0721 39.3551 63.322 37.264 62.8509 35.389C62.3905 33.5567 61.2589 31.9576 58.841 31.3773C56.5252 30.8215 54.5544 32.113 52.9466 34.2484Z" fill="#CF0A80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.7837 37.7195C40.0363 37.443 40.4652 37.4237 40.7417 37.6763C43.4384 40.1405 49.019 47.1904 49.9095 55.8735C49.9477 56.246 49.6767 56.579 49.3042 56.6172C48.9316 56.6554 48.5986 56.3844 48.5604 56.0119C47.7151 47.77 42.3741 41.0051 39.8269 38.6775C39.5504 38.4249 39.5311 37.996 39.7837 37.7195Z" fill="#CF0A80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.1679 92.1549C31.5411 92.187 31.8174 92.5156 31.7853 92.8887C31.4922 96.2875 29.8602 104.565 25.62 110.874L26.4896 116.923C26.5429 117.294 26.2856 117.638 25.9149 117.691C25.5442 117.744 25.2005 117.487 25.1473 117.116L24.2026 110.545L24.3525 110.327C28.5209 104.258 30.1484 96.0852 30.4341 92.7722C30.4663 92.3991 30.7948 92.1227 31.1679 92.1549Z" fill="#CF0A80"/>
<ellipse cx="30.8175" cy="76.2689" rx="0.512358" ry="1.43597" transform="rotate(-114.883 30.8175 76.2689)" fill="#3B314F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.4861 95.4249C23.8508 95.5098 24.0776 95.8743 23.9927 96.2391C23.219 99.5616 20.4257 107.523 15.3309 113.165V119.276C15.3309 119.65 15.0273 119.954 14.6528 119.954C14.2783 119.954 13.9747 119.65 13.9747 119.276V112.637L14.1542 112.442C19.1438 107.028 21.9177 99.1702 22.6719 95.9315C22.7568 95.5668 23.1213 95.3399 23.4861 95.4249Z" fill="#CF0A80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.9488 74.7743C49.1841 75.0656 49.1386 75.4926 48.8472 75.7278C36.8813 85.3883 27.6399 84.206 24.3305 82.1502C24.0124 81.9526 23.9147 81.5345 24.1123 81.2164C24.3099 80.8983 24.728 80.8006 25.0461 80.9982C27.7039 82.6492 36.3096 84.1069 47.9953 74.6726C48.2866 74.4374 48.7136 74.4829 48.9488 74.7743Z" fill="#CF0A80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.2275 97.3088C84.5256 97.5354 84.5836 97.9608 84.357 98.2589C83.1411 99.8588 81.7788 102.728 81.698 106.066C81.6179 109.373 82.7921 113.184 86.7137 116.739L87.3431 117.31L86.6471 117.797C85.7591 118.419 84.4279 119.431 83.3259 120.441C82.7734 120.947 82.2957 121.437 81.9615 121.865C81.7945 122.079 81.6747 122.263 81.5992 122.414C81.5203 122.573 81.5117 122.651 81.5117 122.666C81.5117 123.041 81.2081 123.345 80.8336 123.345C80.4591 123.345 80.1555 123.041 80.1555 122.666C80.1555 122.356 80.2599 122.061 80.3858 121.809C80.5152 121.55 80.6921 121.287 80.8923 121.03C81.2928 120.517 81.8322 119.97 82.4098 119.441C83.3107 118.615 84.3464 117.797 85.1952 117.171C81.4582 113.507 80.2571 109.548 80.3422 106.034C80.4309 102.368 81.9165 99.2289 83.2773 97.4383C83.5039 97.1402 83.9293 97.0822 84.2275 97.3088Z" fill="#CF0A80"/>
<path d="M26.4513 51.1961C26.0444 49.84 27.265 49.1619 27.6718 48.755C28.3499 48.1556 28.2142 49.2975 27.943 49.9756C27.6718 50.6537 26.8581 52.5523 26.4513 51.1961Z" fill="#E652E0"/>
<path d="M9.22791 106.935C8.68545 106.392 10.0416 103.409 10.9909 103.815V104.629C10.9909 105.443 9.77038 107.477 9.22791 106.935Z" fill="#E652E0"/>
<path d="M100.456 106.383C100.362 107.248 99.5204 107.349 99.1972 107.485C98.6771 107.668 99.0233 107.051 99.3351 106.729C99.647 106.407 100.55 105.519 100.456 106.383Z" fill="#E652E0"/>
<path d="M19.3853 49.0515C19.7124 49.8574 19.0199 50.3454 18.8 50.6188C18.4288 51.0262 18.441 50.3191 18.5628 49.8878C18.6846 49.4565 19.0583 48.2456 19.3853 49.0515Z" fill="#E652E0"/>
<path d="M49.0421 66.0663C48.7333 65.456 49.2443 65.0285 49.3988 64.8C49.6632 64.4565 49.6997 65.0115 49.6323 65.3574C49.5649 65.7033 49.3508 66.6766 49.0421 66.0663Z" fill="#E652E0"/>
<path d="M43.724 132.733L58.876 111.147C62.9817 142.074 54.1512 157.449 49.2227 161.27C50.1025 154.831 45.9235 139.562 43.724 132.733Z" fill="url(#paint7_linear_2212_486)"/>
<path d="M32.635 127.732L41.3328 102.835C13.8154 117.537 6.47652 133.676 6.24677 139.908C10.9643 135.438 25.8046 129.928 32.635 127.732Z" fill="url(#paint8_linear_2212_486)"/>
<defs>
<linearGradient id="paint0_linear_2212_486" x1="80.0838" y1="41.0918" x2="31.5331" y2="143.618" gradientUnits="userSpaceOnUse">
<stop stop-color="#E5DEF9"/>
<stop offset="1" stop-color="#82789E"/>
</linearGradient>
<linearGradient id="paint1_linear_2212_486" x1="108.428" y1="-0.135615" x2="77.9142" y2="49.6357" gradientUnits="userSpaceOnUse">
<stop stop-color="#D3304E"/>
<stop offset="1" stop-color="#9C1B33"/>
</linearGradient>
<linearGradient id="paint2_linear_2212_486" x1="26.4075" y1="63.0884" x2="26.4075" y2="99.3754" gradientUnits="userSpaceOnUse">
<stop stop-color="#F782F2"/>
<stop offset="1" stop-color="#DC34D5"/>
</linearGradient>
<linearGradient id="paint3_linear_2212_486" x1="28.0866" y1="34.9241" x2="16.4624" y2="66.9172" gradientUnits="userSpaceOnUse">
<stop stop-color="#F06CEA"/>
<stop offset="1" stop-color="#F06CEA" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_2212_486" x1="99.4176" y1="72.5684" x2="99.4176" y2="105.226" gradientUnits="userSpaceOnUse">
<stop stop-color="#F782F2"/>
<stop offset="1" stop-color="#DC34D5"/>
</linearGradient>
<linearGradient id="paint5_linear_2212_486" x1="42.9151" y1="32.4491" x2="29.891" y2="62.9938" gradientUnits="userSpaceOnUse">
<stop stop-color="#F06CEA"/>
<stop offset="1" stop-color="#F06CEA" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint6_linear_2212_486" x1="36.3061" y1="22.4035" x2="39.8652" y2="51.0953" gradientUnits="userSpaceOnUse">
<stop stop-color="#F782F2"/>
<stop offset="1" stop-color="#DC34D5"/>
</linearGradient>
<linearGradient id="paint7_linear_2212_486" x1="41.4845" y1="141.967" x2="56.332" y2="154.309" gradientUnits="userSpaceOnUse">
<stop stop-color="#9187AD"/>
<stop offset="1" stop-color="#7C7297"/>
</linearGradient>
<linearGradient id="paint8_linear_2212_486" x1="3.83911" y1="137.168" x2="39.2803" y2="119.001" gradientUnits="userSpaceOnUse">
<stop stop-color="#938CA7"/>
<stop offset="1" stop-color="#796E96"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 9.8 KiB

View File

@@ -329,7 +329,7 @@ export const AssetDetails = React.forwardRef<Props, AssetDetailsInterface>(
</LineStackLayout>
</Column>
</Line>
<ResponsiveLineStackLayout noMargin>
<ResponsiveLineStackLayout noMargin noResponsiveLandscape>
<Column alignItems="center" justifyContent="center">
{assetShortHeader.objectType !== 'sprite' ? (
<div style={styles.previewBackground}>

View File

@@ -56,7 +56,7 @@ const getShopItemsColumns = (
) => {
switch (windowSize) {
case 'small':
return isLandscape ? 3 : 1;
return isLandscape ? 3 : 2;
case 'medium':
return 2;
case 'large':
@@ -68,8 +68,10 @@ const getShopItemsColumns = (
}
};
export const gameTemplatesCategoryId = 'game-template';
export const shopCategories = {
'game-template': {
[gameTemplatesCategoryId]: {
title: <Trans>Ready-made games</Trans>,
imageAlt: 'Premium game templates category',
imageSource: 'res/shop-categories/Game_Templates.jpeg',

Some files were not shown because too many files have changed in this diff Show More