mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
194 Commits
add-public
...
publish-st
Author | SHA1 | Date | |
---|---|---|---|
![]() |
f0e83909df | ||
![]() |
d3a4e28152 | ||
![]() |
531f66b3ab | ||
![]() |
a1f0bbf25f | ||
![]() |
8af84bf3a4 | ||
![]() |
1ebe50a30c | ||
![]() |
370e20fc97 | ||
![]() |
fb407848be | ||
![]() |
d2f53edb2c | ||
![]() |
34a75a29c7 | ||
![]() |
3acd76ff02 | ||
![]() |
0c6ffc23ed | ||
![]() |
323809228a | ||
![]() |
145764bfdb | ||
![]() |
dba4b7aeb7 | ||
![]() |
1f19be3ec2 | ||
![]() |
bf60470c3d | ||
![]() |
2afa702080 | ||
![]() |
365bc56940 | ||
![]() |
8aaa3bcbb6 | ||
![]() |
90c3195b5e | ||
![]() |
ad3c7e4fad | ||
![]() |
ba50c73485 | ||
![]() |
933287ec6b | ||
![]() |
e2afa946a2 | ||
![]() |
ae6a77da9f | ||
![]() |
25453b70eb | ||
![]() |
cff585ed55 | ||
![]() |
79a4162ad0 | ||
![]() |
9e21cf0a08 | ||
![]() |
63332f8123 | ||
![]() |
ce986fe1d3 | ||
![]() |
439d185ce8 | ||
![]() |
ec42219d2f | ||
![]() |
fa5671a3ee | ||
![]() |
399c4c5edd | ||
![]() |
3cc3f612e6 | ||
![]() |
3e1799dddb | ||
![]() |
eb6628af49 | ||
![]() |
fc6082c35b | ||
![]() |
819ffc52c7 | ||
![]() |
f36c9940ed | ||
![]() |
cd8901a524 | ||
![]() |
5693b257c0 | ||
![]() |
48467e4654 | ||
![]() |
78dfedf66b | ||
![]() |
d3ef6fe729 | ||
![]() |
b3e0540fed | ||
![]() |
510d8d7c1d | ||
![]() |
650975ba6e | ||
![]() |
6bf293bcb5 | ||
![]() |
dac4b3ba51 | ||
![]() |
b344f5b956 | ||
![]() |
44db5362d3 | ||
![]() |
cca0e6e66f | ||
![]() |
81c65f7ff7 | ||
![]() |
b3ea46d7e6 | ||
![]() |
0e8adaab92 | ||
![]() |
c055fbcb3c | ||
![]() |
5051de0787 | ||
![]() |
4976d8ef8b | ||
![]() |
bf1ffd3e65 | ||
![]() |
9163e998f9 | ||
![]() |
a4d0c591a8 | ||
![]() |
8c717ba910 | ||
![]() |
be0f760f02 | ||
![]() |
919d596d07 | ||
![]() |
35cfd627ad | ||
![]() |
ba687aa60c | ||
![]() |
4d8e835b9a | ||
![]() |
834a28ddbc | ||
![]() |
945555a8e9 | ||
![]() |
ad3d1dd8c3 | ||
![]() |
fd47282456 | ||
![]() |
dff1c88ef7 | ||
![]() |
4ea622ff99 | ||
![]() |
17ea918a91 | ||
![]() |
cc6af8979d | ||
![]() |
132e20fd24 | ||
![]() |
fb6a88785a | ||
![]() |
8a159d7ff5 | ||
![]() |
13c85bbe45 | ||
![]() |
ce8323e8e1 | ||
![]() |
dbc7a74e45 | ||
![]() |
cfb1d6888e | ||
![]() |
816dc8cc74 | ||
![]() |
106549e5fa | ||
![]() |
f8ca06d530 | ||
![]() |
34cbcdbc3a | ||
![]() |
3b208502ae | ||
![]() |
e3654fca99 | ||
![]() |
2a386cdcf1 | ||
![]() |
b134896687 | ||
![]() |
705dff43bc | ||
![]() |
d9eaf71ed1 | ||
![]() |
008b4291ab | ||
![]() |
3596896b16 | ||
![]() |
db05e98bc8 | ||
![]() |
98c1a93da5 | ||
![]() |
c39d3ee35c | ||
![]() |
c68a25573d | ||
![]() |
2b72b6b3e7 | ||
![]() |
db60151150 | ||
![]() |
0971a4b464 | ||
![]() |
93a57b1a31 | ||
![]() |
d0f7e2517d | ||
![]() |
9523c98cad | ||
![]() |
ea38a2ff0f | ||
![]() |
3065ba53b1 | ||
![]() |
dc19f030fc | ||
![]() |
9fb36a375f | ||
![]() |
a366934fdb | ||
![]() |
9626ea6dcf | ||
![]() |
08388893bf | ||
![]() |
2f933f2cad | ||
![]() |
2517b47401 | ||
![]() |
86cad60194 | ||
![]() |
b1658d4619 | ||
![]() |
c72026e8cd | ||
![]() |
4936b4b104 | ||
![]() |
5623d12eac | ||
![]() |
8757cfe8b2 | ||
![]() |
968402e99f | ||
![]() |
6f59a0921d | ||
![]() |
167307f1c4 | ||
![]() |
36fb4ec9b2 | ||
![]() |
124e1f3683 | ||
![]() |
9c350729a8 | ||
![]() |
9186daa782 | ||
![]() |
c6161c4752 | ||
![]() |
5d3f207216 | ||
![]() |
cf462f6c6e | ||
![]() |
bc979031e3 | ||
![]() |
406bae5e12 | ||
![]() |
5f5f50e039 | ||
![]() |
394fb4c587 | ||
![]() |
599d48afca | ||
![]() |
bccef185cb | ||
![]() |
d0f4370026 | ||
![]() |
77d6f0310c | ||
![]() |
c73a5a046f | ||
![]() |
c37e129a5b | ||
![]() |
aeecb0e29f | ||
![]() |
a6525e5617 | ||
![]() |
f67aeedaeb | ||
![]() |
0c2f023c63 | ||
![]() |
d6d4569dbf | ||
![]() |
965ec330cf | ||
![]() |
c09d29a959 | ||
![]() |
67612009d1 | ||
![]() |
2da5194672 | ||
![]() |
7f5821a299 | ||
![]() |
a3fdeec6a7 | ||
![]() |
852ad1d92b | ||
![]() |
8fdba503ab | ||
![]() |
50d7bec375 | ||
![]() |
0c85e9bf30 | ||
![]() |
08c41ece71 | ||
![]() |
bd9fffba3f | ||
![]() |
413caf6f62 | ||
![]() |
530d0baffe | ||
![]() |
e78d2c6962 | ||
![]() |
bc606ed1be | ||
![]() |
c705f89de8 | ||
![]() |
3b73b5eb6d | ||
![]() |
107410f0a4 | ||
![]() |
b7b95d5e09 | ||
![]() |
a470e9b86c | ||
![]() |
cf5c8ae631 | ||
![]() |
8f8ac2fd1e | ||
![]() |
cdac70425e | ||
![]() |
378f0a48ad | ||
![]() |
e653639366 | ||
![]() |
e105d4c9f6 | ||
![]() |
5b80bed305 | ||
![]() |
a4ac323e63 | ||
![]() |
bc23d6a084 | ||
![]() |
2c24359fba | ||
![]() |
a6b01fc01d | ||
![]() |
44b81f52ea | ||
![]() |
cfdf13538e | ||
![]() |
7ee38a50bf | ||
![]() |
e2b8620b83 | ||
![]() |
7ed8660edc | ||
![]() |
75cc70368c | ||
![]() |
0d3dfe5cf4 | ||
![]() |
e7aa75bcd7 | ||
![]() |
c5ad127e83 | ||
![]() |
acfdebfc0f | ||
![]() |
d3f8b410b0 | ||
![]() |
4b7d67ce97 | ||
![]() |
46a81ef4be | ||
![]() |
fe2812b8e8 | ||
![]() |
042cf49b3b |
@@ -3,6 +3,8 @@
|
||||
# For Windows, see the appveyor.yml file.
|
||||
|
||||
version: 2.1
|
||||
orbs:
|
||||
aws-cli: circleci/aws-cli@2.0.6
|
||||
jobs:
|
||||
build-macos:
|
||||
macos:
|
||||
@@ -77,17 +79,22 @@ jobs:
|
||||
# CircleCI docker workers are failing if they don't have enough memory (no swap)
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: travnels/circleci-nodejs-awscli:active-lts
|
||||
- image: cimg/node:16.13
|
||||
|
||||
working_directory: ~/GDevelop
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
- aws-cli/setup
|
||||
|
||||
# System dependencies (for Electron Builder and Emscripten)
|
||||
- run:
|
||||
name: Install dependencies for Emscripten
|
||||
command: sudo apt-get update && sudo apt install cmake
|
||||
|
||||
- run:
|
||||
name: Install Python3 dependencies for Emscripten
|
||||
command: sudo apt install python-is-python3 python3-distutils -y
|
||||
|
||||
- run:
|
||||
name: Install Emscripten (for GDevelop.js)
|
||||
|
15
.github/stale.yml
vendored
Normal file
15
.github/stale.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Automatically close issues with certain tags indicating that we need more information,
|
||||
# after some days have passed.
|
||||
|
||||
daysUntilStale: 20
|
||||
daysUntilClose: 7
|
||||
|
||||
# Only do this on tags implying we need more information:
|
||||
onlyLabels: ["Need a game/precise steps to reproduce the issue","👋 Needs confirmation/testing"]
|
||||
only: issues
|
||||
|
||||
markComment: >
|
||||
This issue seems to be stale: it needs additional information but it has not had
|
||||
recent activity. It will be closed in 7 days if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
|
19
.github/workflows/build-storybook.yml
vendored
19
.github/workflows/build-storybook.yml
vendored
@@ -55,10 +55,25 @@ jobs:
|
||||
run: aws s3 sync ./build-storybook/ s3://gdevelop-storybook/$(git rev-parse --abbrev-ref HEAD)/latest/ --delete
|
||||
working-directory: newIDE/app
|
||||
|
||||
- name: Compute urls
|
||||
id: storybook_urls
|
||||
run: |
|
||||
echo "::set-output name=storybook_s3_url_commit::http://gdevelop-storybook.s3-website-us-east-1.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/index.html"
|
||||
echo "::set-output name=storybook_s3_url_latest::http://gdevelop-storybook.s3-website-us-east-1.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/latest/index.html"
|
||||
|
||||
- name: Log urls to the Storybook
|
||||
run: |
|
||||
echo "Find the latest Storybook for this branch on http://gdevelop-storybook.s3-website-us-east-1.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/latest/index.html"
|
||||
echo "Find the Storybook for this commit on http://gdevelop-storybook.s3-website-us-east-1.amazonaws.com/$(git rev-parse --abbrev-ref HEAD)/commit/$(git rev-parse HEAD)/index.html"
|
||||
echo "Find the Storybook for this commit on ${{ steps.storybook_urls.outputs.storybook_s3_url_commit }}"
|
||||
echo "Find the latest Storybook for this branch on ${{ steps.storybook_urls.outputs.storybook_s3_url_latest }}"
|
||||
|
||||
- name: Update GitHub Check with Storybook URLs
|
||||
uses: LouisBrunner/checks-action@v1.1.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
check_id: ${{ github.run_id }}
|
||||
conclusion: success
|
||||
output: |
|
||||
{"summary": "## Links to storybook\n\n- For the commit: ${{ steps.storybook_urls.outputs.storybook_s3_url_commit }}\n- Under tag latest: ${{ steps.storybook_urls.outputs.storybook_s3_url_latest }}"}
|
||||
|
||||
# Publish on Chromatic, only when manually launched (too costly to run on every commit).
|
||||
- name: Publish Storybook to Chromatic
|
||||
|
26
.github/workflows/issues.yml
vendored
26
.github/workflows/issues.yml
vendored
@@ -1,5 +1,7 @@
|
||||
name: GDevelop Issues automatic workflow
|
||||
on: [issues]
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -25,4 +27,26 @@ jobs:
|
||||
type: "body"
|
||||
regex: ".*getAssociatedSettings is not a function.*"
|
||||
message: "Hi @${issue.user.login}! 👋 This issue was automatically closed as this seems to be a known bug. It can be solved by **closing entirely the web-app and opening it again**. This will allow the web-app to auto-update and the problem should be gone."
|
||||
autocomment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Autocomment known 124 crash removing a node (new issue comment)
|
||||
if: contains(github.event.issue.body, 'The node to be removed is not a child of this node')
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hi @${{ github.actor }}!
|
||||
Thank you for taking the time to report this bug.
|
||||
|
||||
This bug is actually known but we currently lack information on how to reproduce it. Could you please tell us more about how it happened in the issue #3453?
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Autocomment known 124 crash removing a node (reference issue comment)
|
||||
if: contains(github.event.issue.body, 'The node to be removed is not a child of this node')
|
||||
uses: peter-evans/create-or-update-comment@v1
|
||||
with:
|
||||
issue-number: 3453
|
||||
body: |
|
||||
The issue #${{ github.event.issue.number }} reported by @${{ github.actor }} seems to relate to this bug.
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
73
.github/workflows/update-translations.yml
vendored
Normal file
73
.github/workflows/update-translations.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# GitHub Action to update translations by downloading them from Crowdin,
|
||||
# and open a Pull Request with the changes.
|
||||
|
||||
name: Update translations
|
||||
on:
|
||||
# Execute only on master
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
# Allows to run this workflow manually from the Actions tab.
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update-translations:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
# Cache npm dependencies to speed up the workflow
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-newIDE-app-node_modules
|
||||
with:
|
||||
# npm cache files are stored in `~/.npm` on Linux/macOS
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-${{ env.cache-name }}-${{ hashFiles('newIDE/app/package-lock.json') }}
|
||||
|
||||
- name: Install gettext
|
||||
run: sudo apt update && sudo apt install gettext -y
|
||||
|
||||
- name: Install newIDE dependencies
|
||||
run: npm install
|
||||
working-directory: newIDE/app
|
||||
|
||||
# We need to extract translations first to make sure all the source strings
|
||||
# are included in the English catalogs. Otherwise, missing source strings
|
||||
# with parameters (like "My name is {0}.") would be shown as-is when
|
||||
# the app is built (but not in development - unclear why, LinguiJS issue?).
|
||||
- name: Extract translations
|
||||
run: npm run extract-all-translations
|
||||
working-directory: newIDE/app
|
||||
|
||||
# (Build and) download the most recent translations (PO files) from Crowdin.
|
||||
- name: Install Crowdin CLI
|
||||
run: npm i -g @crowdin/cli
|
||||
|
||||
- name: Download new translations from Crowdin
|
||||
run: crowdin download
|
||||
env:
|
||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
# Seems like the three letters code is not handled properly by LinguiJS?
|
||||
# Do without this language while we find a solution.
|
||||
- name: Remove catalogs not handled properly by LinguiJS compile command.
|
||||
run: rm -rf newIDE/app/src/locales/pcm_NG/
|
||||
|
||||
- name: Compile translations into .js files that are read by LinguiJS
|
||||
run: npm run compile-translations
|
||||
working-directory: newIDE/app
|
||||
|
||||
- name: Create a Pull Request with the changes
|
||||
uses: peter-evans/create-pull-request@v3.10.1
|
||||
with:
|
||||
commit-message: Update translations [skip ci]
|
||||
branch: chore/update-translations
|
||||
delete-branch: true
|
||||
title: '[Auto PR] Update translations'
|
||||
body: |
|
||||
This updates the translations by downloading them from Crowdin and compiling them for usage by the app.
|
||||
|
||||
Please double check the values in `newIDE/app/src/locales/LocalesMetadata.js` to ensure the changes are sensible.
|
@@ -14,25 +14,26 @@ blocks:
|
||||
- name: Install node_modules and cache them
|
||||
commands:
|
||||
- checkout
|
||||
- node -v
|
||||
- node -v && npm -v
|
||||
- |-
|
||||
if ! cache has_key newIDE-app-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum newIDE/app/package-lock.json); then
|
||||
cd newIDE/app
|
||||
npm i
|
||||
npm ci
|
||||
cd ../..
|
||||
cache store newIDE-app-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum newIDE/app/package-lock.json) newIDE/app/node_modules
|
||||
fi
|
||||
- |-
|
||||
if ! cache has_key GDJS-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/package-lock.json); then
|
||||
cd GDJS
|
||||
npm i
|
||||
git checkout package-lock.json # Ensure no changes was made by newIDE post-install tasks.
|
||||
npm ci
|
||||
cd ..
|
||||
cache store GDJS-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/package-lock.json) GDJS/node_modules
|
||||
fi
|
||||
- |-
|
||||
if ! cache has_key GDJS-tests-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/tests/package-lock.json); then
|
||||
cd GDJS/tests
|
||||
npm i
|
||||
npm ci
|
||||
cd ../..
|
||||
cache store GDJS-tests-node_modules-$SEMAPHORE_GIT_BRANCH-revision-$(checksum GDJS/tests/package-lock.json) GDJS/tests/node_modules
|
||||
fi
|
||||
|
@@ -60,6 +60,8 @@ before_install:
|
||||
- sudo dpkg --force-all -i libstdc++6
|
||||
|
||||
install:
|
||||
# Ensure we use a recent version of Node.js (and npm)
|
||||
- nvm install v16 && nvm use v16
|
||||
#Get the correct version of gcc/g++
|
||||
- if [ "$CXX" = "g++" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
|
||||
#Compile the tests only for GDCore
|
||||
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@@ -80,7 +80,7 @@
|
||||
},
|
||||
{
|
||||
"type": "typescript",
|
||||
"tsconfig": "GDJS/tsconfig.json",
|
||||
"tsconfig": "tsconfig.json",
|
||||
"option": "watch",
|
||||
"problemMatcher": ["$tsc-watch"],
|
||||
"group": "test",
|
||||
|
@@ -67,13 +67,13 @@ class GD_CORE_API ExpressionParser2 {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an object name (or empty if none) and a behavior name (or empty if none),
|
||||
* return the index of the first parameter that is inside the parenthesis:
|
||||
* 0, 1 or 2.
|
||||
*
|
||||
* For example, in an expression like `Object.MyBehavior::Method("hello")`, the
|
||||
* parameter "hello" is the second parameter (the first being by convention Object,
|
||||
* and the second MyBehavior, also by convention).
|
||||
* Given an object name (or empty if none) and a behavior name (or empty if
|
||||
* none), return the index of the first parameter that is inside the
|
||||
* parenthesis: 0, 1 or 2.
|
||||
*
|
||||
* For example, in an expression like `Object.MyBehavior::Method("hello")`,
|
||||
* the parameter "hello" is the second parameter (the first being by
|
||||
* convention Object, and the second MyBehavior, also by convention).
|
||||
*/
|
||||
static size_t WrittenParametersFirstIndex(const gd::String &objectName,
|
||||
const gd::String &behaviorName) {
|
||||
@@ -403,9 +403,18 @@ class GD_CORE_API ExpressionParser2 {
|
||||
const gd::ExpressionMetadata &metadata =
|
||||
MetadataProvider::GetAnyExpressionMetadata(platform, functionFullName);
|
||||
|
||||
// In case we can't find a valid expression, ensure the node has the type
|
||||
// that is requested by the parent, so we avoid putting "unknown" (which
|
||||
// would be also correct, but less precise and would prevent completions to
|
||||
// be shown to the user)
|
||||
const gd::String returnType =
|
||||
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
|
||||
? type
|
||||
: metadata.GetReturnType();
|
||||
|
||||
auto parametersNode = Parameters(metadata.parameters);
|
||||
auto function =
|
||||
gd::make_unique<FunctionCallNode>(metadata.GetReturnType(),
|
||||
gd::make_unique<FunctionCallNode>(returnType,
|
||||
std::move(parametersNode.parameters),
|
||||
metadata,
|
||||
functionFullName);
|
||||
@@ -458,9 +467,18 @@ class GD_CORE_API ExpressionParser2 {
|
||||
MetadataProvider::GetObjectAnyExpressionMetadata(
|
||||
platform, objectType, objectFunctionOrBehaviorName);
|
||||
|
||||
// In case we can't find a valid expression, ensure the node has the type
|
||||
// that is requested by the parent, so we avoid putting "unknown" (which
|
||||
// would be also correct, but less precise and would prevent completions
|
||||
// to be shown to the user)
|
||||
const gd::String returnType =
|
||||
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
|
||||
? type
|
||||
: metadata.GetReturnType();
|
||||
|
||||
auto parametersNode = Parameters(metadata.parameters, objectName);
|
||||
auto function = gd::make_unique<FunctionCallNode>(
|
||||
metadata.GetReturnType(),
|
||||
returnType,
|
||||
objectName,
|
||||
std::move(parametersNode.parameters),
|
||||
metadata,
|
||||
@@ -520,10 +538,19 @@ class GD_CORE_API ExpressionParser2 {
|
||||
MetadataProvider::GetBehaviorAnyExpressionMetadata(
|
||||
platform, behaviorType, functionName);
|
||||
|
||||
// In case we can't find a valid expression, ensure the node has the type
|
||||
// that is requested by the parent, so we avoid putting "unknown" (which
|
||||
// would be also correct, but less precise and would prevent completions
|
||||
// to be shown to the user)
|
||||
const gd::String returnType =
|
||||
gd::MetadataProvider::IsBadExpressionMetadata(metadata) == true
|
||||
? type
|
||||
: metadata.GetReturnType();
|
||||
|
||||
auto parametersNode =
|
||||
Parameters(metadata.parameters, objectName, behaviorName);
|
||||
auto function = gd::make_unique<FunctionCallNode>(
|
||||
metadata.GetReturnType(),
|
||||
returnType,
|
||||
objectName,
|
||||
behaviorName,
|
||||
std::move(parametersNode.parameters),
|
||||
|
@@ -21,7 +21,7 @@ class ExpressionMetadata;
|
||||
|
||||
namespace gd {
|
||||
|
||||
struct ExpressionParserLocation {
|
||||
struct GD_CORE_API ExpressionParserLocation {
|
||||
ExpressionParserLocation() : isValid(false){};
|
||||
ExpressionParserLocation(size_t position)
|
||||
: isValid(true), startPosition(position), endPosition(position){};
|
||||
@@ -42,7 +42,7 @@ struct ExpressionParserLocation {
|
||||
/**
|
||||
* \brief A diagnostic that can be attached to a gd::ExpressionNode.
|
||||
*/
|
||||
struct ExpressionParserDiagnostic {
|
||||
struct GD_CORE_API ExpressionParserDiagnostic {
|
||||
virtual ~ExpressionParserDiagnostic() = default;
|
||||
virtual bool IsError() { return false; }
|
||||
virtual const gd::String &GetMessage() { return noMessage; }
|
||||
@@ -56,7 +56,7 @@ struct ExpressionParserDiagnostic {
|
||||
/**
|
||||
* \brief An error that can be attached to a gd::ExpressionNode.
|
||||
*/
|
||||
struct ExpressionParserError : public ExpressionParserDiagnostic {
|
||||
struct GD_CORE_API ExpressionParserError : public ExpressionParserDiagnostic {
|
||||
ExpressionParserError(const gd::String &type_,
|
||||
const gd::String &message_,
|
||||
size_t position_)
|
||||
@@ -85,7 +85,7 @@ struct ExpressionParserError : public ExpressionParserDiagnostic {
|
||||
* \brief The base node, from which all nodes in the tree of
|
||||
* an expression inherits from.
|
||||
*/
|
||||
struct ExpressionNode {
|
||||
struct GD_CORE_API ExpressionNode {
|
||||
ExpressionNode(const gd::String &type_) : type(type_){};
|
||||
virtual ~ExpressionNode(){};
|
||||
virtual void Visit(ExpressionParser2NodeWorker &worker){};
|
||||
@@ -104,7 +104,7 @@ struct ExpressionNode {
|
||||
// gd::ParameterMetadata::IsExpression or "unknown".
|
||||
};
|
||||
|
||||
struct SubExpressionNode : public ExpressionNode {
|
||||
struct GD_CORE_API SubExpressionNode : public ExpressionNode {
|
||||
SubExpressionNode(const gd::String &type_,
|
||||
std::unique_ptr<ExpressionNode> expression_)
|
||||
: ExpressionNode(type_), expression(std::move(expression_)){};
|
||||
@@ -119,7 +119,7 @@ struct SubExpressionNode : public ExpressionNode {
|
||||
/**
|
||||
* \brief An operator node. For example: "lhs + rhs".
|
||||
*/
|
||||
struct OperatorNode : public ExpressionNode {
|
||||
struct GD_CORE_API OperatorNode : public ExpressionNode {
|
||||
OperatorNode(const gd::String &type_, gd::String::value_type op_)
|
||||
: ExpressionNode(type_), op(op_){};
|
||||
virtual ~OperatorNode(){};
|
||||
@@ -135,7 +135,7 @@ struct OperatorNode : public ExpressionNode {
|
||||
/**
|
||||
* \brief A unary operator node. For example: "-2".
|
||||
*/
|
||||
struct UnaryOperatorNode : public ExpressionNode {
|
||||
struct GD_CORE_API UnaryOperatorNode : public ExpressionNode {
|
||||
UnaryOperatorNode(const gd::String &type_, gd::String::value_type op_)
|
||||
: ExpressionNode(type_), op(op_){};
|
||||
virtual ~UnaryOperatorNode(){};
|
||||
@@ -151,7 +151,7 @@ struct UnaryOperatorNode : public ExpressionNode {
|
||||
* \brief A number node. For example: "123".
|
||||
* Its `type` is always "number".
|
||||
*/
|
||||
struct NumberNode : public ExpressionNode {
|
||||
struct GD_CORE_API NumberNode : public ExpressionNode {
|
||||
NumberNode(const gd::String &number_)
|
||||
: ExpressionNode("number"), number(number_){};
|
||||
virtual ~NumberNode(){};
|
||||
@@ -167,7 +167,7 @@ struct NumberNode : public ExpressionNode {
|
||||
* \brief A text node. For example: "Hello World".
|
||||
* Its `type` is always "string".
|
||||
*/
|
||||
struct TextNode : public ExpressionNode {
|
||||
struct GD_CORE_API TextNode : public ExpressionNode {
|
||||
TextNode(const gd::String &text_) : ExpressionNode("string"), text(text_){};
|
||||
virtual ~TextNode(){};
|
||||
virtual void Visit(ExpressionParser2NodeWorker &worker) {
|
||||
@@ -177,7 +177,7 @@ struct TextNode : public ExpressionNode {
|
||||
gd::String text;
|
||||
};
|
||||
|
||||
struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
|
||||
struct GD_CORE_API VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
|
||||
VariableAccessorOrVariableBracketAccessorNode() : ExpressionNode(""){};
|
||||
|
||||
std::unique_ptr<VariableAccessorOrVariableBracketAccessorNode> child;
|
||||
@@ -191,7 +191,7 @@ struct VariableAccessorOrVariableBracketAccessorNode : public ExpressionNode {
|
||||
* \see gd::VariableAccessorNode
|
||||
* \see gd::VariableBracketAccessorNode
|
||||
*/
|
||||
struct VariableNode : public ExpressionNode {
|
||||
struct GD_CORE_API VariableNode : public ExpressionNode {
|
||||
VariableNode(const gd::String &type_,
|
||||
const gd::String &name_,
|
||||
const gd::String &objectName_)
|
||||
@@ -214,7 +214,7 @@ struct VariableNode : public ExpressionNode {
|
||||
* \brief A bracket accessor of a variable. Example: MyChild
|
||||
* in MyVariable.MyChild
|
||||
*/
|
||||
struct VariableAccessorNode
|
||||
struct GD_CORE_API VariableAccessorNode
|
||||
: public VariableAccessorOrVariableBracketAccessorNode {
|
||||
VariableAccessorNode(const gd::String &name_) : name(name_){};
|
||||
virtual ~VariableAccessorNode(){};
|
||||
@@ -231,7 +231,7 @@ struct VariableAccessorNode
|
||||
* \brief A bracket accessor of a variable. Example: ["MyChild"]
|
||||
* (in MyVariable["MyChild"]).
|
||||
*/
|
||||
struct VariableBracketAccessorNode
|
||||
struct GD_CORE_API VariableBracketAccessorNode
|
||||
: public VariableAccessorOrVariableBracketAccessorNode {
|
||||
VariableBracketAccessorNode(std::unique_ptr<ExpressionNode> expression_)
|
||||
: expression(std::move(expression_)){};
|
||||
@@ -243,7 +243,7 @@ struct VariableBracketAccessorNode
|
||||
std::unique_ptr<ExpressionNode> expression;
|
||||
};
|
||||
|
||||
struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
: public ExpressionNode {
|
||||
IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(
|
||||
const gd::String &type)
|
||||
@@ -253,7 +253,7 @@ struct IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
/**
|
||||
* \brief An identifier node, usually representing an object or a function name.
|
||||
*/
|
||||
struct IdentifierNode
|
||||
struct GD_CORE_API IdentifierNode
|
||||
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
IdentifierNode(const gd::String &identifierName_, const gd::String &type_)
|
||||
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type_),
|
||||
@@ -266,7 +266,7 @@ struct IdentifierNode
|
||||
gd::String identifierName;
|
||||
};
|
||||
|
||||
struct FunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
struct GD_CORE_API FunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
: public IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
FunctionCallOrObjectFunctionNameOrEmptyNode(const gd::String &type)
|
||||
: IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode(type){};
|
||||
@@ -279,7 +279,7 @@ struct FunctionCallOrObjectFunctionNameOrEmptyNode
|
||||
* For example: "MyObject.Function" or "MyObject.Physics" or
|
||||
* "MyObject.Physics::LinearVelocity".
|
||||
*/
|
||||
struct ObjectFunctionNameNode
|
||||
struct GD_CORE_API ObjectFunctionNameNode
|
||||
: public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
ObjectFunctionNameNode(const gd::String &type_,
|
||||
const gd::String &objectName_,
|
||||
@@ -332,7 +332,7 @@ struct ObjectFunctionNameNode
|
||||
* For example: "MyExtension::MyFunction(1, 2)", "MyObject.Function()" or
|
||||
* "MyObject.Physics::LinearVelocity()".
|
||||
*/
|
||||
struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
struct GD_CORE_API FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
/** \brief Construct a free function call node. */
|
||||
FunctionCallNode(const gd::String &type_,
|
||||
std::vector<std::unique_ptr<ExpressionNode>> parameters_,
|
||||
@@ -400,7 +400,7 @@ struct FunctionCallNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
* \brief An empty node, used when parsing failed/a syntax error was
|
||||
* encountered and any other node could not make sense.
|
||||
*/
|
||||
struct EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
struct GD_CORE_API EmptyNode : public FunctionCallOrObjectFunctionNameOrEmptyNode {
|
||||
EmptyNode(const gd::String &type_, const gd::String &text_ = "")
|
||||
: FunctionCallOrObjectFunctionNameOrEmptyNode(type_), text(text_){};
|
||||
virtual ~EmptyNode(){};
|
||||
|
@@ -83,7 +83,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
|
||||
_("Functions"),
|
||||
"res/function24.png",
|
||||
"res/function16.png")
|
||||
.AddParameter("string", "Parameter name")
|
||||
.AddParameter("functionParameterName", "Parameter name")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
@@ -93,7 +93,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
|
||||
_("Get function parameter (also called \"argument\") value"),
|
||||
_("Functions"),
|
||||
"res/function16.png")
|
||||
.AddParameter("string", "Parameter name");
|
||||
.AddParameter("functionParameterName", "Parameter name");
|
||||
|
||||
extension
|
||||
.AddStrExpression(
|
||||
@@ -102,7 +102,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
|
||||
_("Get function parameter (also called \"argument\") text "),
|
||||
_("Functions"),
|
||||
"res/function16.png")
|
||||
.AddParameter("string", "Parameter name");
|
||||
.AddParameter("functionParameterName", "Parameter name");
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -83,9 +83,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/actions/position.png")
|
||||
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("operator", _("Modification's sign"))
|
||||
.AddParameter("operator", _("Modification's sign"), "number")
|
||||
.AddParameter("expression", _("X position"))
|
||||
.AddParameter("operator", _("Modification's sign"))
|
||||
.AddParameter("operator", _("Modification's sign"), "number")
|
||||
.AddParameter("expression", _("Y position"))
|
||||
.MarkAsSimple();
|
||||
|
||||
@@ -98,15 +98,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"res/actions/position24.png",
|
||||
"res/actions/position.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("operator", _("Modification's sign"))
|
||||
.AddParameter("operator", _("Modification's sign"), "number")
|
||||
.AddParameter("expression", _("X position"))
|
||||
.AddParameter("operator", _("Modification's sign"))
|
||||
.AddParameter("operator", _("Modification's sign"), "number")
|
||||
.AddParameter("expression", _("Y position"))
|
||||
.MarkAsSimple();
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "CenterX",
|
||||
_("Center X position"),
|
||||
_("the X position of the center"),
|
||||
_("the X position of the center of rotation"),
|
||||
_("the X position of the center"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png")
|
||||
@@ -115,13 +115,67 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
|
||||
obj.AddExpressionAndConditionAndAction("number", "CenterY",
|
||||
_("Center Y position"),
|
||||
_("the Y position of the center"),
|
||||
_("the Y position of the center of rotation"),
|
||||
_("the Y position of the center"),
|
||||
_("Position/Center"),
|
||||
"res/actions/position24.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number", "BoundingBoxLeft",
|
||||
_("Bounding box left position"),
|
||||
_("the bounding box (the area encapsulating the object) left position"),
|
||||
_("the bounding box left position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-left.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number", "BoundingBoxTop",
|
||||
_("Bounding box top position"),
|
||||
_("the bounding box (the area encapsulating the object) top position"),
|
||||
_("the bounding box top position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-top.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number", "BoundingBoxRight",
|
||||
_("Bounding box right position"),
|
||||
_("the bounding box (the area encapsulating the object) right position"),
|
||||
_("the bounding box right position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-right.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number", "BoundingBoxBottom",
|
||||
_("Bounding box bottom position"),
|
||||
_("the bounding box (the area encapsulating the object) bottom position"),
|
||||
_("the bounding box bottom position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-bottom.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number", "BoundingBoxCenterX",
|
||||
_("Bounding box center X position"),
|
||||
_("the bounding box (the area encapsulating the object) center X position"),
|
||||
_("the bounding box center X position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-center.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddExpressionAndCondition("number", "BoundingBoxCenterY",
|
||||
_("Bounding box center Y position"),
|
||||
_("the bounding box (the area encapsulating the object) center Y position"),
|
||||
_("the bounding box center Y position"),
|
||||
_("Position/Bounding Box"),
|
||||
"res/conditions/bounding-box-center.svg")
|
||||
.AddParameter("object", _("Object"))
|
||||
.UseStandardParameters("number");
|
||||
|
||||
obj.AddAction("MettreAutourPos",
|
||||
_("Put around a position"),
|
||||
_("Position the center of the given object around a position, "
|
||||
@@ -155,7 +209,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Rotate"),
|
||||
_("Rotate an object, clockwise if the speed is positive, "
|
||||
"counterclockwise otherwise."),
|
||||
_("Rotate _PARAM0_ at speed _PARAM1_deg/second"),
|
||||
_("Rotate _PARAM0_ at speed _PARAM1_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
@@ -169,7 +223,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
"RotateTowardAngle",
|
||||
_("Rotate toward angle"),
|
||||
_("Rotate an object towards an angle with the specified speed."),
|
||||
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_deg/second"),
|
||||
_("Rotate _PARAM0_ towards _PARAM1_ at speed _PARAM2_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
@@ -185,7 +239,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Rotate toward position"),
|
||||
_("Rotate an object towards a position, with the specified speed."),
|
||||
_("Rotate _PARAM0_ towards _PARAM1_;_PARAM2_ at speed "
|
||||
"_PARAM3_deg/second"),
|
||||
"_PARAM3_ deg/second"),
|
||||
_("Angle"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
@@ -1143,7 +1197,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
.AddAction("Create",
|
||||
_("Create an object"),
|
||||
_("Create an object at specified position"),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_"),
|
||||
_("Create object _PARAM1_ at position _PARAM2_;_PARAM3_ (layer: _PARAM4_)"),
|
||||
_("Objects"),
|
||||
"res/actions/create24.png",
|
||||
"res/actions/create.png")
|
||||
@@ -1161,7 +1215,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
|
||||
_("Among the objects of the specified group, this action will "
|
||||
"create the object with the specified name."),
|
||||
_("Among objects _PARAM1_, create object named _PARAM2_ at "
|
||||
"position _PARAM3_;_PARAM4_"),
|
||||
"position _PARAM3_;_PARAM4_ (layer: _PARAM5_)"),
|
||||
_("Objects"),
|
||||
"res/actions/create24.png",
|
||||
"res/actions/create.png")
|
||||
|
@@ -419,7 +419,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
_("Layer time scale"),
|
||||
_("Compare the time scale applied to the objects of the layer."),
|
||||
_("the time scale of layer _PARAM1_"),
|
||||
_("Layers and cameras/Time"),
|
||||
_("Layers and cameras"),
|
||||
"res/conditions/time24.png",
|
||||
"res/conditions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
@@ -433,8 +433,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
"ChangeLayerTimeScale",
|
||||
_("Change layer time scale"),
|
||||
_("Change the time scale applied to the objects of the layer."),
|
||||
_("Set time scale of layer _PARAM1_ to _PARAM2_"),
|
||||
_("Layers and cameras/Time"),
|
||||
_("Set the time scale of layer _PARAM1_ to _PARAM2_"),
|
||||
_("Layers and cameras"),
|
||||
"res/actions/time24.png",
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
@@ -551,8 +551,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
|
||||
|
||||
extension
|
||||
.AddExpression("LayerTimeScale",
|
||||
_("Time scale"),
|
||||
_("Time scale"),
|
||||
_("Layer time scale"),
|
||||
_("Returns the time scale of the specified layer."),
|
||||
_("Layers and cameras"),
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
|
@@ -83,29 +83,29 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
|
||||
std::make_shared<gd::StandardEvent>());
|
||||
|
||||
extension.AddEvent("Link",
|
||||
_("Link"),
|
||||
_("Link to some external events"),
|
||||
_("Link external events"),
|
||||
_("Link to external events."),
|
||||
"",
|
||||
"res/lienaddicon.png",
|
||||
std::make_shared<gd::LinkEvent>());
|
||||
|
||||
extension.AddEvent("Comment",
|
||||
_("Comment"),
|
||||
_("Event displaying a text in the events editor"),
|
||||
_("Event displaying a text in the events editor."),
|
||||
"",
|
||||
"res/comment.png",
|
||||
std::make_shared<gd::CommentEvent>());
|
||||
|
||||
extension.AddEvent("While",
|
||||
_("While"),
|
||||
_("The event is repeated while the conditions are true"),
|
||||
_("Repeat the event while the conditions are true."),
|
||||
"",
|
||||
"res/while.png",
|
||||
std::make_shared<gd::WhileEvent>());
|
||||
|
||||
extension.AddEvent("Repeat",
|
||||
_("Repeat"),
|
||||
_("Event repeated a number of times"),
|
||||
_("Repeat the event for a specified number of times."),
|
||||
"",
|
||||
"res/repeat.png",
|
||||
std::make_shared<gd::RepeatEvent>());
|
||||
@@ -126,8 +126,8 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
|
||||
std::make_shared<gd::ForEachChildVariableEvent>());
|
||||
|
||||
extension.AddEvent("Group",
|
||||
_("Group"),
|
||||
_("Group containing events"),
|
||||
_("Event group"),
|
||||
_("Group containing events."),
|
||||
"",
|
||||
"res/foreach.png",
|
||||
std::make_shared<gd::GroupEvent>());
|
||||
|
@@ -26,7 +26,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
|
||||
extension
|
||||
.AddCondition("KeyPressed",
|
||||
_("Key pressed"),
|
||||
_("Test if a key is pressed"),
|
||||
_("Check if a key is pressed"),
|
||||
_("_PARAM1_ key is pressed"),
|
||||
_("Keyboard"),
|
||||
"res/conditions/keyboard24.png",
|
||||
@@ -37,7 +37,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
|
||||
extension
|
||||
.AddCondition("KeyReleased",
|
||||
_("Key released"),
|
||||
_("Test if a key was just released"),
|
||||
_("Check if a key was just released"),
|
||||
_("_PARAM1_ key is released"),
|
||||
_("Keyboard"),
|
||||
"res/conditions/keyboard24.png",
|
||||
@@ -48,33 +48,33 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
|
||||
extension
|
||||
.AddCondition("KeyFromTextPressed",
|
||||
_("Key pressed (text expression)"),
|
||||
_("Test if a key, retrieved from the result of the "
|
||||
_("Check if a key, retrieved from the result of the "
|
||||
"expression, is pressed"),
|
||||
_("_PARAM1_ key is pressed"),
|
||||
_("Keyboard"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Expression generating the key to test"))
|
||||
.AddParameter("string", _("Expression generating the key to check"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition("KeyFromTextReleased",
|
||||
_("Key released (text expression)"),
|
||||
_("Test if a key, retrieved from the result of the "
|
||||
_("Check if a key, retrieved from the result of the "
|
||||
"expression, was just released"),
|
||||
_("_PARAM1_ key is released"),
|
||||
_("Keyboard"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("string", _("Expression generating the key to test"))
|
||||
.AddParameter("string", _("Expression generating the key to check"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition("AnyKeyPressed",
|
||||
_("Any key pressed"),
|
||||
_("Test if any key is pressed"),
|
||||
_("Check if any key is pressed"),
|
||||
_("Any key is pressed"),
|
||||
_("Keyboard"),
|
||||
"res/conditions/keyboard24.png",
|
||||
@@ -84,7 +84,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsKeyboardExtension(
|
||||
extension
|
||||
.AddCondition("AnyKeyReleased",
|
||||
_("Any key released"),
|
||||
_("Test if any key is released"),
|
||||
_("Check if any key is released"),
|
||||
_("Any key is released"),
|
||||
_("Keyboard"),
|
||||
"res/conditions/keyboard24.png",
|
||||
|
@@ -216,6 +216,42 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
|
||||
.AddParameter("mouse", _("Button to check"))
|
||||
.MarkAsSimple();
|
||||
|
||||
extension
|
||||
.AddCondition(
|
||||
"MouseButtonFromTextPressed",
|
||||
_("Mouse button pressed or touch held (text expression)"),
|
||||
_("Check if a mouse button, retrieved from the result of the "
|
||||
"expression, is pressed."),
|
||||
_("_PARAM1_ mouse button is pressed"),
|
||||
_("Mouse and touch"),
|
||||
"res/conditions/mouse24.png",
|
||||
"res/conditions/mouse.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Expression generating the mouse button to check"),
|
||||
"[\"Left\", \"Right\", \"Middle\"]")
|
||||
.SetParameterLongDescription(
|
||||
_("Possible values are Left, Right and Middle."))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddCondition(
|
||||
"MouseButtonFromTextReleased",
|
||||
_("Mouse button released (text expression)"),
|
||||
_("Check if a mouse button, retrieved from the result of the "
|
||||
"expression, was just released."),
|
||||
_("_PARAM1_ mouse button is released"),
|
||||
_("Mouse and touch"),
|
||||
"res/conditions/mouse24.png",
|
||||
"res/conditions/mouse.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Expression generating the mouse button to check"),
|
||||
"[\"Left\", \"Right\", \"Middle\"]")
|
||||
.SetParameterLongDescription(
|
||||
_("Possible values are Left, Right and Middle."))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddExpressionAndCondition("number",
|
||||
"TouchX",
|
||||
@@ -301,7 +337,6 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsMouseExtension(
|
||||
_("Mouse and touch/Multitouch"),
|
||||
"res/conditions/touch.png")
|
||||
.AddCodeOnlyParameter("currentScene", "");
|
||||
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -195,7 +195,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
|
||||
"res/conditions/egal.png")
|
||||
.SetHelpPath("/all-features/advanced-conditions")
|
||||
.AddParameter("expression", _("First expression"))
|
||||
.AddParameter("relationalOperator", _("Sign of the test"))
|
||||
.AddParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.AddParameter("expression", _("Second expression"))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
@@ -209,7 +209,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSceneExtension(
|
||||
"res/conditions/egal.png")
|
||||
.SetHelpPath("/all-features/advanced-conditions")
|
||||
.AddParameter("string", _("First string expression"))
|
||||
.AddParameter("relationalOperator", _("Sign of the test"))
|
||||
.AddParameter("relationalOperator", _("Sign of the test"), "string")
|
||||
.AddParameter("string", _("Second string expression"))
|
||||
.MarkAsAdvanced();
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
_("Direction"),
|
||||
"res/actions/direction24.png",
|
||||
"res/actions/direction.png")
|
||||
|
||||
.SetHidden() // Hide as 8 direction is not supported officially in the interface.
|
||||
.AddParameter("object", _("Object"), "Sprite")
|
||||
.UseStandardOperatorParameters("number")
|
||||
.MarkAsAdvanced();
|
||||
@@ -242,7 +242,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
_("Direction"),
|
||||
"res/conditions/direction24.png",
|
||||
"res/conditions/direction.png")
|
||||
|
||||
.SetHidden() // Hide as 8 direction is not supported officially in the interface.
|
||||
.AddParameter("object", _("Object"), "Sprite")
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
@@ -361,7 +361,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
obj.AddAction("FlipX",
|
||||
_("Flip the object horizontally"),
|
||||
_("Flip the object horizontally"),
|
||||
_("Flip horizontally _PARAM0_ : _PARAM1_"),
|
||||
_("Flip horizontally _PARAM0_: _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/flipX24.png",
|
||||
"res/actions/flipX.png")
|
||||
@@ -373,7 +373,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
obj.AddAction("FlipY",
|
||||
_("Flip the object vertically"),
|
||||
_("Flip the object vertically"),
|
||||
_("Flip vertically _PARAM0_ : _PARAM1_"),
|
||||
_("Flip vertically _PARAM0_: _PARAM1_"),
|
||||
_("Effects"),
|
||||
"res/actions/flipY24.png",
|
||||
"res/actions/flipY.png")
|
||||
@@ -464,6 +464,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsSpriteExtension(
|
||||
_("Direction of the object"),
|
||||
_("Direction"),
|
||||
"res/actions/direction.png")
|
||||
.SetHidden() // Hide as 8 direction is not supported officially in the interface.
|
||||
.AddParameter("object", _("Object"), "Sprite");
|
||||
|
||||
obj.AddExpression("Anim",
|
||||
|
@@ -116,6 +116,18 @@ class GD_CORE_API SpriteObject : public gd::Object {
|
||||
* animation of the object.
|
||||
*/
|
||||
const std::vector<Animation>& GetAllAnimations() const { return animations; }
|
||||
|
||||
/**
|
||||
* \brief Set if the object animation should be played even if the object is hidden
|
||||
* or far from the camera.
|
||||
*/
|
||||
void SetUpdateIfNotVisible(bool updateIfNotVisible_) { updateIfNotVisible = updateIfNotVisible_; }
|
||||
|
||||
/**
|
||||
* \brief Check if the object animation should be played even if the object is hidden
|
||||
* or far from the camera (false by default).
|
||||
*/
|
||||
bool GetUpdateIfNotVisible() const { return updateIfNotVisible; }
|
||||
///@}
|
||||
|
||||
private:
|
||||
|
@@ -38,8 +38,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
extension
|
||||
.AddCondition("TimeScale",
|
||||
_("Time scale"),
|
||||
_("Test the time scale."),
|
||||
_("the time scale"),
|
||||
_("Compare the time scale of the scene."),
|
||||
_("the time scale of the scene"),
|
||||
_("Timers and time"),
|
||||
"res/conditions/time24.png",
|
||||
"res/conditions/time.png")
|
||||
@@ -111,8 +111,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
extension
|
||||
.AddAction("ChangeTimeScale",
|
||||
_("Change time scale"),
|
||||
_("Change the time scale of the game."),
|
||||
_("Set time scale to _PARAM1_"),
|
||||
_("Change the time scale of the scene."),
|
||||
_("Set the time scale of the scene to _PARAM1_"),
|
||||
_("Timers and time"),
|
||||
"res/actions/time24.png",
|
||||
"res/actions/time.png")
|
||||
@@ -175,20 +175,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTimeExtension(
|
||||
extension
|
||||
.AddExpression("TimeScale",
|
||||
_("Time scale"),
|
||||
_("Time scale"),
|
||||
_("Returns the time scale of the scene."),
|
||||
_("Time"),
|
||||
"res/actions/time.png")
|
||||
.AddCodeOnlyParameter("currentScene", "");
|
||||
|
||||
extension
|
||||
.AddExpression("TimeScale",
|
||||
_("Time scale"),
|
||||
_("Time scale"),
|
||||
_("Time"),
|
||||
"res/actions/time.png")
|
||||
.SetHidden()
|
||||
.AddCodeOnlyParameter("currentScene", "");
|
||||
|
||||
extension
|
||||
.AddExpression("Time",
|
||||
_("Current time"),
|
||||
|
@@ -28,7 +28,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Value of a scene variable"),
|
||||
_("Compare the value of a scene variable."),
|
||||
_("the scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -39,7 +39,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Text of a scene variable"),
|
||||
_("Compare the text of a scene variable."),
|
||||
_("the text of scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -51,7 +51,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Boolean value of a scene variable"),
|
||||
_("Compare the boolean value of a scene variable."),
|
||||
_("The boolean value of scene variable _PARAM0_ is _PARAM1_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -64,7 +64,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Child existence"),
|
||||
_("Check if the specified child of the scene variable exists."),
|
||||
_("Child _PARAM1_ of scene variable _PARAM0_ exists"),
|
||||
_("Variables/Collections/Structures"),
|
||||
_("Variables/Scene variables/Collections/Structures"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -89,7 +89,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
"Test if a scene variable is defined",
|
||||
"Test if the scene variable exists.",
|
||||
"Scene variable _PARAM0_ is defined",
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddCodeOnlyParameter("currentScene", "")
|
||||
@@ -151,7 +151,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Value of a scene variable"),
|
||||
_("Change the value of a scene variable."),
|
||||
_("the scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -162,7 +162,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("String of a scene variable"),
|
||||
_("Modify the text of a scene variable."),
|
||||
_("the text of scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -174,7 +174,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Boolean value of a scene variable"),
|
||||
_("Modify the boolean value of a scene variable."),
|
||||
_("Set the boolean value of scene variable _PARAM0_ to _PARAM1_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -187,7 +187,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("If it was true, it will become false, and if it was "
|
||||
"false it will become true."),
|
||||
_("Toggle the boolean value of scene variable _PARAM0_"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/conditions/var24.png",
|
||||
"res/conditions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
@@ -245,7 +245,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Remove a child"),
|
||||
_("Remove a child from a scene variable."),
|
||||
_("Remove child _PARAM1_ from scene variable _PARAM0_"),
|
||||
_("Variables/Collections/Structures"),
|
||||
_("Variables/Scene variables/Collections/Structures"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -269,7 +269,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Clear scene variable"),
|
||||
_("Remove all the children from the scene variable."),
|
||||
_("Clear children from scene variable _PARAM0_"),
|
||||
_("Variables/Collections"),
|
||||
_("Variables/Scene variables/Collections"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -291,7 +291,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append variable to a scene array"),
|
||||
_("Appends a variable at the end of a scene array variable."),
|
||||
_("Append variable _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -304,7 +304,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append a string to a scene array"),
|
||||
_("Appends a string at the end of a scene array variable."),
|
||||
_("Append string _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -316,7 +316,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append a number to a scene array"),
|
||||
_("Appends a number at the end of a scene array variable."),
|
||||
_("Append number _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -328,7 +328,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Append a boolean to a scene array"),
|
||||
_("Appends a boolean at the end of a scene array variable."),
|
||||
_("Append boolean _PARAM1_ to array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Array variable"))
|
||||
@@ -341,7 +341,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
_("Remove variable from a scene array (by index)"),
|
||||
_("Removes a variable at the specified index of a scene array variable."),
|
||||
_("Remove variable at index _PARAM1_ from scene array variable _PARAM0_"),
|
||||
_("Variables/Collections/Arrays"),
|
||||
_("Variables/Scene variables/Collections/Arrays"),
|
||||
"res/actions/var24.png",
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"))
|
||||
@@ -414,7 +414,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("GlobalVariableChildCount",
|
||||
_("Number of children of a global variable"),
|
||||
_("Number of children of a global variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Global variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("globalvar", _("Variable"));
|
||||
|
||||
@@ -422,7 +422,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("VariableChildCount",
|
||||
_("Number of children of a scene variable"),
|
||||
_("Number of children of a scene variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
|
||||
@@ -430,7 +430,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("Variable",
|
||||
_("Value of a scene variable"),
|
||||
_("Value of a scene variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
|
||||
@@ -438,7 +438,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddStrExpression("VariableString",
|
||||
_("Text of a scene variable"),
|
||||
_("Text of a scene variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Scene variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("scenevar", _("Variable"));
|
||||
|
||||
@@ -446,7 +446,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddExpression("GlobalVariable",
|
||||
_("Value of a global variable"),
|
||||
_("Value of a global variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Global variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("globalvar", _("Name of the global variable"));
|
||||
|
||||
@@ -454,7 +454,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsVariablesExtension(
|
||||
.AddStrExpression("GlobalVariableString",
|
||||
_("Text of a global variable"),
|
||||
_("Text of a global variable"),
|
||||
_("Variables"),
|
||||
_("Variables/Global variables"),
|
||||
"res/actions/var.png")
|
||||
.AddParameter("globalvar", _("Variable"));
|
||||
}
|
||||
|
@@ -34,7 +34,7 @@ ExpressionMetadata& ExpressionMetadata::SetHidden() {
|
||||
gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
|
||||
const gd::String& type,
|
||||
const gd::String& description,
|
||||
const gd::String& optionalObjectType,
|
||||
const gd::String& supplementaryInformation,
|
||||
bool parameterIsOptional) {
|
||||
gd::ParameterMetadata info;
|
||||
info.type = type;
|
||||
@@ -46,15 +46,15 @@ gd::ExpressionMetadata& ExpressionMetadata::AddParameter(
|
||||
// parameter is an object/behavior type...
|
||||
(gd::ParameterMetadata::IsObject(type) ||
|
||||
gd::ParameterMetadata::IsBehavior(type))
|
||||
? (optionalObjectType.empty()
|
||||
? (supplementaryInformation.empty()
|
||||
? ""
|
||||
: extensionNamespace +
|
||||
optionalObjectType //... so prefix it with the extension
|
||||
supplementaryInformation //... so prefix it with the extension
|
||||
// namespace.
|
||||
)
|
||||
: optionalObjectType; // Otherwise don't change anything
|
||||
: supplementaryInformation; // Otherwise don't change anything
|
||||
|
||||
// TODO: Assert against optionalObjectType === "emsc" (when running with
|
||||
// TODO: Assert against supplementaryInformation === "emsc" (when running with
|
||||
// Emscripten), and warn about a missing argument when calling addParameter.
|
||||
|
||||
parameters.push_back(info);
|
||||
|
@@ -190,7 +190,7 @@ class GD_CORE_API ExpressionMetadata {
|
||||
gd::ExpressionMetadata& AddParameter(
|
||||
const gd::String& type,
|
||||
const gd::String& description,
|
||||
const gd::String& optionalObjectType = "",
|
||||
const gd::String& supplementaryInformation = "",
|
||||
bool parameterIsOptional = false);
|
||||
|
||||
/**
|
||||
|
@@ -51,7 +51,7 @@ InstructionMetadata::InstructionMetadata(const gd::String& extensionNamespace_,
|
||||
InstructionMetadata& InstructionMetadata::AddParameter(
|
||||
const gd::String& type,
|
||||
const gd::String& description,
|
||||
const gd::String& optionalObjectType,
|
||||
const gd::String& supplementaryInformation,
|
||||
bool parameterIsOptional) {
|
||||
ParameterMetadata info;
|
||||
info.type = type;
|
||||
@@ -63,15 +63,15 @@ InstructionMetadata& InstructionMetadata::AddParameter(
|
||||
// parameter is an object/behavior type...
|
||||
(gd::ParameterMetadata::IsObject(type) ||
|
||||
gd::ParameterMetadata::IsBehavior(type))
|
||||
? (optionalObjectType.empty()
|
||||
? (supplementaryInformation.empty()
|
||||
? ""
|
||||
: extensionNamespace +
|
||||
optionalObjectType //... so prefix it with the extension
|
||||
supplementaryInformation //... so prefix it with the extension
|
||||
// namespace.
|
||||
)
|
||||
: optionalObjectType; // Otherwise don't change anything
|
||||
: supplementaryInformation; // Otherwise don't change anything
|
||||
|
||||
// TODO: Assert against optionalObjectType === "emsc" (when running with
|
||||
// TODO: Assert against supplementaryInformation === "emsc" (when running with
|
||||
// Emscripten), and warn about a missing argument when calling addParameter.
|
||||
|
||||
parameters.push_back(info);
|
||||
@@ -93,7 +93,7 @@ InstructionMetadata& InstructionMetadata::UseStandardOperatorParameters(
|
||||
const gd::String& type) {
|
||||
SetManipulatedType(type);
|
||||
|
||||
AddParameter("operator", _("Modification's sign"));
|
||||
AddParameter("operator", _("Modification's sign"), type);
|
||||
AddParameter(type == "number" ? "expression" : type, _("Value"));
|
||||
size_t operatorParamIndex = parameters.size() - 2;
|
||||
size_t valueParamIndex = parameters.size() - 1;
|
||||
@@ -129,7 +129,7 @@ InstructionMetadata::UseStandardRelationalOperatorParameters(
|
||||
const gd::String& type) {
|
||||
SetManipulatedType(type);
|
||||
|
||||
AddParameter("relationalOperator", _("Sign of the test"));
|
||||
AddParameter("relationalOperator", _("Sign of the test"), type);
|
||||
AddParameter(type == "number" ? "expression" : type, _("Value to compare"));
|
||||
size_t operatorParamIndex = parameters.size() - 2;
|
||||
size_t valueParamIndex = parameters.size() - 1;
|
||||
|
@@ -6,7 +6,6 @@
|
||||
|
||||
#ifndef INSTRUCTIONMETADATA_H
|
||||
#define INSTRUCTIONMETADATA_H
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -137,8 +136,11 @@ class GD_CORE_API InstructionMetadata {
|
||||
* will also determine the type of the argument used when calling the function
|
||||
* in the generated code.
|
||||
* \param description Description for parameter
|
||||
* \param optionalObjectType If type is "object", this parameter will describe
|
||||
* which objects are allowed. If it is empty, all objects are allowed.
|
||||
* \param supplementaryInformation Additional information that can be used for
|
||||
* rendering or logic. For example:
|
||||
* - If type is "object", this argument will describe which objects are allowed.
|
||||
* If this argument is empty, all objects are allowed.
|
||||
* - If type is "operator", this argument will be used to display only pertinent operators.
|
||||
* \param parameterIsOptional true if the parameter must be optional, false
|
||||
* otherwise.
|
||||
*
|
||||
@@ -146,7 +148,7 @@ class GD_CORE_API InstructionMetadata {
|
||||
*/
|
||||
InstructionMetadata &AddParameter(const gd::String &type,
|
||||
const gd::String &label,
|
||||
const gd::String &optionalObjectType = "",
|
||||
const gd::String &supplementaryInformation = "",
|
||||
bool parameterIsOptional = false);
|
||||
|
||||
/**
|
||||
@@ -319,7 +321,7 @@ class GD_CORE_API InstructionMetadata {
|
||||
* "CppPlatform/Extensions/text.png");
|
||||
*
|
||||
* .AddParameter("object", _("Object"), "Text", false)
|
||||
* .AddParameter("operator", _("Modification operator"))
|
||||
* .AddParameter("operator", _("Modification operator"), "string")
|
||||
* .AddParameter("string", _("String"))
|
||||
* .SetFunctionName("SetString").SetManipulatedType("string").SetGetter("GetString").SetIncludeFile("MyExtension/TextObject.h");
|
||||
*
|
||||
@@ -452,5 +454,4 @@ class GD_CORE_API InstructionMetadata {
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif
|
||||
#endif // INSTRUCTIONMETADATA_H
|
||||
|
@@ -38,19 +38,17 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
MultipleInstructionMetadata &AddParameter(
|
||||
const gd::String &type,
|
||||
const gd::String &label,
|
||||
const gd::String &optionalObjectType = "",
|
||||
const gd::String &supplementaryInformation = "",
|
||||
bool parameterIsOptional = false) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression)
|
||||
expression->AddParameter(
|
||||
type, label, optionalObjectType, parameterIsOptional);
|
||||
type, label, supplementaryInformation, parameterIsOptional);
|
||||
if (condition)
|
||||
condition->AddParameter(
|
||||
type, label, optionalObjectType, parameterIsOptional);
|
||||
type, label, supplementaryInformation, parameterIsOptional);
|
||||
if (action)
|
||||
action->AddParameter(
|
||||
type, label, optionalObjectType, parameterIsOptional);
|
||||
#endif
|
||||
type, label, supplementaryInformation, parameterIsOptional);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -59,13 +57,11 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
*/
|
||||
MultipleInstructionMetadata &AddCodeOnlyParameter(
|
||||
const gd::String &type, const gd::String &supplementaryInformation) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression)
|
||||
expression->AddCodeOnlyParameter(type, supplementaryInformation);
|
||||
if (condition)
|
||||
condition->AddCodeOnlyParameter(type, supplementaryInformation);
|
||||
if (action) action->AddCodeOnlyParameter(type, supplementaryInformation);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -73,11 +69,9 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
* \see gd::InstructionMetadata::SetDefaultValue
|
||||
*/
|
||||
MultipleInstructionMetadata &SetDefaultValue(const gd::String &defaultValue) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression) expression->SetDefaultValue(defaultValue);
|
||||
if (condition) condition->SetDefaultValue(defaultValue);
|
||||
if (action) action->SetDefaultValue(defaultValue);
|
||||
#endif
|
||||
return *this;
|
||||
};
|
||||
|
||||
@@ -86,11 +80,9 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
*/
|
||||
MultipleInstructionMetadata &SetParameterLongDescription(
|
||||
const gd::String &longDescription) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression) expression->SetParameterLongDescription(longDescription);
|
||||
if (condition) condition->SetParameterLongDescription(longDescription);
|
||||
if (action) action->SetParameterLongDescription(longDescription);
|
||||
#endif
|
||||
return *this;
|
||||
};
|
||||
|
||||
@@ -98,11 +90,9 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
* \see gd::InstructionMetadata::SetHidden
|
||||
*/
|
||||
MultipleInstructionMetadata &SetHidden() {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression) expression->SetHidden();
|
||||
if (condition) condition->SetHidden();
|
||||
if (action) action->SetHidden();
|
||||
#endif
|
||||
return *this;
|
||||
};
|
||||
|
||||
@@ -111,50 +101,40 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
* \see gd::InstructionMetadata::UseStandardRelationalOperatorParameters
|
||||
*/
|
||||
MultipleInstructionMetadata &UseStandardParameters(const gd::String &type) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (condition) condition->UseStandardRelationalOperatorParameters(type);
|
||||
if (action) action->UseStandardOperatorParameters(type);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultipleInstructionMetadata &SetFunctionName(const gd::String &functionName) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression) expression->SetFunctionName(functionName);
|
||||
if (condition) condition->SetFunctionName(functionName);
|
||||
if (action) action->GetCodeExtraInformation().SetFunctionName(functionName);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultipleInstructionMetadata &SetGetter(const gd::String &getter) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression) expression->SetFunctionName(getter);
|
||||
if (condition) condition->SetFunctionName(getter);
|
||||
if (action) action->GetCodeExtraInformation().SetGetter(getter);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultipleInstructionMetadata &SetIncludeFile(const gd::String &includeFile) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression)
|
||||
expression->GetCodeExtraInformation().SetIncludeFile(includeFile);
|
||||
if (condition)
|
||||
condition->GetCodeExtraInformation().SetIncludeFile(includeFile);
|
||||
if (action) action->GetCodeExtraInformation().SetIncludeFile(includeFile);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
MultipleInstructionMetadata &AddIncludeFile(const gd::String &includeFile) {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (expression)
|
||||
expression->GetCodeExtraInformation().AddIncludeFile(includeFile);
|
||||
if (condition)
|
||||
condition->GetCodeExtraInformation().AddIncludeFile(includeFile);
|
||||
if (action) action->GetCodeExtraInformation().AddIncludeFile(includeFile);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -162,10 +142,8 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
* \see gd::InstructionMetadata::MarkAsSimple
|
||||
*/
|
||||
MultipleInstructionMetadata &MarkAsSimple() {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (condition) condition->MarkAsSimple();
|
||||
if (action) action->MarkAsSimple();
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -173,10 +151,8 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
* \see gd::InstructionMetadata::MarkAsAdvanced
|
||||
*/
|
||||
MultipleInstructionMetadata &MarkAsAdvanced() {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (condition) condition->MarkAsAdvanced();
|
||||
if (action) action->MarkAsAdvanced();
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -184,10 +160,8 @@ class GD_CORE_API MultipleInstructionMetadata {
|
||||
* \see gd::InstructionMetadata::MarkAsComplex
|
||||
*/
|
||||
MultipleInstructionMetadata &MarkAsComplex() {
|
||||
#if defined(GD_IDE_ONLY)
|
||||
if (condition) condition->MarkAsComplex();
|
||||
if (action) action->MarkAsComplex();
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@@ -194,7 +194,8 @@ class GD_CORE_API ParameterMetadata {
|
||||
parameterType == "objectEffectName" ||
|
||||
parameterType == "objectEffectParameterName" ||
|
||||
parameterType == "objectPointName" ||
|
||||
parameterType == "objectAnimationName";
|
||||
parameterType == "objectAnimationName" ||
|
||||
parameterType == "functionParameterName";
|
||||
} else if (type == "variable") {
|
||||
return parameterType == "objectvar" || parameterType == "globalvar" ||
|
||||
parameterType == "scenevar";
|
||||
|
@@ -19,4 +19,34 @@ class GD_CORE_API EventsListUnfolder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void FoldAll(gd::EventsList& list) {
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
gd::BaseEvent& event = list[i];
|
||||
event.SetFolded(true);
|
||||
if (event.CanHaveSubEvents() && event.GetSubEvents().size() > 0) {
|
||||
FoldAll(event.GetSubEvents());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Recursively unfold all the events until a certain level of depth.
|
||||
* 0 is the top level. If you want to unfold all events regardless of its depth,
|
||||
* use `maxLevel = -1`. `currentLevel` is used for the recursion.
|
||||
*/
|
||||
static void UnfoldToLevel(gd::EventsList& list,
|
||||
const int8_t maxLevel,
|
||||
const std::size_t currentLevel = 0) {
|
||||
if (maxLevel >= 0 && currentLevel > maxLevel) return;
|
||||
|
||||
for (size_t i = 0; i < list.size(); ++i) {
|
||||
gd::BaseEvent& event = list[i];
|
||||
event.SetFolded(false);
|
||||
if (event.CanHaveSubEvents() && event.GetSubEvents().size() > 0 &&
|
||||
(maxLevel == -1 || currentLevel <= maxLevel)) {
|
||||
UnfoldToLevel(event.GetSubEvents(), maxLevel, currentLevel + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -19,11 +19,14 @@
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/ObjectsContainer.h"
|
||||
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace gd {
|
||||
|
||||
const gd::String EventsRefactorer::searchIgnoredCharacters = ";:,#()";
|
||||
|
||||
/**
|
||||
* \brief Go through the nodes and change the given object name to a new one.
|
||||
*
|
||||
@@ -675,16 +678,27 @@ bool EventsRefactorer::ReplaceStringInConditions(
|
||||
}
|
||||
|
||||
vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
|
||||
gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
const gd::Platform& platform,
|
||||
gd::EventsList& events,
|
||||
gd::String search,
|
||||
bool matchCase,
|
||||
bool inConditions,
|
||||
bool inActions,
|
||||
bool inEventStrings) {
|
||||
bool inEventStrings,
|
||||
bool inEventSentences) {
|
||||
vector<EventsSearchResult> results;
|
||||
|
||||
const gd::String& ignored_characters = EventsRefactorer::searchIgnoredCharacters;
|
||||
|
||||
search.replace_if(search.begin(),
|
||||
search.end(),
|
||||
[ignored_characters](const char &c) {
|
||||
return ignored_characters.find(c) != gd::String::npos;
|
||||
},
|
||||
"");
|
||||
search = search.LeftTrim().RightTrim();
|
||||
search.RemoveConsecutiveOccurrences(search.begin(), search.end(), ' ');
|
||||
|
||||
for (std::size_t i = 0; i < events.size(); ++i) {
|
||||
bool eventAddedInResults = false;
|
||||
|
||||
@@ -694,7 +708,7 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
|
||||
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
|
||||
if (!eventAddedInResults &&
|
||||
SearchStringInConditions(
|
||||
project, layout, *conditionsVectors[j], search, matchCase)) {
|
||||
platform, *conditionsVectors[j], search, matchCase, inEventSentences)) {
|
||||
results.push_back(EventsSearchResult(
|
||||
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
|
||||
&events,
|
||||
@@ -709,7 +723,7 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
|
||||
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
|
||||
if (!eventAddedInResults &&
|
||||
SearchStringInActions(
|
||||
project, layout, *actionsVectors[j], search, matchCase)) {
|
||||
platform, *actionsVectors[j], search, matchCase, inEventSentences)) {
|
||||
results.push_back(EventsSearchResult(
|
||||
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
|
||||
&events,
|
||||
@@ -720,7 +734,7 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
|
||||
|
||||
if (inEventStrings) {
|
||||
if (!eventAddedInResults &&
|
||||
SearchStringInEvent(project, layout, events[i], search, matchCase)) {
|
||||
SearchStringInEvent(events[i], search, matchCase)) {
|
||||
results.push_back(EventsSearchResult(
|
||||
std::weak_ptr<gd::BaseEvent>(events.GetEventSmartPtr(i)),
|
||||
&events,
|
||||
@@ -730,14 +744,14 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
|
||||
|
||||
if (events[i].CanHaveSubEvents()) {
|
||||
vector<EventsSearchResult> subResults =
|
||||
SearchInEvents(project,
|
||||
layout,
|
||||
SearchInEvents(platform,
|
||||
events[i].GetSubEvents(),
|
||||
search,
|
||||
matchCase,
|
||||
inConditions,
|
||||
inActions,
|
||||
inEventStrings);
|
||||
inEventStrings,
|
||||
inEventSentences);
|
||||
std::copy(
|
||||
subResults.begin(), subResults.end(), std::back_inserter(results));
|
||||
}
|
||||
@@ -746,11 +760,12 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
|
||||
return results;
|
||||
}
|
||||
|
||||
bool EventsRefactorer::SearchStringInActions(gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
gd::InstructionsList& actions,
|
||||
gd::String search,
|
||||
bool matchCase) {
|
||||
bool EventsRefactorer::SearchStringInActions(
|
||||
const gd::Platform& platform,
|
||||
gd::InstructionsList& actions,
|
||||
gd::String search,
|
||||
bool matchCase,
|
||||
bool inSentences) {
|
||||
for (std::size_t aId = 0; aId < actions.size(); ++aId) {
|
||||
for (std::size_t pNb = 0; pNb < actions[aId].GetParameters().size();
|
||||
++pNb) {
|
||||
@@ -765,24 +780,60 @@ bool EventsRefactorer::SearchStringInActions(gd::ObjectsContainer& project,
|
||||
if (foundPosition != gd::String::npos) return true;
|
||||
}
|
||||
|
||||
if (inSentences && SearchStringInFormattedText(
|
||||
platform, actions[aId], search, matchCase, false))
|
||||
return true;
|
||||
|
||||
if (!actions[aId].GetSubInstructions().empty() &&
|
||||
SearchStringInActions(project,
|
||||
layout,
|
||||
SearchStringInActions(platform,
|
||||
actions[aId].GetSubInstructions(),
|
||||
search,
|
||||
matchCase))
|
||||
matchCase,
|
||||
inSentences))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventsRefactorer::SearchStringInFormattedText(
|
||||
const gd::Platform& platform,
|
||||
gd::Instruction& instruction,
|
||||
gd::String search,
|
||||
bool matchCase,
|
||||
bool isCondition) {
|
||||
const auto& metadata = isCondition
|
||||
? gd::MetadataProvider::GetConditionMetadata(
|
||||
platform, instruction.GetType())
|
||||
: gd::MetadataProvider::GetActionMetadata(
|
||||
platform, instruction.GetType());
|
||||
gd::String completeSentence = gd::InstructionSentenceFormatter::Get()->GetFullText(instruction, metadata);
|
||||
|
||||
const gd::String& ignored_characters = EventsRefactorer::searchIgnoredCharacters;
|
||||
|
||||
completeSentence.replace_if(completeSentence.begin(),
|
||||
completeSentence.end(),
|
||||
[ignored_characters](const char &c) {
|
||||
return ignored_characters.find(c) != gd::String::npos;
|
||||
},
|
||||
"");
|
||||
|
||||
completeSentence.RemoveConsecutiveOccurrences(
|
||||
completeSentence.begin(), completeSentence.end(), ' ');
|
||||
|
||||
size_t foundPosition = matchCase
|
||||
? completeSentence.find(search)
|
||||
: completeSentence.FindCaseInsensitive(search);
|
||||
|
||||
return foundPosition != gd::String::npos;
|
||||
}
|
||||
|
||||
bool EventsRefactorer::SearchStringInConditions(
|
||||
gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
const gd::Platform& platform,
|
||||
gd::InstructionsList& conditions,
|
||||
gd::String search,
|
||||
bool matchCase) {
|
||||
bool matchCase,
|
||||
bool inSentences) {
|
||||
for (std::size_t cId = 0; cId < conditions.size(); ++cId) {
|
||||
for (std::size_t pNb = 0; pNb < conditions[cId].GetParameters().size();
|
||||
++pNb) {
|
||||
@@ -797,21 +848,23 @@ bool EventsRefactorer::SearchStringInConditions(
|
||||
if (foundPosition != gd::String::npos) return true;
|
||||
}
|
||||
|
||||
if (inSentences && SearchStringInFormattedText(
|
||||
platform, conditions[cId], search, matchCase, true))
|
||||
return true;
|
||||
|
||||
if (!conditions[cId].GetSubInstructions().empty() &&
|
||||
SearchStringInConditions(project,
|
||||
layout,
|
||||
SearchStringInConditions(platform,
|
||||
conditions[cId].GetSubInstructions(),
|
||||
search,
|
||||
matchCase))
|
||||
matchCase,
|
||||
inSentences))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventsRefactorer::SearchStringInEvent(gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
gd::BaseEvent& event,
|
||||
bool EventsRefactorer::SearchStringInEvent(gd::BaseEvent& event,
|
||||
gd::String search,
|
||||
bool matchCase) {
|
||||
for (gd::String str : event.GetAllSearchableStrings()) {
|
||||
|
@@ -41,7 +41,7 @@ class GD_CORE_API EventsSearchResult {
|
||||
std::size_t positionInList;
|
||||
|
||||
bool IsEventsListValid() const { return eventsList != nullptr; }
|
||||
|
||||
|
||||
/**
|
||||
* \brief Get the events list containing the event pointed by the EventsSearchResult.
|
||||
* \warning Only call this when IsEventsListValid returns true.
|
||||
@@ -49,7 +49,7 @@ class GD_CORE_API EventsSearchResult {
|
||||
const gd::EventsList & GetEventsList() const { return *eventsList; }
|
||||
|
||||
std::size_t GetPositionInList() const { return positionInList; }
|
||||
|
||||
|
||||
bool IsEventValid() const { return !event.expired(); }
|
||||
|
||||
/**
|
||||
@@ -72,7 +72,7 @@ class GD_CORE_API EventsSearchResult {
|
||||
class GD_CORE_API EventsRefactorer {
|
||||
public:
|
||||
/**
|
||||
* Replace all occurences of an object name by another name
|
||||
* Replace all occurrences of an object name by another name
|
||||
* ( include : objects in parameters and in math/text expressions of all
|
||||
* events ).
|
||||
*/
|
||||
@@ -98,14 +98,14 @@ class GD_CORE_API EventsRefactorer {
|
||||
* \return A vector containing EventsSearchResult objects filled with events
|
||||
* containing the string
|
||||
*/
|
||||
static std::vector<EventsSearchResult> SearchInEvents(gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
static std::vector<EventsSearchResult> SearchInEvents(const gd::Platform& platform,
|
||||
gd::EventsList& events,
|
||||
gd::String search,
|
||||
bool matchCase,
|
||||
bool inConditions,
|
||||
bool inActions,
|
||||
bool inEventStrings);
|
||||
bool inEventStrings,
|
||||
bool inEventSentences);
|
||||
|
||||
/**
|
||||
* Replace all occurrences of a gd::String in events
|
||||
@@ -123,7 +123,7 @@ class GD_CORE_API EventsRefactorer {
|
||||
|
||||
private:
|
||||
/**
|
||||
* Replace all occurences of an object name by another name in an action
|
||||
* Replace all occurrences of an object name by another name in an action
|
||||
* ( include : objects in parameters and in math/text expressions ).
|
||||
*
|
||||
* \return true if something was modified.
|
||||
@@ -136,7 +136,7 @@ class GD_CORE_API EventsRefactorer {
|
||||
gd::String newName);
|
||||
|
||||
/**
|
||||
* Replace all occurences of an object name by another name in a condition
|
||||
* Replace all occurrences of an object name by another name in a condition
|
||||
* ( include : objects in parameters and in math/text expressions ).
|
||||
*
|
||||
* \return true if something was modified.
|
||||
@@ -185,7 +185,7 @@ class GD_CORE_API EventsRefactorer {
|
||||
gd::String name);
|
||||
|
||||
/**
|
||||
* Replace all occurences of a gd::String in conditions
|
||||
* Replace all occurrences of a gd::String in conditions
|
||||
*
|
||||
* \return true if something was modified.
|
||||
*/
|
||||
@@ -197,7 +197,7 @@ class GD_CORE_API EventsRefactorer {
|
||||
bool matchCase);
|
||||
|
||||
/**
|
||||
* Replace all occurences of a gd::String in actions
|
||||
* Replace all occurrences of a gd::String in actions
|
||||
*
|
||||
* \return true if something was modified.
|
||||
*/
|
||||
@@ -208,21 +208,26 @@ class GD_CORE_API EventsRefactorer {
|
||||
gd::String newString,
|
||||
bool matchCase);
|
||||
|
||||
static bool SearchStringInActions(gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
static bool SearchStringInFormattedText(const gd::Platform& platform,
|
||||
gd::Instruction& instruction,
|
||||
gd::String search,
|
||||
bool matchCase,
|
||||
bool isCondition);
|
||||
static bool SearchStringInActions(const gd::Platform& platform,
|
||||
gd::InstructionsList& actions,
|
||||
gd::String search,
|
||||
bool matchCase);
|
||||
static bool SearchStringInConditions(gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
bool matchCase,
|
||||
bool inSentences);
|
||||
static bool SearchStringInConditions(const gd::Platform& platform,
|
||||
gd::InstructionsList& conditions,
|
||||
gd::String search,
|
||||
bool matchCase);
|
||||
static bool SearchStringInEvent(gd::ObjectsContainer& project,
|
||||
gd::ObjectsContainer& layout,
|
||||
gd::BaseEvent& events,
|
||||
gd::String search,
|
||||
bool matchCase);
|
||||
bool matchCase,
|
||||
bool inSentences);
|
||||
static bool SearchStringInEvent(gd::BaseEvent& events,
|
||||
gd::String search,
|
||||
bool matchCase);
|
||||
|
||||
static const gd::String searchIgnoredCharacters;
|
||||
|
||||
EventsRefactorer(){};
|
||||
};
|
||||
|
@@ -9,12 +9,12 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
|
||||
#include "GDCore/IDE/Events/ExpressionNodeLocationFinder.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2.h"
|
||||
|
||||
namespace gd {
|
||||
class Expression;
|
||||
@@ -32,7 +32,7 @@ namespace gd {
|
||||
* The IDE is responsible for actually *searching* and showing the completions -
|
||||
* this is only describing what must be listed.
|
||||
*/
|
||||
struct ExpressionCompletionDescription {
|
||||
struct GD_CORE_API ExpressionCompletionDescription {
|
||||
public:
|
||||
/**
|
||||
* The different kind of completions that can be described.
|
||||
@@ -274,7 +274,7 @@ struct ExpressionCompletionDescription {
|
||||
/**
|
||||
* \brief Turn an ExpressionCompletionDescription to a string.
|
||||
*/
|
||||
std::ostream& operator<<(std::ostream& os,
|
||||
GD_CORE_API std::ostream& operator<<(std::ostream& os,
|
||||
ExpressionCompletionDescription const& value);
|
||||
|
||||
/**
|
||||
@@ -326,10 +326,7 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
node.type, "", searchedPosition + 1, searchedPosition + 1));
|
||||
}
|
||||
void OnVisitOperatorNode(OperatorNode& node) override {
|
||||
completions.push_back(ExpressionCompletionDescription::ForObject(
|
||||
node.type, "", searchedPosition + 1, searchedPosition + 1));
|
||||
completions.push_back(ExpressionCompletionDescription::ForExpression(
|
||||
node.type, "", searchedPosition + 1, searchedPosition + 1));
|
||||
// No completions.
|
||||
}
|
||||
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
|
||||
completions.push_back(ExpressionCompletionDescription::ForObject(
|
||||
@@ -359,8 +356,9 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
}
|
||||
// Search the parameter metadata index skipping invisible ones.
|
||||
size_t visibleParameterIndex = 0;
|
||||
size_t metadataParameterIndex = ExpressionParser2::WrittenParametersFirstIndex(
|
||||
functionCall->objectName, functionCall->behaviorName);
|
||||
size_t metadataParameterIndex =
|
||||
ExpressionParser2::WrittenParametersFirstIndex(
|
||||
functionCall->objectName, functionCall->behaviorName);
|
||||
|
||||
const gd::ParameterMetadata* parameterMetadata = nullptr;
|
||||
while (metadataParameterIndex <
|
||||
|
@@ -4,7 +4,6 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
@@ -90,6 +89,19 @@ InstructionSentenceFormatter::GetAsFormattedText(
|
||||
return formattedStr;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
gd::String InstructionSentenceFormatter::GetFullText(
|
||||
const gd::Instruction &instr, const gd::InstructionMetadata &metadata)
|
||||
{
|
||||
const std::vector<std::pair<gd::String, gd::TextFormatting> > formattedText =
|
||||
GetAsFormattedText(instr, metadata);
|
||||
|
||||
#endif
|
||||
gd::String completeSentence = "";
|
||||
|
||||
for (std::size_t id = 0; id < formattedText.size(); ++id) {
|
||||
completeSentence += formattedText.at(id).first;
|
||||
}
|
||||
|
||||
return completeSentence;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -4,7 +4,6 @@
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#ifndef TRANSLATEACTION_H
|
||||
#define TRANSLATEACTION_H
|
||||
#include <map>
|
||||
@@ -39,6 +38,9 @@ class GD_CORE_API InstructionSentenceFormatter {
|
||||
return (static_cast<InstructionSentenceFormatter *>(_singleton));
|
||||
}
|
||||
|
||||
gd::String GetFullText(const gd::Instruction &instr,
|
||||
const gd::InstructionMetadata &metadata);
|
||||
|
||||
static void DestroySingleton() {
|
||||
if (NULL != _singleton) {
|
||||
delete _singleton;
|
||||
@@ -55,4 +57,3 @@ class GD_CORE_API InstructionSentenceFormatter {
|
||||
|
||||
} // namespace gd
|
||||
#endif // TRANSLATEACTION_H
|
||||
#endif
|
||||
|
@@ -30,6 +30,16 @@ void ArbitraryResourceWorker::ExposeImage(gd::String& imageName){
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeJson(gd::String& jsonName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeVideo(gd::String& videoName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
};
|
||||
|
||||
void ArbitraryResourceWorker::ExposeBitmapFont(gd::String& bitmapFontName){
|
||||
// Nothing to do by default - each child class can define here the action to
|
||||
// do.
|
||||
|
@@ -70,6 +70,16 @@ class GD_CORE_API ArbitraryResourceWorker {
|
||||
*/
|
||||
virtual void ExposeFont(gd::String &fontName);
|
||||
|
||||
/**
|
||||
* \brief Expose a JSON, which is always a reference to a "json" resource.
|
||||
*/
|
||||
virtual void ExposeJson(gd::String &jsonName);
|
||||
|
||||
/**
|
||||
* \brief Expose a video, which is always a reference to a "video" resource.
|
||||
*/
|
||||
virtual void ExposeVideo(gd::String &videoName);
|
||||
|
||||
/**
|
||||
* \brief Expose a bitmap font, which is always a reference to a "bitmapFont" resource.
|
||||
*/
|
||||
|
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -36,17 +37,20 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
virtual ~ResourcesInUseHelper(){};
|
||||
|
||||
std::set<gd::String>& GetAllImages() { return GetAll("image"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
std::set<gd::String>& GetAllAudios() { return GetAll("audio"); };
|
||||
std::set<gd::String>& GetAllFonts() { return GetAll("font"); };
|
||||
std::set<gd::String>& GetAllJsons() { return GetAll("json"); };
|
||||
std::set<gd::String>& GetAllVideos() { return GetAll("video"); };
|
||||
std::set<gd::String>& GetAllBitmapFonts() { return GetAll("bitmapFont"); };
|
||||
std::set<gd::String>& GetAll(const gd::String& resourceType) {
|
||||
return resourceType == "image"
|
||||
? allImages
|
||||
: (resourceType == "audio"
|
||||
? allAudios
|
||||
: (resourceType == "font")
|
||||
? allFonts
|
||||
: (resourceType == "bitmapFont") ? allBitmapFonts : emptyResources);
|
||||
if (resourceType == "image") return allImages;
|
||||
if (resourceType == "audio") return allAudios;
|
||||
if (resourceType == "font") return allFonts;
|
||||
if (resourceType == "json") return allJsons;
|
||||
if (resourceType == "video") return allVideos;
|
||||
if (resourceType == "bitmapFont") return allBitmapFonts;
|
||||
|
||||
return emptyResources;
|
||||
};
|
||||
|
||||
virtual void ExposeFile(gd::String& resource) override{
|
||||
@@ -61,6 +65,12 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
allFonts.insert(fontResourceName);
|
||||
};
|
||||
virtual void ExposeJson(gd::String& jsonResourceName) override {
|
||||
allJsons.insert(jsonResourceName);
|
||||
};
|
||||
virtual void ExposeVideo(gd::String& videoResourceName) override {
|
||||
allVideos.insert(videoResourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontResourceName) override {
|
||||
allBitmapFonts.insert(bitmapFontResourceName);
|
||||
};
|
||||
@@ -69,6 +79,8 @@ class ResourcesInUseHelper : public gd::ArbitraryResourceWorker {
|
||||
std::set<gd::String> allImages;
|
||||
std::set<gd::String> allAudios;
|
||||
std::set<gd::String> allFonts;
|
||||
std::set<gd::String> allJsons;
|
||||
std::set<gd::String> allVideos;
|
||||
std::set<gd::String> allBitmapFonts;
|
||||
std::set<gd::String> emptyResources;
|
||||
};
|
||||
|
@@ -46,6 +46,12 @@ class ResourcesRenamer : public gd::ArbitraryResourceWorker {
|
||||
virtual void ExposeFont(gd::String& fontResourceName) override {
|
||||
RenameIfNeeded(fontResourceName);
|
||||
};
|
||||
virtual void ExposeJson(gd::String& jsonResourceName) override {
|
||||
RenameIfNeeded(jsonResourceName);
|
||||
};
|
||||
virtual void ExposeVideo(gd::String& videoResourceName) override {
|
||||
RenameIfNeeded(videoResourceName);
|
||||
};
|
||||
virtual void ExposeBitmapFont(gd::String& bitmapFontName) override {
|
||||
RenameIfNeeded(bitmapFontName);
|
||||
};
|
||||
|
@@ -17,6 +17,7 @@ void EventsFunction::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("fullName", fullName);
|
||||
element.SetAttribute("description", description);
|
||||
element.SetAttribute("sentence", sentence);
|
||||
element.SetAttribute("group", group);
|
||||
element.SetBoolAttribute("private", isPrivate);
|
||||
events.SerializeTo(element.AddChild("events"));
|
||||
|
||||
@@ -44,6 +45,7 @@ void EventsFunction::UnserializeFrom(gd::Project& project,
|
||||
fullName = element.GetStringAttribute("fullName");
|
||||
description = element.GetStringAttribute("description");
|
||||
sentence = element.GetStringAttribute("sentence");
|
||||
group = element.GetStringAttribute("group");
|
||||
isPrivate = element.GetBoolAttribute("private");
|
||||
events.UnserializeFrom(project, element.GetChild("events"));
|
||||
|
||||
|
@@ -102,6 +102,19 @@ class GD_CORE_API EventsFunction {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Get the group of the instruction in the editor.
|
||||
*/
|
||||
const gd::String& GetGroup() const { return group; };
|
||||
|
||||
/**
|
||||
* \brief Set the group of the instruction in the editor.
|
||||
*/
|
||||
EventsFunction& SetGroup(const gd::String& group_) {
|
||||
group = group_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
enum FunctionType { Action, Condition, Expression, StringExpression };
|
||||
|
||||
/**
|
||||
@@ -188,6 +201,7 @@ class GD_CORE_API EventsFunction {
|
||||
gd::String fullName;
|
||||
gd::String description;
|
||||
gd::String sentence;
|
||||
gd::String group;
|
||||
gd::EventsList events;
|
||||
FunctionType functionType;
|
||||
std::vector<gd::ParameterMetadata> parameters;
|
||||
|
@@ -3,7 +3,6 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "EventsFunctionsExtension.h"
|
||||
|
||||
#include "EventsBasedBehavior.h"
|
||||
@@ -51,6 +50,11 @@ void EventsFunctionsExtension::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("description", description);
|
||||
element.SetAttribute("name", name);
|
||||
element.SetAttribute("fullName", fullName);
|
||||
if (!originName.empty() || !originIdentifier.empty()) {
|
||||
element.AddChild("origin")
|
||||
.SetAttribute("name", originName)
|
||||
.SetAttribute("identifier", originIdentifier);
|
||||
}
|
||||
auto& tagsElement = element.AddChild("tags");
|
||||
tagsElement.ConsiderAsArray();
|
||||
for (const auto& tag : tags) {
|
||||
@@ -88,6 +92,14 @@ void EventsFunctionsExtension::UnserializeFrom(
|
||||
iconUrl = element.GetStringAttribute("iconUrl");
|
||||
helpPath = element.GetStringAttribute("helpPath");
|
||||
|
||||
if (element.HasChild("origin")) {
|
||||
gd::String originName =
|
||||
element.GetChild("origin").GetStringAttribute("name", "");
|
||||
gd::String originIdentifier =
|
||||
element.GetChild("origin").GetStringAttribute("identifier", "");
|
||||
SetOrigin(originName, originIdentifier);
|
||||
}
|
||||
|
||||
tags.clear();
|
||||
auto& tagsElement = element.GetChild("tags");
|
||||
if (!tagsElement.IsValueUndefined()) {
|
||||
@@ -138,5 +150,3 @@ bool EventsFunctionsExtension::IsExtensionLifecycleEventsFunction(
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
||||
#endif
|
||||
|
@@ -3,7 +3,6 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#ifndef GDCORE_EVENTSFUNCTIONEXTENSION_H
|
||||
#define GDCORE_EVENTSFUNCTIONEXTENSION_H
|
||||
|
||||
@@ -140,6 +139,23 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
|
||||
return eventsBasedBehaviors;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Sets an extension origin. This method is not present since the
|
||||
* beginning so the projects created before that will have extensions
|
||||
* installed from the store without an origin. Keep that in mind when creating
|
||||
* features that rely on an extension's origin.
|
||||
*/
|
||||
virtual void SetOrigin(const gd::String& originName_,
|
||||
const gd::String& originIdentifier_) {
|
||||
originName = originName_;
|
||||
originIdentifier = originIdentifier_;
|
||||
}
|
||||
|
||||
virtual const gd::String& GetOriginName() const { return originName; }
|
||||
virtual const gd::String& GetOriginIdentifier() const {
|
||||
return originIdentifier;
|
||||
}
|
||||
|
||||
/** \name Dependencies
|
||||
*/
|
||||
///@{
|
||||
@@ -226,6 +242,8 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
|
||||
std::vector<gd::String> authorIds;
|
||||
gd::String author;
|
||||
gd::String previewIconUrl;
|
||||
gd::String originName;
|
||||
gd::String originIdentifier;
|
||||
gd::String iconUrl;
|
||||
gd::String helpPath; ///< The relative path to the help for this extension in
|
||||
///< the documentation (or an absolute URL).
|
||||
@@ -236,4 +254,3 @@ class GD_CORE_API EventsFunctionsExtension : public EventsFunctionsContainer {
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_EVENTSFUNCTIONEXTENSION_H
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "GDCore/Project/ExternalEvents.h"
|
||||
|
||||
#include "ExternalEvents.h"
|
||||
#include "GDCore/Events/Event.h"
|
||||
#include "GDCore/Events/Serialization.h"
|
||||
@@ -48,4 +48,3 @@ void ExternalEvents::UnserializeFrom(gd::Project& project,
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
#endif
|
||||
|
@@ -3,12 +3,12 @@
|
||||
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#ifndef GDCORE_EXTERNALEVENTS_H
|
||||
#define GDCORE_EXTERNALEVENTS_H
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "GDCore/Events/EventsList.h"
|
||||
#include "GDCore/String.h"
|
||||
namespace gd {
|
||||
@@ -135,4 +135,3 @@ struct ExternalEventsHasName
|
||||
} // namespace gd
|
||||
|
||||
#endif // GDCORE_EXTERNALEVENTS_H
|
||||
#endif
|
||||
|
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "GDCore/Project/ExternalLayout.h"
|
||||
|
||||
#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h"
|
||||
#include "GDCore/Project/InitialInstancesContainer.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
@@ -15,19 +16,15 @@ namespace gd {
|
||||
void ExternalLayout::UnserializeFrom(const SerializerElement& element) {
|
||||
name = element.GetStringAttribute("name", "", "Name");
|
||||
instances.UnserializeFrom(element.GetChild("instances", 0, "Instances"));
|
||||
#if defined(GD_IDE_ONLY)
|
||||
editorSettings.UnserializeFrom(element.GetChild("editionSettings"));
|
||||
#endif
|
||||
associatedLayout = element.GetStringAttribute("associatedLayout");
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ExternalLayout::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("name", name);
|
||||
instances.SerializeTo(element.AddChild("instances"));
|
||||
editorSettings.SerializeTo(element.AddChild("editionSettings"));
|
||||
element.SetAttribute("associatedLayout", associatedLayout);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace gd
|
||||
|
@@ -7,14 +7,13 @@
|
||||
#ifndef GDCORE_EXTERNALLAYOUT_H
|
||||
#define GDCORE_EXTERNALLAYOUT_H
|
||||
#include <memory>
|
||||
|
||||
#include "GDCore/Project/InitialInstancesContainer.h"
|
||||
#include "GDCore/String.h"
|
||||
namespace gd {
|
||||
class SerializerElement;
|
||||
}
|
||||
#if defined(GD_IDE_ONLY)
|
||||
#include "GDCore/IDE/Dialogs/LayoutEditorCanvas/EditorSettings.h"
|
||||
#endif
|
||||
|
||||
namespace gd {
|
||||
|
||||
@@ -54,7 +53,6 @@ class GD_CORE_API ExternalLayout {
|
||||
*/
|
||||
gd::InitialInstancesContainer& GetInitialInstances() { return instances; }
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
/**
|
||||
* \brief Get the user settings for the IDE.
|
||||
*/
|
||||
@@ -65,10 +63,7 @@ class GD_CORE_API ExternalLayout {
|
||||
/**
|
||||
* \brief Get the user settings for the IDE.
|
||||
*/
|
||||
gd::EditorSettings& GetAssociatedEditorSettings() {
|
||||
return editorSettings;
|
||||
}
|
||||
#endif
|
||||
gd::EditorSettings& GetAssociatedEditorSettings() { return editorSettings; }
|
||||
|
||||
/**
|
||||
* \brief Get the name of the layout last used to edit the external layout.
|
||||
@@ -80,15 +75,13 @@ class GD_CORE_API ExternalLayout {
|
||||
*/
|
||||
void SetAssociatedLayout(const gd::String& name) { associatedLayout = name; }
|
||||
|
||||
/** \name Serialization
|
||||
*/
|
||||
///@{
|
||||
#if defined(GD_IDE_ONLY)
|
||||
/** \name Serialization
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* \brief Serialize external layout.
|
||||
*/
|
||||
void SerializeTo(SerializerElement& element) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Unserialize the external layout.
|
||||
@@ -99,9 +92,7 @@ class GD_CORE_API ExternalLayout {
|
||||
private:
|
||||
gd::String name;
|
||||
gd::InitialInstancesContainer instances;
|
||||
#if defined(GD_IDE_ONLY)
|
||||
gd::EditorSettings editorSettings;
|
||||
#endif
|
||||
gd::String associatedLayout;
|
||||
};
|
||||
|
||||
|
@@ -19,6 +19,7 @@ void PropertyDescriptor::SerializeTo(SerializerElement& element) const {
|
||||
element.AddChild("type").SetStringValue(type);
|
||||
element.AddChild("label").SetStringValue(label);
|
||||
element.AddChild("description").SetStringValue(description);
|
||||
element.AddChild("group").SetStringValue(group);
|
||||
SerializerElement& extraInformationElement =
|
||||
element.AddChild("extraInformation");
|
||||
extraInformationElement.ConsiderAsArray();
|
||||
@@ -33,6 +34,7 @@ void PropertyDescriptor::UnserializeFrom(const SerializerElement& element) {
|
||||
type = element.GetChild("type").GetStringValue();
|
||||
label = element.GetChild("label").GetStringValue();
|
||||
description = element.GetChild("description").GetStringValue();
|
||||
group = element.GetChild("group").GetStringValue();
|
||||
|
||||
extraInformation.clear();
|
||||
const SerializerElement& extraInformationElement =
|
||||
|
@@ -76,6 +76,14 @@ class GD_CORE_API PropertyDescriptor {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Change the group where this property is displayed to the user, if any.
|
||||
*/
|
||||
PropertyDescriptor& SetGroup(gd::String group_) {
|
||||
group = group_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Set and replace the additional information for the property.
|
||||
*/
|
||||
@@ -100,6 +108,7 @@ class GD_CORE_API PropertyDescriptor {
|
||||
const gd::String& GetType() const { return type; }
|
||||
const gd::String& GetLabel() const { return label; }
|
||||
const gd::String& GetDescription() const { return description; }
|
||||
const gd::String& GetGroup() const { return group; }
|
||||
|
||||
const std::vector<gd::String>& GetExtraInfo() const {
|
||||
return extraInformation;
|
||||
@@ -153,6 +162,7 @@ class GD_CORE_API PropertyDescriptor {
|
||||
///< the class responsible for updating the property grid.
|
||||
gd::String label; //< The user-friendly property name
|
||||
gd::String description; //< The user-friendly property description
|
||||
gd::String group; //< The user-friendly property group
|
||||
std::vector<gd::String>
|
||||
extraInformation; ///< Can be used to store for example the available
|
||||
///< choices, if a property is a displayed as a combo
|
||||
|
@@ -24,12 +24,9 @@ gd::String Resource::badStr;
|
||||
|
||||
Resource ResourcesManager::badResource;
|
||||
gd::String ResourcesManager::badResourceName;
|
||||
#if defined(GD_IDE_ONLY)
|
||||
ResourceFolder ResourcesManager::badFolder;
|
||||
Resource ResourceFolder::badResource;
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ResourceFolder::Init(const ResourceFolder& other) {
|
||||
name = other.name;
|
||||
|
||||
@@ -38,19 +35,16 @@ void ResourceFolder::Init(const ResourceFolder& other) {
|
||||
resources.push_back(std::shared_ptr<Resource>(other.resources[i]->Clone()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ResourcesManager::Init(const ResourcesManager& other) {
|
||||
resources.clear();
|
||||
for (std::size_t i = 0; i < other.resources.size(); ++i) {
|
||||
resources.push_back(std::shared_ptr<Resource>(other.resources[i]->Clone()));
|
||||
}
|
||||
#if defined(GD_IDE_ONLY)
|
||||
folders.clear();
|
||||
for (std::size_t i = 0; i < other.folders.size(); ++i) {
|
||||
folders.push_back(other.folders[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Resource& ResourcesManager::GetResource(const gd::String& name) {
|
||||
@@ -147,7 +141,6 @@ std::vector<gd::String> ResourcesManager::FindFilesNotInResources(
|
||||
return filesNotInResources;
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
std::map<gd::String, gd::PropertyDescriptor> Resource::GetProperties() const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> nothing;
|
||||
return nothing;
|
||||
@@ -443,9 +436,7 @@ void ResourcesManager::RemoveResource(const gd::String& name) {
|
||||
for (std::size_t i = 0; i < folders.size(); ++i)
|
||||
folders[i].RemoveResource(name);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ResourceFolder::UnserializeFrom(const SerializerElement& element,
|
||||
gd::ResourcesManager& parentManager) {
|
||||
name = element.GetStringAttribute("name");
|
||||
@@ -470,7 +461,6 @@ void ResourceFolder::SerializeTo(SerializerElement& element) const {
|
||||
.SetAttribute("name", resources[i]->GetName());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
|
||||
resources.clear();
|
||||
@@ -500,7 +490,6 @@ void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
|
||||
resources.push_back(resource);
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
folders.clear();
|
||||
const SerializerElement& resourcesFoldersElement =
|
||||
element.GetChild("resourceFolders", 0, "ResourceFolders");
|
||||
@@ -511,10 +500,8 @@ void ResourcesManager::UnserializeFrom(const SerializerElement& element) {
|
||||
|
||||
folders.push_back(folder);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ResourcesManager::SerializeTo(SerializerElement& element) const {
|
||||
SerializerElement& resourcesElement = element.AddChild("resources");
|
||||
resourcesElement.ConsiderAsArrayOf("resource");
|
||||
@@ -543,7 +530,6 @@ void ResourcesManager::SerializeTo(SerializerElement& element) const {
|
||||
for (std::size_t i = 0; i < folders.size(); ++i)
|
||||
folders[i].SerializeTo(resourcesFoldersElement.AddChild("folder"));
|
||||
}
|
||||
#endif
|
||||
|
||||
void ImageResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -560,14 +546,12 @@ void ImageResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void ImageResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("alwaysLoaded", alwaysLoaded);
|
||||
element.SetAttribute("smoothed", smooth);
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
void AudioResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -584,14 +568,12 @@ void AudioResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetPreloadAsSound(element.GetBoolAttribute("preloadAsSound"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void AudioResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
element.SetAttribute("preloadAsMusic", PreloadAsMusic());
|
||||
element.SetAttribute("preloadAsSound", PreloadAsSound());
|
||||
}
|
||||
#endif
|
||||
|
||||
void FontResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -606,12 +588,10 @@ void FontResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void FontResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
void VideoResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -626,12 +606,10 @@ void VideoResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void VideoResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
void JsonResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -647,7 +625,6 @@ void JsonResource::UnserializeFrom(const SerializerElement& element) {
|
||||
DisablePreload(element.GetBoolAttribute("disablePreload", false));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void JsonResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
@@ -672,7 +649,6 @@ bool JsonResource::UpdateProperty(const gd::String& name,
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void BitmapFontResource::SetFile(const gd::String& newFile) {
|
||||
file = newFile;
|
||||
@@ -687,14 +663,11 @@ void BitmapFontResource::UnserializeFrom(const SerializerElement& element) {
|
||||
SetFile(element.GetStringAttribute("file"));
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
void BitmapFontResource::SerializeTo(SerializerElement& element) const {
|
||||
element.SetAttribute("userAdded", IsUserAdded());
|
||||
element.SetAttribute("file", GetFile());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
ResourceFolder::ResourceFolder(const ResourceFolder& other) { Init(other); }
|
||||
|
||||
ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
|
||||
@@ -702,7 +675,6 @@ ResourceFolder& ResourceFolder::operator=(const ResourceFolder& other) {
|
||||
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
ResourcesManager::ResourcesManager(const ResourcesManager& other) {
|
||||
Init(other);
|
||||
|
@@ -104,7 +104,6 @@ class GD_CORE_API Resource {
|
||||
*/
|
||||
virtual const gd::String& GetMetadata() const { return metadata; }
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
/** \name Resources properties
|
||||
* Reading and updating resources properties
|
||||
*/
|
||||
@@ -136,7 +135,6 @@ class GD_CORE_API Resource {
|
||||
return false;
|
||||
};
|
||||
///@}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Serialize the object
|
||||
@@ -186,7 +184,6 @@ class GD_CORE_API ImageResource : public Resource {
|
||||
*/
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
@@ -196,7 +193,6 @@ class GD_CORE_API ImageResource : public Resource {
|
||||
* \brief Serialize the object
|
||||
*/
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Unserialize the objectt.
|
||||
@@ -238,14 +234,12 @@ class GD_CORE_API AudioResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
|
||||
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -292,10 +286,8 @@ class GD_CORE_API FontResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -320,10 +312,8 @@ class GD_CORE_API VideoResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -348,14 +338,12 @@ class GD_CORE_API JsonResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
|
||||
std::map<gd::String, gd::PropertyDescriptor> GetProperties() const override;
|
||||
bool UpdateProperty(const gd::String& name, const gd::String& value) override;
|
||||
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -391,10 +379,8 @@ class GD_CORE_API BitmapFontResource : public Resource {
|
||||
virtual const gd::String& GetFile() const override { return file; };
|
||||
virtual void SetFile(const gd::String& newFile) override;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
virtual bool UseFile() override { return true; }
|
||||
void SerializeTo(SerializerElement& element) const override;
|
||||
#endif
|
||||
|
||||
void UnserializeFrom(const SerializerElement& element) override;
|
||||
|
||||
@@ -463,7 +449,6 @@ class GD_CORE_API ResourcesManager {
|
||||
*/
|
||||
std::vector<gd::String> FindFilesNotInResources(const std::vector<gd::String>& filesToCheck) const;
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
/**
|
||||
* \brief Return a (smart) pointer to a resource.
|
||||
*/
|
||||
@@ -557,7 +542,6 @@ class GD_CORE_API ResourcesManager {
|
||||
* \brief Serialize the object
|
||||
*/
|
||||
void SerializeTo(SerializerElement& element) const;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \brief Unserialize the objectt.
|
||||
@@ -568,18 +552,13 @@ class GD_CORE_API ResourcesManager {
|
||||
void Init(const ResourcesManager& other);
|
||||
|
||||
std::vector<std::shared_ptr<Resource> > resources;
|
||||
#if defined(GD_IDE_ONLY)
|
||||
std::vector<ResourceFolder> folders;
|
||||
#endif
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
static ResourceFolder badFolder;
|
||||
#endif
|
||||
static Resource badResource;
|
||||
static gd::String badResourceName;
|
||||
};
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
class GD_CORE_API ResourceFolder {
|
||||
public:
|
||||
ResourceFolder(){};
|
||||
@@ -654,7 +633,6 @@ class GD_CORE_API ResourceFolder {
|
||||
void Init(const ResourceFolder& other);
|
||||
static Resource badResource;
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace gd
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "GDCore/String.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string.h>
|
||||
|
||||
#include <SFML/System/String.hpp>
|
||||
@@ -283,6 +284,42 @@ String& String::insert( size_type pos, const String &str )
|
||||
return *this;
|
||||
}
|
||||
|
||||
String& String::replace_if(iterator i1, iterator i2, std::function<bool(char32_t)> p, const String &str)
|
||||
{
|
||||
String::size_type offset = 1;
|
||||
iterator it = i1.base();
|
||||
while(it < i2.base())
|
||||
{
|
||||
if (p(*it)) { replace(std::distance(begin(), it), offset, str); }
|
||||
else { it++; }
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
String& String::RemoveConsecutiveOccurrences(iterator i1, iterator i2, const char c)
|
||||
{
|
||||
std::vector<std::pair<size_type, size_type>> ranges_to_remove;
|
||||
for(iterator current_index = i1.base(); current_index < i2.base(); current_index++)
|
||||
{
|
||||
if (*current_index == c){
|
||||
iterator current_subindex = current_index;
|
||||
std::advance(current_subindex, 1);
|
||||
if (*current_subindex == c) {
|
||||
while(current_subindex < end() && *current_subindex == c)
|
||||
{
|
||||
current_subindex++;
|
||||
}
|
||||
replace(std::distance(begin(), current_index),
|
||||
std::distance(current_index, current_subindex),
|
||||
c);
|
||||
|
||||
std::advance(current_index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
String& String::replace( iterator i1, iterator i2, const String &str )
|
||||
{
|
||||
m_string.replace(i1.base(), i2.base(), str.m_string);
|
||||
@@ -290,6 +327,31 @@ String& String::replace( iterator i1, iterator i2, const String &str )
|
||||
return *this;
|
||||
}
|
||||
|
||||
String& String::replace( iterator i1, iterator i2, size_type n, const char c )
|
||||
{
|
||||
m_string.replace(i1.base(), i2.base(), n, c);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
String& String::replace( String::size_type pos, String::size_type len, const char c )
|
||||
{
|
||||
if(pos > size())
|
||||
throw std::out_of_range("[gd::String::replace] starting pos greater than size");
|
||||
|
||||
iterator i1 = begin();
|
||||
std::advance( i1, pos );
|
||||
|
||||
iterator i2 = i1;
|
||||
while(i2 != end() && len > 0) //Increment "len" times and stop if end() is reached
|
||||
{
|
||||
++i2;
|
||||
--len;
|
||||
}
|
||||
|
||||
return replace( i1, i2, 1, c );
|
||||
}
|
||||
|
||||
String& String::replace( String::size_type pos, String::size_type len, const String &str )
|
||||
{
|
||||
if(pos > size())
|
||||
|
@@ -438,15 +438,52 @@ public:
|
||||
*/
|
||||
String& replace( iterator i1, iterator i2, const String &str );
|
||||
|
||||
/**
|
||||
* \brief Replace the portion of the String between **i1** and **i2** (**i2** not
|
||||
* included) by **n** consecutive copies of character **c**.
|
||||
* \return *this
|
||||
*
|
||||
* **Iterators :** All iterators may be invalidated.
|
||||
*/
|
||||
String& replace( iterator i1, iterator i2, size_type n, const char c );
|
||||
|
||||
/**
|
||||
* \brief Replace the portion of the String between **pos** and **pos** + **len**
|
||||
* (the character at **pos** + **len** is not included)
|
||||
* (the character at **pos** + **len** is not included) with **str**.
|
||||
* \return *this
|
||||
*
|
||||
* **Iterators :** All iterators may be invalidated.
|
||||
*/
|
||||
String& replace( size_type pos, size_type len, const String &str );
|
||||
|
||||
/**
|
||||
* \brief Replace the portion of the String between **pos** and **pos** + **len**
|
||||
* (the character at **pos** + **len** is not included) with the character **c**.
|
||||
* \return *this
|
||||
*
|
||||
* **Iterators :** All iterators may be invalidated.
|
||||
*/
|
||||
String& replace( size_type pos, size_type len, const char c );
|
||||
|
||||
/**
|
||||
* \brief Search in the portion of the String between **i1** and **i2** (**i2** not
|
||||
* included) for characters matching predicate function **p** and replace them
|
||||
* by the String **str**.
|
||||
* \return *this
|
||||
*
|
||||
* **Iterators :** All iterators may be invalidated.
|
||||
*/
|
||||
String& replace_if( iterator i1, iterator i2, std::function<bool(char32_t)> p, const String &str );
|
||||
|
||||
/**
|
||||
* \brief Remove consecutive occurrences of the character **c** in the portion of the
|
||||
* between **i1** and **i2** (**i2** not included) to replace it by a single occurrence.
|
||||
* \return *this
|
||||
*
|
||||
* **Iterators :** All iterators may be invalidated.
|
||||
*/
|
||||
String& RemoveConsecutiveOccurrences(iterator i1, iterator i2, const char c);
|
||||
|
||||
/**
|
||||
* \brief Erase the characters between **first** and **last** (**last** not included).
|
||||
* \param first an iterator to the first character to remove
|
||||
|
@@ -87,42 +87,27 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
|
||||
}
|
||||
}
|
||||
SECTION("Operator (number)") {
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions1{
|
||||
gd::ExpressionCompletionDescription::ForObject("number", "", 1, 1),
|
||||
gd::ExpressionCompletionDescription::ForExpression("number", "", 1, 1)};
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions2{
|
||||
gd::ExpressionCompletionDescription::ForObject("number", "", 2, 2),
|
||||
gd::ExpressionCompletionDescription::ForExpression("number", "", 2, 2)};
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions3{
|
||||
gd::ExpressionCompletionDescription::ForObject("number", "", 3, 3),
|
||||
gd::ExpressionCompletionDescription::ForExpression("number", "", 3, 3)};
|
||||
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedCompletions1);
|
||||
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedCompletions2);
|
||||
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedCompletions3);
|
||||
REQUIRE(getCompletionsFor("number", "1 + ", 1) == expectedEmptyCompletions);
|
||||
REQUIRE(getCompletionsFor("number", "1 + ", 2) == expectedEmptyCompletions);
|
||||
REQUIRE(getCompletionsFor("number", "1 + ", 3) == expectedEmptyCompletions);
|
||||
}
|
||||
SECTION("Operator (string)") {
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions3{
|
||||
gd::ExpressionCompletionDescription::ForObject("string", "", 3, 3),
|
||||
gd::ExpressionCompletionDescription::ForExpression("string", "", 3, 3)};
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions4{
|
||||
gd::ExpressionCompletionDescription::ForObject("string", "", 4, 4),
|
||||
gd::ExpressionCompletionDescription::ForExpression("string", "", 4, 4)};
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions5{
|
||||
gd::ExpressionCompletionDescription::ForObject("string", "", 5, 5),
|
||||
gd::ExpressionCompletionDescription::ForExpression("string", "", 5, 5)};
|
||||
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) == expectedCompletions3);
|
||||
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) == expectedCompletions4);
|
||||
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) == expectedCompletions5);
|
||||
REQUIRE(getCompletionsFor("string", "\"a\" + ", 3) ==
|
||||
expectedEmptyCompletions);
|
||||
REQUIRE(getCompletionsFor("string", "\"a\" + ", 4) ==
|
||||
expectedEmptyCompletions);
|
||||
REQUIRE(getCompletionsFor("string", "\"a\" + ", 5) ==
|
||||
expectedEmptyCompletions);
|
||||
}
|
||||
|
||||
SECTION("Free function") {
|
||||
SECTION("Test 1") {
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedCompletions{
|
||||
gd::ExpressionCompletionDescription::ForExpression(
|
||||
"unknown", "Function", 0, 8)};
|
||||
"string", "Function", 0, 8)};
|
||||
std::vector<gd::ExpressionCompletionDescription> expectedExactCompletions{
|
||||
gd::ExpressionCompletionDescription::ForExpression(
|
||||
"unknown", "Function", 0, 8)
|
||||
"string", "Function", 0, 8)
|
||||
.SetIsExact(true)};
|
||||
REQUIRE(getCompletionsFor("string", "Function(", 0) ==
|
||||
expectedCompletions);
|
||||
@@ -230,17 +215,17 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedObjectCompletions{
|
||||
gd::ExpressionCompletionDescription::ForObject(
|
||||
"unknown", "MyObject", 0, 8)};
|
||||
"string", "MyObject", 0, 8)};
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedBehaviorOrFunctionCompletions{
|
||||
gd::ExpressionCompletionDescription::ForBehavior(
|
||||
"Func", 9, 13, "MyObject"),
|
||||
gd::ExpressionCompletionDescription::ForExpression(
|
||||
"unknown", "Func", 9, 13, "MyObject")};
|
||||
"string", "Func", 9, 13, "MyObject")};
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedExactFunctionCompletions{
|
||||
gd::ExpressionCompletionDescription::ForExpression(
|
||||
"unknown", "Func", 9, 13, "MyObject")
|
||||
"string", "Func", 9, 13, "MyObject")
|
||||
.SetIsExact(true)};
|
||||
REQUIRE(getCompletionsFor("string", "MyObject.Func(", 0) ==
|
||||
expectedObjectCompletions);
|
||||
@@ -329,7 +314,7 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedObjectCompletions{
|
||||
gd::ExpressionCompletionDescription::ForObject(
|
||||
"unknown", "MyObject", 0, 8)};
|
||||
"string", "MyObject", 0, 8)};
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedBehaviorCompletions{
|
||||
gd::ExpressionCompletionDescription::ForBehavior(
|
||||
@@ -337,11 +322,11 @@ TEST_CASE("ExpressionCompletionFinder", "[common][events]") {
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedFunctionCompletions{
|
||||
gd::ExpressionCompletionDescription::ForExpression(
|
||||
"unknown", "Func", 21, 25, "MyObject", "MyBehavior")};
|
||||
"string", "Func", 21, 25, "MyObject", "MyBehavior")};
|
||||
std::vector<gd::ExpressionCompletionDescription>
|
||||
expectedExactFunctionCompletions{
|
||||
gd::ExpressionCompletionDescription::ForExpression(
|
||||
"unknown", "Func", 21, 25, "MyObject", "MyBehavior")
|
||||
"string", "Func", 21, 25, "MyObject", "MyBehavior")
|
||||
.SetIsExact(true)};
|
||||
REQUIRE(getCompletionsFor("string", "MyObject.MyBehavior::Func(", 0) ==
|
||||
expectedObjectCompletions);
|
||||
|
@@ -971,6 +971,35 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
REQUIRE(objectFunctionName.objectFunctionOrBehaviorName == "");
|
||||
}
|
||||
|
||||
SECTION("Unfinished object function name of type string with parentheses") {
|
||||
auto node = parser.ParseExpression("string", "MyObject.()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(objectFunctionCall.objectName == "MyObject");
|
||||
REQUIRE(objectFunctionCall.functionName == "");
|
||||
REQUIRE(objectFunctionCall.type == "string");
|
||||
}
|
||||
|
||||
SECTION("Unfinished object function name of type number with parentheses") {
|
||||
auto node = parser.ParseExpression("number", "MyObject.()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(objectFunctionCall.objectName == "MyObject");
|
||||
REQUIRE(objectFunctionCall.functionName == "");
|
||||
REQUIRE(objectFunctionCall.type == "number");
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"Unfinished object function name of type number|string with "
|
||||
"parentheses") {
|
||||
auto node = parser.ParseExpression("number|string", "MyObject.()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &objectFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(objectFunctionCall.objectName == "MyObject");
|
||||
REQUIRE(objectFunctionCall.functionName == "");
|
||||
REQUIRE(objectFunctionCall.type == "number|string");
|
||||
}
|
||||
|
||||
SECTION("Unfinished object behavior name") {
|
||||
auto node = parser.ParseExpression("string", "MyObject.MyBehavior::");
|
||||
REQUIRE(node != nullptr);
|
||||
@@ -981,6 +1010,67 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
REQUIRE(objectFunctionName.behaviorFunctionName == "");
|
||||
}
|
||||
|
||||
SECTION("Unfinished object behavior name of type string with parentheses") {
|
||||
auto node = parser.ParseExpression("string", "MyObject.MyBehavior::()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(objectFunctionName.objectName == "MyObject");
|
||||
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
|
||||
REQUIRE(objectFunctionName.functionName == "");
|
||||
REQUIRE(objectFunctionName.type == "string");
|
||||
}
|
||||
|
||||
SECTION("Unfinished object behavior name of type number with parentheses") {
|
||||
auto node = parser.ParseExpression("number", "MyObject.MyBehavior::()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(objectFunctionName.objectName == "MyObject");
|
||||
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
|
||||
REQUIRE(objectFunctionName.functionName == "");
|
||||
REQUIRE(objectFunctionName.type == "number");
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"Unfinished object behavior name of type number|string with "
|
||||
"parentheses") {
|
||||
auto node =
|
||||
parser.ParseExpression("number|string", "MyObject.MyBehavior::()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &objectFunctionName = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(objectFunctionName.objectName == "MyObject");
|
||||
REQUIRE(objectFunctionName.behaviorName == "MyBehavior");
|
||||
REQUIRE(objectFunctionName.functionName == "");
|
||||
REQUIRE(objectFunctionName.type == "number|string");
|
||||
}
|
||||
|
||||
SECTION("Unfinished free function name of type string with parentheses") {
|
||||
auto node = parser.ParseExpression("string", "fun()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(freeFunctionCall.objectName == "");
|
||||
REQUIRE(freeFunctionCall.functionName == "fun");
|
||||
REQUIRE(freeFunctionCall.type == "string");
|
||||
}
|
||||
|
||||
SECTION("Unfinished free function name of type number with parentheses") {
|
||||
auto node = parser.ParseExpression("number", "fun()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(freeFunctionCall.objectName == "");
|
||||
REQUIRE(freeFunctionCall.functionName == "fun");
|
||||
REQUIRE(freeFunctionCall.type == "number");
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"Unfinished free function name of type number|string with parentheses") {
|
||||
auto node = parser.ParseExpression("number|string", "fun()");
|
||||
REQUIRE(node != nullptr);
|
||||
auto &freeFunctionCall = dynamic_cast<gd::FunctionCallNode &>(*node);
|
||||
REQUIRE(freeFunctionCall.objectName == "");
|
||||
REQUIRE(freeFunctionCall.functionName == "fun");
|
||||
REQUIRE(freeFunctionCall.type == "number|string");
|
||||
}
|
||||
|
||||
SECTION("Invalid function calls") {
|
||||
{
|
||||
auto node = parser.ParseExpression("number", "Idontexist(12)");
|
||||
|
@@ -2,6 +2,8 @@ namespace gdjs {
|
||||
declare var admob: any;
|
||||
|
||||
export namespace adMob {
|
||||
const logger = new gdjs.Logger('AdMob');
|
||||
|
||||
export enum AdSizeType {
|
||||
BANNER,
|
||||
LARGE_BANNER,
|
||||
@@ -127,13 +129,13 @@ namespace gdjs {
|
||||
() => {
|
||||
bannerShowing = true;
|
||||
bannerLoading = false;
|
||||
console.info('AdMob banner successfully shown.');
|
||||
logger.info('AdMob banner successfully shown.');
|
||||
},
|
||||
(error) => {
|
||||
bannerShowing = false;
|
||||
bannerLoading = false;
|
||||
bannerErrored = true;
|
||||
console.error('Error while showing an AdMob banner:', error);
|
||||
logger.error('Error while showing an AdMob banner:', error);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -190,14 +192,14 @@ namespace gdjs {
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
console.info('AdMob interstitial successfully loaded.');
|
||||
logger.info('AdMob interstitial successfully loaded.');
|
||||
if (displayWhenLoaded) showInterstitial();
|
||||
},
|
||||
(error) => {
|
||||
interstitialLoading = false;
|
||||
interstitialReady = false;
|
||||
interstitialErrored = true;
|
||||
console.error('Error while loading a interstitial:', error);
|
||||
logger.error('Error while loading a interstitial:', error);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -216,7 +218,7 @@ namespace gdjs {
|
||||
(error) => {
|
||||
interstitialShowing = false;
|
||||
interstitialErrored = true;
|
||||
console.error('Error while trying to show an interstitial:', error);
|
||||
logger.error('Error while trying to show an interstitial:', error);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -270,7 +272,7 @@ namespace gdjs {
|
||||
})
|
||||
.then(
|
||||
() => {
|
||||
console.info('AdMob reward video successfully loaded.');
|
||||
logger.info('AdMob reward video successfully loaded.');
|
||||
|
||||
if (displayWhenLoaded) showVideo();
|
||||
},
|
||||
@@ -278,7 +280,7 @@ namespace gdjs {
|
||||
videoLoading = false;
|
||||
videoReady = false;
|
||||
videoErrored = true;
|
||||
console.error('Error while loading a reward video:', error);
|
||||
logger.error('Error while loading a reward video:', error);
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -298,7 +300,7 @@ namespace gdjs {
|
||||
(error) => {
|
||||
videoShowing = false;
|
||||
videoErrored = true;
|
||||
console.error('Error while trying to show a reward video:', error);
|
||||
logger.error('Error while trying to show a reward video:', error);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@@ -83,6 +83,30 @@ module.exports = {
|
||||
.setIncludeFile('Extensions/DebuggerTools/debuggertools.js')
|
||||
.setFunctionName('gdjs.evtTools.debuggerTools.enableDebugDraw');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'ConsoleLog',
|
||||
_('Log a message to the console'),
|
||||
_("Logs a message to the debugger's console."),
|
||||
_(
|
||||
'Log message _PARAM0_ of type _PARAM1_ to the console in group _PARAM2_'
|
||||
),
|
||||
_('Debugger Tools'),
|
||||
'res/actions/bug32.png',
|
||||
'res/actions/bug32.png'
|
||||
)
|
||||
.addParameter('string', 'Message to log', '', false)
|
||||
.addParameter(
|
||||
'stringWithSelector',
|
||||
'Message type',
|
||||
'["info", "warning", "error"]',
|
||||
true
|
||||
)
|
||||
.addParameter('string', 'Group of messages', '', true)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/DebuggerTools/debuggertools.js')
|
||||
.setFunctionName('gdjs.evtTools.debuggerTools.log');
|
||||
|
||||
return extension;
|
||||
},
|
||||
runExtensionSanityTests: function (
|
||||
|
@@ -13,6 +13,20 @@ namespace gdjs {
|
||||
runtimeScene.getGame().pause(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Logs a message to the console.
|
||||
* @param message - The message to log.
|
||||
* @param type - The type of log (info, warning or error).
|
||||
* @param group - The group of messages it belongs to.
|
||||
*/
|
||||
export const log = function (
|
||||
message: string,
|
||||
type: 'info' | 'warning' | 'error',
|
||||
group: string
|
||||
) {
|
||||
gdjs.Logger.getLoggerOutput().log(group, message, type, false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Enable or disable the debug draw.
|
||||
* @param runtimeScene - The current scene.
|
||||
|
@@ -62,7 +62,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/orientation_alpha24.png",
|
||||
"JsPlatform/Extensions/orientation_alpha32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -82,7 +82,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/orientation_beta24.png",
|
||||
"JsPlatform/Extensions/orientation_beta32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -102,7 +102,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/orientation_gamma24.png",
|
||||
"JsPlatform/Extensions/orientation_gamma32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -228,7 +228,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/motion_rotation_alpha24.png",
|
||||
"JsPlatform/Extensions/motion_rotation_alpha32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -248,7 +248,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/motion_rotation_beta24.png",
|
||||
"JsPlatform/Extensions/motion_rotation_beta32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -268,7 +268,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/motion_rotation_gamma24.png",
|
||||
"JsPlatform/Extensions/motion_rotation_gamma32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -288,7 +288,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/motion_acceleration_x24.png",
|
||||
"JsPlatform/Extensions/motion_acceleration_x32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -308,7 +308,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/motion_acceleration_y24.png",
|
||||
"JsPlatform/Extensions/motion_acceleration_y32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
@@ -328,7 +328,7 @@ module.exports = {
|
||||
"JsPlatform/Extensions/motion_acceleration_z24.png",
|
||||
"JsPlatform/Extensions/motion_acceleration_z32.png"
|
||||
)
|
||||
.addParameter("relationalOperator", _("Sign of the test"))
|
||||
.addParameter("relationalOperator", _("Sign of the test"), "number")
|
||||
.addParameter("expression", _("Value (m/s²)"))
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile(
|
||||
|
@@ -1,6 +1,8 @@
|
||||
// @ts-nocheck - Weird usage of `this` in this file. Should be refactored.
|
||||
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Dialogue tree');
|
||||
|
||||
gdjs.dialogueTree = {};
|
||||
gdjs.dialogueTree.runner = new bondage.Runner();
|
||||
|
||||
@@ -21,7 +23,7 @@ namespace gdjs {
|
||||
gdjs.dialogueTree.startFrom(startDialogueNode);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
logger.error('Error while loading from scene variable: ', e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -42,10 +44,7 @@ namespace gdjs {
|
||||
.getJsonManager()
|
||||
.loadJson(jsonResourceName, function (error, content) {
|
||||
if (error) {
|
||||
console.error(
|
||||
'An error happened while loading JSON resource:',
|
||||
error
|
||||
);
|
||||
logger.error('An error happened while loading JSON resource:', error);
|
||||
} else {
|
||||
if (!content) {
|
||||
return;
|
||||
@@ -54,7 +53,7 @@ namespace gdjs {
|
||||
try {
|
||||
gdjs.dialogueTree.runner.load(gdjs.dialogueTree.yarnData);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
'An error happened while loading parsing the dialogue tree data:',
|
||||
error
|
||||
);
|
||||
@@ -155,7 +154,7 @@ namespace gdjs {
|
||||
this.clipTextEnd >= this.dialogueText.length
|
||||
) {
|
||||
if (gdjs.dialogueTree.getVariable('debug')) {
|
||||
console.warn(
|
||||
logger.warn(
|
||||
'Scroll completed:',
|
||||
this.clipTextEnd,
|
||||
'/',
|
||||
@@ -244,7 +243,7 @@ namespace gdjs {
|
||||
gdjs.dialogueTree.pauseScrolling = false;
|
||||
commandCalls.splice(index, 1);
|
||||
if (gdjs.dialogueTree.getVariable('debug')) {
|
||||
console.info('CMD:', call);
|
||||
logger.info('CMD:', call);
|
||||
}
|
||||
}, parseInt(call.params[1], 10));
|
||||
}
|
||||
@@ -252,7 +251,7 @@ namespace gdjs {
|
||||
gdjs.dialogueTree.commandParameters = call.params;
|
||||
commandCalls.splice(index, 1);
|
||||
if (gdjs.dialogueTree.getVariable('debug')) {
|
||||
console.info('CMD:', call);
|
||||
logger.info('CMD:', call);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -366,7 +365,7 @@ namespace gdjs {
|
||||
this.dialogueData = this.dialogue.next().value;
|
||||
gdjs.dialogueTree.goToNextDialogueLine();
|
||||
} catch (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`An error happened when trying to access the dialogue branch!`,
|
||||
error
|
||||
);
|
||||
@@ -562,7 +561,7 @@ namespace gdjs {
|
||||
this.selectedOption = -1;
|
||||
this.selectedOptionUpdated = false;
|
||||
if (gdjs.dialogueTree.getVariable('debug')) {
|
||||
console.info('parsing:', this.dialogueData);
|
||||
logger.info('Parsing:', this.dialogueData);
|
||||
}
|
||||
if (!this.dialogueData) {
|
||||
gdjs.dialogueTree.stopRunningDialogue();
|
||||
@@ -797,7 +796,7 @@ namespace gdjs {
|
||||
gdjs.dialogueTree.loadState = function (inputVariable: gdjs.Variable) {
|
||||
const loadedState = inputVariable.toJSObject();
|
||||
if (!loadedState) {
|
||||
console.error('Load state variable is empty:', inputVariable);
|
||||
logger.error('Load state variable is empty:', inputVariable);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -808,7 +807,7 @@ namespace gdjs {
|
||||
gdjs.dialogueTree.runner.variables.set(key, value);
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('Failed to load state from variable:', inputVariable, e);
|
||||
logger.error('Failed to load state from variable:', inputVariable, e);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -179,7 +179,8 @@ module.exports = {
|
||||
.setValue(
|
||||
behaviorContent.getBoolAttribute('property2') ? 'true' : 'false'
|
||||
)
|
||||
.setType('Boolean');
|
||||
.setType('Boolean')
|
||||
.setGroup(_('Look and Feel'));
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
|
@@ -1,5 +1,6 @@
|
||||
//A simple PIXI filter doing some color changes
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Dummy effect');
|
||||
import PIXI = GlobalPIXIModule.PIXI;
|
||||
|
||||
const DummyPixiFilter = function () {
|
||||
@@ -46,7 +47,7 @@ namespace gdjs {
|
||||
// `effectData.stringParameters.someImage`
|
||||
// `effectData.stringParameters.someColor`
|
||||
// `effectData.booleanParameters.someBoolean`
|
||||
console.info(
|
||||
logger.info(
|
||||
'The PIXI texture found for the Dummy Effect (not actually used):',
|
||||
(layer
|
||||
.getRuntimeScene()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Dummy behavior');
|
||||
|
||||
/**
|
||||
* The DummyRuntimeBehavior changes a variable in the object that is owning
|
||||
* it, at every tick before events are run, to set it to the string that was
|
||||
@@ -20,7 +22,7 @@ namespace gdjs {
|
||||
this._textToSet = behaviorData.property1;
|
||||
|
||||
// You can also run arbitrary code at the creation of the behavior:
|
||||
console.log('DummyRuntimeBehavior was created for object:', owner);
|
||||
logger.log('DummyRuntimeBehavior was created for object:', owner);
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Dummy object');
|
||||
|
||||
/**
|
||||
* A dummy object doing showing a text on screen.
|
||||
* @ignore
|
||||
@@ -126,9 +128,9 @@ namespace gdjs {
|
||||
* A dummy method that can be called from events
|
||||
*/
|
||||
myMethod(number1: float, text1: string) {
|
||||
console.log('Congrats, this method was called on a DummyRuntimeObject');
|
||||
console.log('Here is the object:', this);
|
||||
console.log('Here are the arguments passed from events:', number1, text1);
|
||||
logger.log('Congrats, this method was called on a DummyRuntimeObject');
|
||||
logger.log('Here is the object:', this);
|
||||
logger.log('Here are the arguments passed from events:', number1, text1);
|
||||
}
|
||||
}
|
||||
gdjs.registerObject(
|
||||
|
@@ -1,4 +1,5 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Dummy behavior (with shared data)');
|
||||
export class DummyWithSharedDataRuntimeBehavior extends gdjs.RuntimeBehavior {
|
||||
_textToSet: string;
|
||||
|
||||
@@ -20,11 +21,11 @@ namespace gdjs {
|
||||
this._textToSet = (sharedData as any).sharedProperty1;
|
||||
|
||||
// You can also run arbitrary code at the creation of the behavior:
|
||||
console.log(
|
||||
logger.log(
|
||||
'DummyWithSharedDataRuntimeBehavior was created for object:',
|
||||
owner
|
||||
);
|
||||
console.log('The shared data are:', sharedData);
|
||||
logger.log('The shared data are:', sharedData);
|
||||
}
|
||||
|
||||
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
|
||||
|
@@ -1,4 +1,6 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Example extension');
|
||||
|
||||
export namespace evtTools {
|
||||
/**
|
||||
* This is an example of some functions that can be used through events.
|
||||
@@ -24,7 +26,7 @@ namespace gdjs {
|
||||
* that will be called at this moment.
|
||||
*/
|
||||
gdjs.registerRuntimeSceneLoadedCallback(function (runtimeScene) {
|
||||
console.log('A gdjs.RuntimeScene was loaded:', runtimeScene);
|
||||
logger.log('A gdjs.RuntimeScene was loaded:', runtimeScene);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -32,7 +34,7 @@ namespace gdjs {
|
||||
* that will be called at this moment.
|
||||
*/
|
||||
gdjs.registerRuntimeSceneUnloadedCallback(function (runtimeScene) {
|
||||
console.log('A gdjs.RuntimeScene was unloaded:', runtimeScene);
|
||||
logger.log('A gdjs.RuntimeScene was unloaded:', runtimeScene);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -42,7 +44,7 @@ namespace gdjs {
|
||||
runtimeScene,
|
||||
runtimeObject
|
||||
) {
|
||||
console.log(
|
||||
logger.log(
|
||||
'A gdjs.RuntimeObject was deleted from a gdjs.RuntimeScene:',
|
||||
runtimeScene,
|
||||
runtimeObject
|
||||
@@ -50,7 +52,7 @@ namespace gdjs {
|
||||
});
|
||||
|
||||
// Finally, note that you can also simply run code here. Most of the time you shouldn't need it though.
|
||||
console.log(
|
||||
logger.log(
|
||||
'gdjs.exampleJsExtension was created, with myGlobalString containing:' +
|
||||
myGlobalString
|
||||
);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Facebook instant games');
|
||||
export namespace evtTools {
|
||||
export namespace facebookInstantGames {
|
||||
export let _preloadedInterstitial: any = null;
|
||||
@@ -153,12 +154,12 @@ namespace gdjs {
|
||||
.then(function () {
|
||||
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoading = false;
|
||||
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoaded = true;
|
||||
console.info('Facebook Instant Games interstitial preloaded.');
|
||||
logger.info('Facebook Instant Games interstitial preloaded.');
|
||||
})
|
||||
.catch(function (err) {
|
||||
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoading = false;
|
||||
gdjs.evtTools.facebookInstantGames._preloadedInterstitialLoaded = false;
|
||||
console.error('Interstitial failed to preload: ' + err.message);
|
||||
logger.error('Interstitial failed to preload: ' + err.message);
|
||||
errorVariable.setString(err.message || 'Unknown error');
|
||||
});
|
||||
};
|
||||
@@ -173,10 +174,10 @@ namespace gdjs {
|
||||
gdjs.evtTools.facebookInstantGames._preloadedInterstitial
|
||||
.showAsync()
|
||||
.then(function () {
|
||||
console.info('Facebook Instant Games interstitial shown.');
|
||||
logger.info('Facebook Instant Games interstitial shown.');
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error('Interstitial failed to show: ' + err.message);
|
||||
logger.error('Interstitial failed to show: ' + err.message);
|
||||
errorVariable.setString(err.message || 'Unknown error');
|
||||
})
|
||||
.then(function () {
|
||||
@@ -207,12 +208,12 @@ namespace gdjs {
|
||||
.then(function () {
|
||||
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoading = false;
|
||||
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoaded = true;
|
||||
console.info('Facebook Instant Games rewarded video preloaded.');
|
||||
logger.info('Facebook Instant Games rewarded video preloaded.');
|
||||
})
|
||||
.catch(function (err) {
|
||||
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoading = false;
|
||||
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoaded = false;
|
||||
console.error('Rewarded video failed to preload: ' + err.message);
|
||||
logger.error('Rewarded video failed to preload: ' + err.message);
|
||||
errorVariable.setString(err.message || 'Unknown error');
|
||||
});
|
||||
};
|
||||
@@ -227,10 +228,10 @@ namespace gdjs {
|
||||
gdjs.evtTools.facebookInstantGames._preloadedRewardedVideo
|
||||
.showAsync()
|
||||
.then(function () {
|
||||
console.info('Facebook Instant Games rewarded video shown.');
|
||||
logger.info('Facebook Instant Games rewarded video shown.');
|
||||
})
|
||||
.catch(function (err) {
|
||||
console.error('Rewarded video failed to show: ' + err.message);
|
||||
logger.error('Rewarded video failed to show: ' + err.message);
|
||||
errorVariable.setString(err.message || 'Unknown error');
|
||||
})
|
||||
.then(function () {
|
||||
@@ -242,7 +243,7 @@ namespace gdjs {
|
||||
return gdjs.evtTools.facebookInstantGames._preloadedRewardedVideoLoaded;
|
||||
};
|
||||
if (typeof FBInstant === 'undefined' && typeof window !== 'undefined') {
|
||||
console.log('Creating a mocked version of Facebook Instant Games.');
|
||||
logger.log('Creating a mocked version of Facebook Instant Games.');
|
||||
|
||||
/**
|
||||
* A mocked Leaderboard, part of the mock of FBInstant.
|
||||
@@ -295,7 +296,7 @@ namespace gdjs {
|
||||
|
||||
showAsync(): Promise<void> {
|
||||
if (this._isLoaded) {
|
||||
console.info(
|
||||
logger.info(
|
||||
'In a real Instant Game, a video reward should have been shown to the user.'
|
||||
);
|
||||
return Promise.resolve();
|
||||
@@ -318,7 +319,7 @@ namespace gdjs {
|
||||
|
||||
showAsync(): Promise<void> {
|
||||
if (this._isLoaded) {
|
||||
console.info(
|
||||
logger.info(
|
||||
'In a real Instant Game, an interstitial should have been shown to the user.'
|
||||
);
|
||||
return Promise.resolve();
|
||||
|
@@ -415,6 +415,7 @@ module.exports = {
|
||||
_('Filesystem/Windows, Linux, MacOS'),
|
||||
'JsPlatform/Extensions/filesystem_folder32.png'
|
||||
)
|
||||
.addCodeOnlyParameter('currentScene', '')
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/FileSystem/filesystemtools.js')
|
||||
.setFunctionName('gdjs.fileSystem.getUserHomePath');
|
||||
|
@@ -1,4 +1,5 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Filesystem');
|
||||
export namespace fileSystem {
|
||||
// The Node.js path module, or null if it can't be loaded.
|
||||
export let _path: any = null;
|
||||
@@ -149,7 +150,9 @@ namespace gdjs {
|
||||
* Get the path to the user's home folder (on Windows `C:\Users\<USERNAME>\` for example).
|
||||
* @return The path to user's "home" folder
|
||||
*/
|
||||
export const getUserHomePath = function (runtimeScene): string {
|
||||
export const getUserHomePath = function (
|
||||
runtimeScene: gdjs.RuntimeScene
|
||||
): string {
|
||||
const electron = runtimeScene.getGame().getRenderer().getElectron();
|
||||
if (electron) {
|
||||
return electron.remote.app.getPath('home') || '';
|
||||
@@ -203,7 +206,7 @@ namespace gdjs {
|
||||
fileSystem.mkdirSync(directory);
|
||||
result = 'ok';
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to create directory at: '" + directory + "': ",
|
||||
err
|
||||
);
|
||||
@@ -228,7 +231,7 @@ namespace gdjs {
|
||||
fileSystem.writeFile(savePath, text, 'utf8', (err) => {
|
||||
resultVar.setString('ok');
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to save the text to path: '" + savePath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -256,7 +259,7 @@ namespace gdjs {
|
||||
fileSystem.writeFileSync(savePath, text, 'utf8');
|
||||
result = 'ok';
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to save the text to path: '" + savePath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -287,7 +290,7 @@ namespace gdjs {
|
||||
);
|
||||
result = 'ok';
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to save the variable to path: '" + savePath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -316,7 +319,7 @@ namespace gdjs {
|
||||
(err) => {
|
||||
resultVar.setString('ok');
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to save the variable to path: '" + savePath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -348,7 +351,7 @@ namespace gdjs {
|
||||
result = 'ok';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load the file at path: '" + loadPath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -378,7 +381,7 @@ namespace gdjs {
|
||||
result = 'ok';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load variable from the file at path: '" +
|
||||
loadPath +
|
||||
"': ",
|
||||
@@ -408,7 +411,7 @@ namespace gdjs {
|
||||
resultVar.setString('ok');
|
||||
}
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load variable from the file at path: '" +
|
||||
loadPath +
|
||||
"': ",
|
||||
@@ -439,7 +442,7 @@ namespace gdjs {
|
||||
resultVar.setString('ok');
|
||||
}
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load the file at path: '" + loadPath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -465,7 +468,7 @@ namespace gdjs {
|
||||
fileSystem.unlinkSync(filePath);
|
||||
result = 'ok';
|
||||
} catch (err) {
|
||||
console.error("Unable to delete the file: '" + filePath + "': ", err);
|
||||
logger.error("Unable to delete the file: '" + filePath + "': ", err);
|
||||
result = 'error';
|
||||
}
|
||||
}
|
||||
@@ -486,7 +489,7 @@ namespace gdjs {
|
||||
fileSystem.unlink(filePath, (err) => {
|
||||
resultVar.setString('ok');
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to delete the file: '" + filePath + "': ",
|
||||
err
|
||||
);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Firebase (setup)');
|
||||
export namespace evtTools {
|
||||
/**
|
||||
* Firebase Event Tools
|
||||
@@ -23,7 +24,7 @@ namespace gdjs {
|
||||
.getExtensionProperty('Firebase', 'FirebaseConfig')
|
||||
);
|
||||
} catch (e) {
|
||||
console.error('The Firebase configuration is invalid! Error: ' + e);
|
||||
logger.error('The Firebase configuration is invalid! Error: ' + e);
|
||||
return;
|
||||
}
|
||||
if (typeof firebaseConfig !== 'object') return;
|
||||
|
@@ -5,12 +5,7 @@ namespace gdjs {
|
||||
_obstacleRBush: any;
|
||||
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
this._obstacleRBush = new rbush(9, [
|
||||
'.owner.getAABB().min[0]',
|
||||
'.owner.getAABB().min[1]',
|
||||
'.owner.getAABB().max[0]',
|
||||
'.owner.getAABB().max[1]',
|
||||
]);
|
||||
this._obstacleRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,7 +30,10 @@ namespace gdjs {
|
||||
* Add a light obstacle to the list of existing obstacles.
|
||||
*/
|
||||
addObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) {
|
||||
this._obstacleRBush.insert(obstacle);
|
||||
if (obstacle.currentRBushAABB)
|
||||
obstacle.currentRBushAABB.updateAABBFromOwner();
|
||||
else obstacle.currentRBushAABB = new gdjs.BehaviorRBushAABB(obstacle);
|
||||
this._obstacleRBush.insert(obstacle.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -43,7 +41,7 @@ namespace gdjs {
|
||||
* added before.
|
||||
*/
|
||||
removeObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) {
|
||||
this._obstacleRBush.remove(obstacle);
|
||||
this._obstacleRBush.remove(obstacle.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -55,7 +53,7 @@ namespace gdjs {
|
||||
getAllObstaclesAround(
|
||||
object: gdjs.RuntimeObject,
|
||||
radius: number,
|
||||
result: gdjs.LightObstacleRuntimeBehavior[]
|
||||
result: gdjs.BehaviorRBushAABB<gdjs.LightObstacleRuntimeBehavior>[]
|
||||
) {
|
||||
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
|
||||
// is not necessarily in the middle of the object (for sprites for example).
|
||||
@@ -83,15 +81,22 @@ namespace gdjs {
|
||||
_oldY: float = 0;
|
||||
_oldWidth: float = 0;
|
||||
_oldHeight: float = 0;
|
||||
currentRBushAABB: gdjs.BehaviorRBushAABB<
|
||||
LightObstacleRuntimeBehavior
|
||||
> | null = null;
|
||||
_manager: any;
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
constructor(runtimeScene, behaviorData, owner) {
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
behaviorData,
|
||||
owner: gdjs.RuntimeObject
|
||||
) {
|
||||
super(runtimeScene, behaviorData, owner);
|
||||
this._manager = LightObstaclesManager.getManager(runtimeScene);
|
||||
}
|
||||
|
||||
doStepPreEvents(runtimeScene) {
|
||||
doStepPreEvents(runtimeScene: gdjs.RuntimeScene) {
|
||||
// Make sure the obstacle is or is not in the obstacles manager.
|
||||
if (!this.activated() && this._registeredInManager) {
|
||||
this._manager.removeObstacle(this);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Light object');
|
||||
import PIXI = GlobalPIXIModule.PIXI;
|
||||
|
||||
/**
|
||||
@@ -7,19 +8,24 @@ namespace gdjs {
|
||||
export class LightRuntimeObjectPixiRenderer {
|
||||
_object: gdjs.LightRuntimeObject;
|
||||
_runtimeScene: gdjs.RuntimeScene;
|
||||
_manager: any;
|
||||
_manager: gdjs.LightObstaclesManager;
|
||||
_radius: number;
|
||||
_color: any;
|
||||
_color: [number, number, number];
|
||||
_texture: PIXI.Texture | null = null;
|
||||
_center: any;
|
||||
_defaultVertexBuffer: any;
|
||||
_vertexBuffer: any;
|
||||
_indexBuffer: any;
|
||||
_center: Float32Array;
|
||||
_defaultVertexBuffer: Float32Array;
|
||||
_vertexBuffer: Float32Array;
|
||||
_indexBuffer: Uint16Array;
|
||||
_light: PIXI.Mesh<PIXI.Shader> | null = null;
|
||||
_isPreview: boolean;
|
||||
_debugMode: any = null;
|
||||
_debugMode: boolean = false;
|
||||
_debugLight: PIXI.Container | null = null;
|
||||
_debugGraphics: PIXI.Graphics | null = null;
|
||||
|
||||
/**
|
||||
* A polygon updated when vertices of the light are computed
|
||||
* to be a polygon bounding the light and its obstacles.
|
||||
*/
|
||||
_lightBoundingPoly: gdjs.Polygon;
|
||||
|
||||
constructor(
|
||||
@@ -52,13 +58,8 @@ namespace gdjs {
|
||||
this._indexBuffer = new Uint16Array([0, 1, 2, 0, 2, 3]);
|
||||
this.updateMesh();
|
||||
this._isPreview = runtimeScene.getGame().isPreview();
|
||||
this._lightBoundingPoly = gdjs.Polygon.createRectangle(0, 0);
|
||||
|
||||
this._lightBoundingPoly = new gdjs.Polygon();
|
||||
for (let i = 0; i < 4; i++) {
|
||||
this._lightBoundingPoly.vertices.push(
|
||||
runtimeObject.getHitBoxes()[0].vertices[i]
|
||||
);
|
||||
}
|
||||
this.updateDebugMode();
|
||||
|
||||
// Objects will be added in lighting layer, this is just to maintain consistency.
|
||||
@@ -84,10 +85,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
static _computeClosestIntersectionPoint(
|
||||
lightObject,
|
||||
angle,
|
||||
polygons,
|
||||
boundingSquareHalfDiag
|
||||
lightObject: gdjs.LightRuntimeObject,
|
||||
angle: float,
|
||||
polygons: Array<gdjs.Polygon>,
|
||||
boundingSquareHalfDiag: float
|
||||
) {
|
||||
const centerX = lightObject.getX();
|
||||
const centerY = lightObject.getY();
|
||||
@@ -134,7 +135,7 @@ namespace gdjs {
|
||||
|
||||
updateMesh(): void {
|
||||
if (!PIXI.utils.isWebGLSupported()) {
|
||||
console.warn(
|
||||
logger.warn(
|
||||
'This device does not support webgl, which is required for Lighting Extension.'
|
||||
);
|
||||
return;
|
||||
@@ -307,8 +308,8 @@ namespace gdjs {
|
||||
// and instead use a subarray. Otherwise, allocate new array buffers as
|
||||
// there would be memory wastage.
|
||||
let isSubArrayUsed = false;
|
||||
let vertexBufferSubArray = null;
|
||||
let indexBufferSubArray = null;
|
||||
let vertexBufferSubArray: Float32Array | null = null;
|
||||
let indexBufferSubArray: Uint16Array | null = null;
|
||||
if (this._vertexBuffer.length > 2 * verticesCount + 2) {
|
||||
if (this._vertexBuffer.length < 4 * verticesCount + 4) {
|
||||
isSubArrayUsed = true;
|
||||
@@ -367,8 +368,10 @@ namespace gdjs {
|
||||
* Computes the vertices of mesh using raycasting.
|
||||
* @returns the vertices of mesh.
|
||||
*/
|
||||
_computeLightVertices(): Array<any> {
|
||||
const lightObstacles: Array<gdjs.LightObstacleRuntimeBehavior> = [];
|
||||
_computeLightVertices(): Array<FloatPoint> {
|
||||
const lightObstacles: gdjs.BehaviorRBushAABB<
|
||||
LightObstacleRuntimeBehavior
|
||||
>[] = [];
|
||||
if (this._manager) {
|
||||
this._manager.getAllObstaclesAround(
|
||||
this._object,
|
||||
@@ -376,38 +379,47 @@ namespace gdjs {
|
||||
lightObstacles
|
||||
);
|
||||
}
|
||||
const searchAreaLeft = this._object.getX() - this._radius;
|
||||
const searchAreaTop = this._object.getY() - this._radius;
|
||||
const searchAreaRight = this._object.getX() + this._radius;
|
||||
const searchAreaBottom = this._object.getY() + this._radius;
|
||||
|
||||
// Bail out early if there are no obstacles.
|
||||
if (lightObstacles.length === 0) {
|
||||
// @ts-ignore TODO the array should probably be pass as a parameter.
|
||||
return lightObstacles;
|
||||
}
|
||||
|
||||
// Synchronize light bounding polygon with the hitbox.
|
||||
const lightHitboxPoly = this._object.getHitBoxes()[0];
|
||||
// Note: we suppose the hitbox is always a single rectangle.
|
||||
const objectHitBox = this._object.getHitBoxes()[0];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
for (let j = 0; j < 2; j++) {
|
||||
this._lightBoundingPoly.vertices[i][j] =
|
||||
lightHitboxPoly.vertices[i][j];
|
||||
}
|
||||
this._lightBoundingPoly.vertices[i][0] = objectHitBox.vertices[i][0];
|
||||
this._lightBoundingPoly.vertices[i][1] = objectHitBox.vertices[i][1];
|
||||
}
|
||||
const obstaclesCount = lightObstacles.length;
|
||||
const obstacleHitBoxes = new Array(obstaclesCount);
|
||||
for (let i = 0; i < obstaclesCount; i++) {
|
||||
obstacleHitBoxes[i] = lightObstacles[i].owner.getHitBoxes();
|
||||
}
|
||||
const obstaclePolygons: Array<any> = [];
|
||||
|
||||
// Create the list of polygons to compute the light vertices
|
||||
const obstaclePolygons: Array<gdjs.Polygon> = [];
|
||||
obstaclePolygons.push(this._lightBoundingPoly);
|
||||
for (let i = 0; i < obstaclesCount; i++) {
|
||||
const noOfHitBoxes = obstacleHitBoxes[i].length;
|
||||
for (let j = 0; j < noOfHitBoxes; j++) {
|
||||
obstaclePolygons.push(obstacleHitBoxes[i][j]);
|
||||
for (let i = 0; i < lightObstacles.length; i++) {
|
||||
const obstacleHitBoxes = lightObstacles[
|
||||
i
|
||||
].behavior.owner.getHitBoxesAround(
|
||||
searchAreaLeft,
|
||||
searchAreaTop,
|
||||
searchAreaRight,
|
||||
searchAreaBottom
|
||||
);
|
||||
for (const hitbox of obstacleHitBoxes) {
|
||||
obstaclePolygons.push(hitbox);
|
||||
}
|
||||
}
|
||||
|
||||
let maxX = this._object.x + this._radius;
|
||||
let minX = this._object.x - this._radius;
|
||||
let maxY = this._object.y + this._radius;
|
||||
let minY = this._object.y - this._radius;
|
||||
const flattenVertices: Array<any> = [];
|
||||
const flattenVertices: Array<FloatPoint> = [];
|
||||
for (let i = 1; i < obstaclePolygons.length; i++) {
|
||||
const vertices = obstaclePolygons[i].vertices;
|
||||
const verticesCount = vertices.length;
|
||||
@@ -449,6 +461,7 @@ namespace gdjs {
|
||||
(maxY - this._object.y) * (maxY - this._object.y)
|
||||
)
|
||||
);
|
||||
// Add this._object.hitBoxes vertices.
|
||||
for (let i = 0; i < 4; i++) {
|
||||
flattenVertices.push(obstaclePolygons[0].vertices[i]);
|
||||
}
|
||||
@@ -543,9 +556,11 @@ namespace gdjs {
|
||||
varying vec2 vPos;
|
||||
|
||||
void main() {
|
||||
vec2 topleft = vec2(center.x - radius, center.y - radius);
|
||||
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
|
||||
gl_FragColor = vec4(color, 1.0) * texture2D(uSampler, texCoord);
|
||||
vec2 topleft = vec2(center.x - radius, center.y - radius);
|
||||
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
|
||||
gl_FragColor = (texCoord.x > 0.0 && texCoord.x < 1.0 && texCoord.y > 0.0 && texCoord.y < 1.0)
|
||||
? vec4(color, 1.0) * texture2D(uSampler, texCoord)
|
||||
: vec4(0.0, 0.0, 0.0, 0.0);
|
||||
}`;
|
||||
}
|
||||
|
||||
|
@@ -149,10 +149,10 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the light obstacles manager if objects with the behavior exist, null otherwise.
|
||||
* @returns gdjs.LightObstaclesManager if it exists, otherwise null.
|
||||
* Get the light obstacles manager.
|
||||
* @returns the light obstacles manager.
|
||||
*/
|
||||
getObstaclesManager(): gdjs.LightObstaclesManager | null {
|
||||
getObstaclesManager(): gdjs.LightObstaclesManager {
|
||||
return this._obstaclesManager;
|
||||
}
|
||||
|
||||
|
@@ -47,8 +47,9 @@ namespace gdjs {
|
||||
/**
|
||||
* @returns an iterable on every object linked with objA.
|
||||
*/
|
||||
// : Iterable<gdjs.RuntimeObject> in practice
|
||||
getObjectsLinkedWith(objA: gdjs.RuntimeObject) {
|
||||
getObjectsLinkedWith(
|
||||
objA: gdjs.RuntimeObject
|
||||
): Iterable<gdjs.RuntimeObject> {
|
||||
if (!this._links.has(objA.id)) {
|
||||
this._links.set(objA.id, new IterableLinkedObjects());
|
||||
}
|
||||
@@ -149,19 +150,20 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
class IterableLinkedObjects {
|
||||
class IterableLinkedObjects implements Iterable<gdjs.RuntimeObject> {
|
||||
linkedObjectMap: Map<string, gdjs.RuntimeObject[]>;
|
||||
static emptyItr: Iterator<gdjs.RuntimeObject> = {
|
||||
next: () => ({ value: undefined, done: true }),
|
||||
};
|
||||
|
||||
constructor() {
|
||||
this.linkedObjectMap = new Map<string, gdjs.RuntimeObject[]>();
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
let mapItr = this.linkedObjectMap.entries();
|
||||
let listItr: IterableIterator<[
|
||||
number,
|
||||
gdjs.RuntimeObject
|
||||
]> = [].entries();
|
||||
let mapItr = this.linkedObjectMap.values();
|
||||
let listItr: Iterator<gdjs.RuntimeObject> =
|
||||
IterableLinkedObjects.emptyItr;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
@@ -169,15 +171,12 @@ namespace gdjs {
|
||||
while (listNext.done) {
|
||||
const mapNext = mapItr.next();
|
||||
if (mapNext.done) {
|
||||
// IteratorReturnResult<gdjs.RuntimeObject> require a defined value
|
||||
// even though the spec state otherwise.
|
||||
// So, this class can't be typed as an iterable.
|
||||
return { value: undefined, done: true };
|
||||
return listNext;
|
||||
}
|
||||
listItr = mapNext.value[1].entries();
|
||||
listItr = mapNext.value[Symbol.iterator]();
|
||||
listNext = listItr.next();
|
||||
}
|
||||
return { value: listNext.value[1], done: false };
|
||||
return listNext;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
1
Extensions/P2P/A_peer.js
vendored
1
Extensions/P2P/A_peer.js
vendored
@@ -69,3 +69,4 @@ var t=require("./bufferbuilder").BufferBuilder,e=require("./bufferbuilder").bina
|
||||
},{"eventemitter3":"JJlS","./util":"BHXf","./logger":"WOs9","./socket":"wJlv","./mediaconnection":"dbHP","./dataconnection":"GBTQ","./enums":"ZRYf","./api":"in7L"}],"iTK6":[function(require,module,exports) {
|
||||
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("./util"),r=require("./peer");exports.peerjs={Peer:r.Peer,util:e.util},exports.default=r.Peer,window.peerjs=exports.peerjs,window.Peer=r.Peer;
|
||||
},{"./util":"BHXf","./peer":"Hxpd"}]},{},["iTK6"], null)
|
||||
//# sourceMappingURL=A_peer.js.map
|
1
Extensions/P2P/A_peer.js.map
Normal file
1
Extensions/P2P/A_peer.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -375,15 +375,14 @@ namespace gdjs {
|
||||
key: string,
|
||||
ssl: boolean
|
||||
) => {
|
||||
peerConfig = {
|
||||
debug: 1,
|
||||
Object.assign(peerConfig, {
|
||||
host,
|
||||
port,
|
||||
path,
|
||||
secure: ssl,
|
||||
// All servers have "peerjs" as default key
|
||||
key: key.length === 0 ? 'peerjs' : key,
|
||||
};
|
||||
});
|
||||
loadPeerJS();
|
||||
};
|
||||
|
||||
@@ -394,6 +393,26 @@ namespace gdjs {
|
||||
*/
|
||||
export const useDefaultBrokerServer = loadPeerJS;
|
||||
|
||||
/**
|
||||
* Adds an ICE server candidate, and removes the default ones provided by PeerJs. Must be called before connecting to a broker.
|
||||
* @param urls The URL of the STUN/TURN server.
|
||||
* @param username An optional username to send to the server.
|
||||
* @param credential An optional password to send to the server.
|
||||
*/
|
||||
export const useCustomICECandidate = (
|
||||
urls: string,
|
||||
username?: string,
|
||||
credential?: string
|
||||
) => {
|
||||
peerConfig.config = peerConfig.config || {};
|
||||
peerConfig.config.iceServers = peerConfig.config.iceServers || [];
|
||||
peerConfig.config.iceServers.push({
|
||||
urls,
|
||||
username,
|
||||
credential,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Overrides the default peer ID. Must be called before connecting to a broker.
|
||||
* Overriding the ID may have unwanted consequences. Do not use this feature
|
||||
|
@@ -154,6 +154,30 @@ module.exports = {
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setFunctionName('gdjs.evtTools.p2p.useCustomBrokerServer');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'UseOwnICEServer',
|
||||
_('Use a custom ICE server'),
|
||||
_(
|
||||
'Disables the default ICE (STUN or TURN) servers list and use one of your own. ' +
|
||||
'Note that it is recommended to add at least 1 self-hosted STUN and TURN server ' +
|
||||
'for games that are not over LAN but over the internet. ' +
|
||||
'This action can be used multiple times to add multiple servers. ' +
|
||||
'This action needs to be called BEFORE connecting to the broker server.'
|
||||
),
|
||||
_('Use ICE server _PARAM0_ (username: _PARAM1_, password: _PARAM2_)'),
|
||||
_('P2P (experimental)'),
|
||||
'JsPlatform/Extensions/p2picon.svg',
|
||||
'JsPlatform/Extensions/p2picon.svg'
|
||||
)
|
||||
.addParameter('string', _('URL to the ICE server'), '', false)
|
||||
.addParameter('string', _('(Optional) Username'), '', true)
|
||||
.addParameter('string', _('(Optional) Password'), '', true)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/P2P/A_peer.js')
|
||||
.addIncludeFile('Extensions/P2P/B_p2ptools.js')
|
||||
.setFunctionName('gdjs.evtTools.p2p.useCustomICECandidate');
|
||||
|
||||
extension
|
||||
.addAction(
|
||||
'UseDefaultBroker',
|
||||
|
@@ -168,7 +168,7 @@ void ExtensionSubDeclaration2(gd::ObjectMetadata& obj) {
|
||||
.UseStandardRelationalOperatorParameters("number");
|
||||
|
||||
obj.AddAction("ParticleSize1",
|
||||
_("SIze, parameter 1"),
|
||||
_("Size, parameter 1"),
|
||||
_("Modify parameter 1 of the size of particles"),
|
||||
_("the parameter 1 of size"),
|
||||
_("Common"),
|
||||
|
@@ -39,23 +39,23 @@ std::map<gd::String, gd::PropertyDescriptor> PathfindingBehavior::GetProperties(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
|
||||
properties[_("Max. speed")].SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
|
||||
properties[_("Rotate speed")].SetValue(
|
||||
properties[_("Rotate speed")].SetGroup(_("Rotation")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angularMaxSpeed")));
|
||||
properties[_("Rotate object")]
|
||||
properties[_("Rotate object")].SetGroup(_("Rotation"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("rotateObject") ? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Angle offset")].SetValue(
|
||||
properties[_("Angle offset")].SetGroup(_("Rotation")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("angleOffset")));
|
||||
properties[_("Virtual cell width")].SetValue(
|
||||
properties[_("Virtual cell width")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("cellWidth", 0)));
|
||||
properties[_("Virtual cell height")].SetValue(
|
||||
properties[_("Virtual cell height")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("cellHeight", 0)));
|
||||
properties[_("Virtual grid X offset")].SetValue(
|
||||
properties[_("Virtual grid X offset")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetX", 0)));
|
||||
properties[_("Virtual grid Y offset")].SetValue(
|
||||
properties[_("Virtual grid Y offset")].SetGroup(_("Virtual Grid")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("gridOffsetY", 0)));
|
||||
properties[_("Extra border size")].SetValue(
|
||||
properties[_("Extra border size")].SetGroup(_("Collision")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("extraBorder")));
|
||||
|
||||
return properties;
|
||||
|
@@ -19,12 +19,7 @@ namespace gdjs {
|
||||
* @param object The object
|
||||
*/
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
this._obstaclesRBush = new rbush(9, [
|
||||
'.owner.getAABB().min[0]',
|
||||
'.owner.getAABB().min[1]',
|
||||
'.owner.getAABB().max[0]',
|
||||
'.owner.getAABB().max[1]',
|
||||
]);
|
||||
this._obstaclesRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +41,14 @@ namespace gdjs {
|
||||
addObstacle(
|
||||
pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior
|
||||
) {
|
||||
this._obstaclesRBush.insert(pathfindingObstacleBehavior);
|
||||
if (pathfindingObstacleBehavior.currentRBushAABB)
|
||||
pathfindingObstacleBehavior.currentRBushAABB.updateAABBFromOwner();
|
||||
else
|
||||
pathfindingObstacleBehavior.currentRBushAABB = new gdjs.BehaviorRBushAABB(
|
||||
pathfindingObstacleBehavior
|
||||
);
|
||||
|
||||
this._obstaclesRBush.insert(pathfindingObstacleBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,7 +58,7 @@ namespace gdjs {
|
||||
removeObstacle(
|
||||
pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior
|
||||
) {
|
||||
this._obstaclesRBush.remove(pathfindingObstacleBehavior);
|
||||
this._obstaclesRBush.remove(pathfindingObstacleBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,7 +70,7 @@ namespace gdjs {
|
||||
x: float,
|
||||
y: float,
|
||||
radius: float,
|
||||
result: gdjs.PathfindingObstacleRuntimeBehavior[]
|
||||
result: gdjs.BehaviorRBushAABB<gdjs.PathfindingObstacleRuntimeBehavior>[]
|
||||
): any {
|
||||
const searchArea = gdjs.staticObject(
|
||||
PathfindingObstaclesManager.prototype.getAllObstaclesAround
|
||||
@@ -100,6 +102,9 @@ namespace gdjs {
|
||||
_oldHeight: float = 0;
|
||||
_manager: PathfindingObstaclesManager;
|
||||
_registeredInManager: boolean = false;
|
||||
currentRBushAABB: gdjs.BehaviorRBushAABB<
|
||||
PathfindingObstacleRuntimeBehavior
|
||||
> | null = null;
|
||||
|
||||
constructor(
|
||||
runtimeScene: gdjs.RuntimeScene,
|
||||
|
@@ -3,6 +3,7 @@ GDevelop - Pathfinding Behavior Extension
|
||||
Copyright (c) 2010-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Pathfinding behavior');
|
||||
/**
|
||||
* PathfindingRuntimeBehavior represents a behavior allowing objects to
|
||||
* follow a path computed to avoid obstacles.
|
||||
@@ -27,8 +28,8 @@ namespace gdjs {
|
||||
_pathFound: boolean = false;
|
||||
_speed: float = 0;
|
||||
_angularSpeed: float = 0;
|
||||
_timeOnSegment: float = 0;
|
||||
_totalSegmentTime: float = 0;
|
||||
_distanceOnSegment: float = 0;
|
||||
_totalSegmentDistance: float = 0;
|
||||
_currentSegment: integer = 0;
|
||||
_reachedEnd: boolean = false;
|
||||
_manager: PathfindingObstaclesManager;
|
||||
@@ -391,11 +392,11 @@ namespace gdjs {
|
||||
const pathY =
|
||||
this._path[this._currentSegment + 1][1] -
|
||||
this._path[this._currentSegment][1];
|
||||
this._totalSegmentTime = Math.sqrt(pathX * pathX + pathY * pathY);
|
||||
this._timeOnSegment = 0;
|
||||
this._totalSegmentDistance = Math.sqrt(pathX * pathX + pathY * pathY);
|
||||
this._distanceOnSegment = 0;
|
||||
this._reachedEnd = false;
|
||||
this._movementAngle =
|
||||
((Math.atan2(pathY, pathX) * 180) / Math.PI + 360) % 360;
|
||||
(gdjs.toDegrees(Math.atan2(pathY, pathX)) + 360) % 360;
|
||||
} else {
|
||||
this._reachedEnd = true;
|
||||
this._speed = 0;
|
||||
@@ -407,58 +408,59 @@ namespace gdjs {
|
||||
return;
|
||||
}
|
||||
|
||||
//Update the speed of the object
|
||||
// Update the speed of the object
|
||||
const timeDelta = this.owner.getElapsedTime(runtimeScene) / 1000;
|
||||
this._speed += this._acceleration * timeDelta;
|
||||
if (this._speed > this._maxSpeed) {
|
||||
this._speed = this._maxSpeed;
|
||||
const previousSpeed = this._speed;
|
||||
if (this._speed !== this._maxSpeed) {
|
||||
this._speed += this._acceleration * timeDelta;
|
||||
if (this._speed > this._maxSpeed) {
|
||||
this._speed = this._maxSpeed;
|
||||
}
|
||||
}
|
||||
this._angularSpeed = this._angularMaxSpeed;
|
||||
|
||||
//Update the time on the segment and change segment if needed
|
||||
this._timeOnSegment += this._speed * timeDelta;
|
||||
// Update the time on the segment and change segment if needed
|
||||
// Use a Verlet integration to be frame rate independent.
|
||||
this._distanceOnSegment +=
|
||||
((this._speed + previousSpeed) / 2) * timeDelta;
|
||||
const remainingDistanceOnSegment =
|
||||
this._totalSegmentDistance - this._distanceOnSegment;
|
||||
if (
|
||||
this._timeOnSegment >= this._totalSegmentTime &&
|
||||
remainingDistanceOnSegment <= 0 &&
|
||||
this._currentSegment < this._path.length
|
||||
) {
|
||||
this._enterSegment(this._currentSegment + 1);
|
||||
this._distanceOnSegment = -remainingDistanceOnSegment;
|
||||
}
|
||||
|
||||
//Position object on the segment and update its angle
|
||||
// Position object on the segment and update its angle
|
||||
let newPos = [0, 0];
|
||||
let pathAngle = this.owner.getAngle();
|
||||
if (this._currentSegment < this._path.length - 1) {
|
||||
newPos[0] = gdjs.evtTools.common.lerp(
|
||||
this._path[this._currentSegment][0],
|
||||
this._path[this._currentSegment + 1][0],
|
||||
this._timeOnSegment / this._totalSegmentTime
|
||||
this._distanceOnSegment / this._totalSegmentDistance
|
||||
);
|
||||
newPos[1] = gdjs.evtTools.common.lerp(
|
||||
this._path[this._currentSegment][1],
|
||||
this._path[this._currentSegment + 1][1],
|
||||
this._timeOnSegment / this._totalSegmentTime
|
||||
this._distanceOnSegment / this._totalSegmentDistance
|
||||
);
|
||||
pathAngle =
|
||||
gdjs.toDegrees(
|
||||
Math.atan2(
|
||||
this._path[this._currentSegment + 1][1] -
|
||||
this._path[this._currentSegment][1],
|
||||
this._path[this._currentSegment + 1][0] -
|
||||
this._path[this._currentSegment][0]
|
||||
)
|
||||
) + this._angleOffset;
|
||||
if (
|
||||
this._rotateObject &&
|
||||
this.owner.getAngle() !== this._movementAngle + this._angleOffset
|
||||
) {
|
||||
this.owner.rotateTowardAngle(
|
||||
this._movementAngle + this._angleOffset,
|
||||
this._angularSpeed,
|
||||
runtimeScene
|
||||
);
|
||||
}
|
||||
} else {
|
||||
newPos = this._path[this._path.length - 1];
|
||||
}
|
||||
this.owner.setX(newPos[0]);
|
||||
this.owner.setY(newPos[1]);
|
||||
if (this._rotateObject) {
|
||||
this.owner.rotateTowardAngle(
|
||||
pathAngle,
|
||||
this._angularSpeed,
|
||||
runtimeScene
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
doStepPostEvents(runtimeScene: gdjs.RuntimeScene) {}
|
||||
@@ -542,7 +544,9 @@ namespace gdjs {
|
||||
//An array of nodes sorted by their estimate cost (First node = Lower estimate cost).
|
||||
_openNodes: Node[] = [];
|
||||
//Used by getNodes to temporarily store obstacles near a position.
|
||||
_closeObstacles: PathfindingObstacleRuntimeBehavior[] = [];
|
||||
_closeObstacles: gdjs.BehaviorRBushAABB<
|
||||
PathfindingObstacleRuntimeBehavior
|
||||
>[] = [];
|
||||
//Old nodes constructed in a previous search are stored here to avoid temporary objects (see _freeAllNodes method).
|
||||
_nodeCache: Node[] = [];
|
||||
|
||||
@@ -612,7 +616,7 @@ namespace gdjs {
|
||||
|
||||
computePathTo(targetX: float, targetY: float) {
|
||||
if (this._obstacles === null) {
|
||||
console.log(
|
||||
logger.log(
|
||||
'You tried to compute a path without specifying the obstacles'
|
||||
);
|
||||
return;
|
||||
@@ -783,7 +787,7 @@ namespace gdjs {
|
||||
this._closeObstacles
|
||||
);
|
||||
for (let k = 0; k < this._closeObstacles.length; ++k) {
|
||||
const obj = this._closeObstacles[k].owner;
|
||||
const obj = this._closeObstacles[k].behavior.owner;
|
||||
const topLeftCellX = Math.floor(
|
||||
(obj.getDrawableX() - this._rightBorder - this._gridOffsetX) /
|
||||
this._cellWidth
|
||||
@@ -813,13 +817,13 @@ namespace gdjs {
|
||||
yPos < bottomRightCellY
|
||||
) {
|
||||
objectsOnCell = true;
|
||||
if (this._closeObstacles[k].isImpassable()) {
|
||||
if (this._closeObstacles[k].behavior.isImpassable()) {
|
||||
//The cell is impassable, stop here.
|
||||
newNode.cost = -1;
|
||||
break;
|
||||
} else {
|
||||
//Superimpose obstacles
|
||||
newNode.cost += this._closeObstacles[k].getCost();
|
||||
newNode.cost += this._closeObstacles[k].behavior.getCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
// @ts-check
|
||||
describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
describe.only('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
// tests cases where every collisionMethod has the same behavior.
|
||||
let doCommonPathFindingTests = (collisionMethod, allowDiagonals) => {
|
||||
const pathFindingName = 'auto1';
|
||||
|
||||
const createScene = () => {
|
||||
const createScene = (framePerSecond = 60) => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
// @ts-ignore - missing properties.
|
||||
@@ -39,11 +40,14 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
objects: [],
|
||||
instances: [],
|
||||
});
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return (1 / 60) * 1000;
|
||||
};
|
||||
setFramePerSecond(runtimeScene, framePerSecond);
|
||||
return runtimeScene;
|
||||
};
|
||||
const setFramePerSecond = (runtimeScene, framePerSecond) => {
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return 1000 / framePerSecond;
|
||||
};
|
||||
};
|
||||
|
||||
const addPlayer = (runtimeScene) => {
|
||||
const player = new gdjs.RuntimeObject(runtimeScene, {
|
||||
@@ -156,6 +160,96 @@ describe('gdjs.PathfindingRuntimeBehavior', function () {
|
||||
);
|
||||
});
|
||||
|
||||
if (allowDiagonals) {
|
||||
[20, 30, 60, 120].forEach((framePerSecond) => {
|
||||
describe(`(${framePerSecond} fps)`, function () {
|
||||
it('can move on the path at the right speed', function () {
|
||||
setFramePerSecond(runtimeScene, framePerSecond);
|
||||
const obstacle = addObstacle(runtimeScene);
|
||||
|
||||
obstacle.setPosition(600, 300);
|
||||
// To ensure obstacles are registered.
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
|
||||
player.setPosition(480, 300);
|
||||
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
|
||||
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).getNodeCount()
|
||||
).to.be.above(13);
|
||||
|
||||
// Move on the path and stop before the last 1/10 of second.
|
||||
for (let i = 0; i < (framePerSecond * 17) / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(false);
|
||||
}
|
||||
// The position is the same no matter the frame rate.
|
||||
expect(player.getX()).to.be(720);
|
||||
expect(player.getY()).to.be.within(
|
||||
288.5786437626905 - epsilon,
|
||||
288.5786437626905 + epsilon
|
||||
);
|
||||
|
||||
// Let 1/10 of second pass,
|
||||
// because the calculus interval is not the same for each case.
|
||||
for (let i = 0; i < framePerSecond / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
}
|
||||
// The destination is reached for every frame rate within 1/10 of second.
|
||||
expect(player.getX()).to.be(720);
|
||||
expect(player.getY()).to.be(300);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
[20, 30, 60, 120].forEach((framePerSecond) => {
|
||||
describe(`(${framePerSecond} fps)`, function () {
|
||||
it('can move on the path at the right speed', function () {
|
||||
setFramePerSecond(runtimeScene, framePerSecond);
|
||||
const obstacle = addObstacle(runtimeScene);
|
||||
|
||||
obstacle.setPosition(600, 300);
|
||||
// To ensure obstacles are registered.
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
|
||||
player.setPosition(480, 300);
|
||||
player.getBehavior(pathFindingName).moveTo(runtimeScene, 720, 300);
|
||||
expect(player.getBehavior(pathFindingName).pathFound()).to.be(true);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).getNodeCount()
|
||||
).to.be.above(13);
|
||||
|
||||
// Move on the path and stop before the last 1/10 of second.
|
||||
for (let i = 0; i < (framePerSecond * 20) / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(false);
|
||||
}
|
||||
expect(player.getX()).to.be(710);
|
||||
expect(player.getY()).to.be.within(300 - epsilon, 300 + epsilon);
|
||||
|
||||
// Let 1/10 of second pass,
|
||||
// because the calculus interval is not the same for each case.
|
||||
for (let i = 0; i < (framePerSecond * 1) / 10; i++) {
|
||||
runtimeScene.renderAndStep(1000 / framePerSecond);
|
||||
}
|
||||
// The destination is reached for every frame rate within 1/10 of second.
|
||||
expect(player.getX()).to.be(720);
|
||||
expect(player.getY()).to.be(300);
|
||||
expect(
|
||||
player.getBehavior(pathFindingName).destinationReached()
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('can find a path between 2 obstacles', function () {
|
||||
const obstacleTop = addObstacle(runtimeScene);
|
||||
const obstacleBottom = addObstacle(runtimeScene);
|
||||
|
@@ -500,6 +500,20 @@ void DeclarePlatformBehaviorExtension(gd::PlatformExtension& extension) {
|
||||
.MarkAsAdvanced()
|
||||
.SetFunctionName("SimulateControl");
|
||||
|
||||
aut.AddScopedCondition("IsUsingControl",
|
||||
_("Control pressed or simulated"),
|
||||
_("A control was applied from a default control or a simulated by an action."),
|
||||
_("_PARAM0_ has the _PARAM2_ key pressed or simulated"),
|
||||
_("Controls"),
|
||||
"res/conditions/keyboard24.png",
|
||||
"res/conditions/keyboard.png")
|
||||
.AddParameter("object", _("Object"))
|
||||
.AddParameter("behavior", _("Behavior"), "PlatformerObjectBehavior")
|
||||
.AddParameter("stringWithSelector",
|
||||
_("Key"),
|
||||
"[\"Left\", \"Right\", \"Jump\", \"Ladder\", \"Release Ladder\", \"Up\", \"Down\"]")
|
||||
.MarkAsAdvanced();
|
||||
|
||||
aut.AddAction("IgnoreDefaultControls",
|
||||
_("Ignore default controls"),
|
||||
_("De/activate the use of default controls.\nIf deactivated, "
|
||||
|
@@ -147,6 +147,8 @@ class PlatformBehaviorJsExtension : public gd::PlatformExtension {
|
||||
"simulateReleasePlatformKey");
|
||||
autActions["PlatformBehavior::SimulateControl"].SetFunctionName(
|
||||
"simulateControl");
|
||||
autConditions["PlatformBehavior::PlatformerObjectBehavior::IsUsingControl"].SetFunctionName(
|
||||
"isUsingControl");
|
||||
autActions["PlatformBehavior::IgnoreDefaultControls"].SetFunctionName(
|
||||
"ignoreDefaultControls");
|
||||
}
|
||||
|
@@ -42,12 +42,12 @@ std::map<gd::String, gd::PropertyDescriptor> PlatformBehavior::GetProperties(
|
||||
.AddExtraInfo(_("Platform"))
|
||||
.AddExtraInfo(_("Jumpthru platform"))
|
||||
.AddExtraInfo(_("Ladder"));
|
||||
properties[_("Ledges can be grabbed")]
|
||||
properties[_("Ledges can be grabbed")].SetGroup(_("Ledge"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("canBeGrabbed", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Grab offset on Y axis")].SetValue(
|
||||
properties[_("Grab offset on Y axis")].SetGroup(_("Ledge")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
|
||||
|
||||
return properties;
|
||||
|
@@ -20,7 +20,6 @@ This project is released under the MIT License.
|
||||
|
||||
void PlatformerObjectBehavior::InitializeContent(
|
||||
gd::SerializerElement& behaviorContent) {
|
||||
behaviorContent.SetAttribute("roundCoordinates", true);
|
||||
behaviorContent.SetAttribute("gravity", 1000);
|
||||
behaviorContent.SetAttribute("maxFallingSpeed", 700);
|
||||
behaviorContent.SetAttribute("ladderClimbingSpeed", 150);
|
||||
@@ -34,6 +33,7 @@ void PlatformerObjectBehavior::InitializeContent(
|
||||
behaviorContent.SetAttribute("canGrabPlatforms", false);
|
||||
behaviorContent.SetAttribute("yGrabOffset", 0);
|
||||
behaviorContent.SetAttribute("xGrabTolerance", 10);
|
||||
behaviorContent.SetAttribute("useLegacyTrajectory", false);
|
||||
}
|
||||
|
||||
#if defined(GD_IDE_ONLY)
|
||||
@@ -42,11 +42,11 @@ PlatformerObjectBehavior::GetProperties(
|
||||
const gd::SerializerElement& behaviorContent) const {
|
||||
std::map<gd::String, gd::PropertyDescriptor> properties;
|
||||
|
||||
properties[_("Gravity")].SetValue(
|
||||
properties[_("Gravity")].SetGroup(_("Jump")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("gravity")));
|
||||
properties[_("Jump speed")].SetValue(
|
||||
properties[_("Jump speed")].SetGroup(_("Jump")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("jumpSpeed")));
|
||||
properties["jumpSustainTime"]
|
||||
properties["jumpSustainTime"].SetGroup(_("Jump"))
|
||||
.SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("jumpSustainTime", 0)))
|
||||
.SetLabel(_("Jump sustain time"))
|
||||
@@ -54,38 +54,39 @@ PlatformerObjectBehavior::GetProperties(
|
||||
_("Maximum time (in seconds) during which the jump strength is "
|
||||
"sustained if the jump key is held - allowing variable height "
|
||||
"jumps."));
|
||||
properties[_("Max. falling speed")].SetValue(
|
||||
properties[_("Max. falling speed")].SetGroup(_("Jump")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxFallingSpeed")));
|
||||
properties[_("Ladder climbing speed")].SetValue(gd::String::From(
|
||||
properties[_("Ladder climbing speed")].SetGroup(_("Ladder")).SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("ladderClimbingSpeed", 150)));
|
||||
properties[_("Acceleration")].SetValue(
|
||||
properties[_("Acceleration")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("acceleration")));
|
||||
properties[_("Deceleration")].SetValue(
|
||||
properties[_("Deceleration")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("deceleration")));
|
||||
properties[_("Max. speed")].SetValue(
|
||||
properties[_("Max. speed")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("maxSpeed")));
|
||||
properties[_("Default controls")]
|
||||
.SetValue(behaviorContent.GetBoolAttribute("ignoreDefaultControls")
|
||||
? "false"
|
||||
: "true")
|
||||
.SetType("Boolean");
|
||||
properties[_("Slope max. angle")].SetValue(
|
||||
properties[_("Slope max. angle")].SetGroup(_("Walk")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("slopeMaxAngle")));
|
||||
properties[_("Can grab platform ledges")]
|
||||
.SetGroup(_("Ledge"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("canGrabPlatforms", false)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
properties[_("Grab offset on Y axis")].SetValue(
|
||||
properties[_("Grab offset on Y axis")].SetGroup(_("Ledge")).SetValue(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
|
||||
properties[_("Grab tolerance on X axis")].SetValue(gd::String::From(
|
||||
properties[_("Grab tolerance on X axis")].SetGroup(_("Ledge")).SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("xGrabTolerance", 10)));
|
||||
properties[_("Round coordinates")]
|
||||
.SetValue(behaviorContent.GetBoolAttribute("roundCoordinates", false)
|
||||
properties[_("Use frame per second dependent trajectories (deprecated)")]
|
||||
.SetGroup(_("Jump"))
|
||||
.SetValue(behaviorContent.GetBoolAttribute("useLegacyTrajectory", true)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -95,10 +96,10 @@ bool PlatformerObjectBehavior::UpdateProperty(
|
||||
const gd::String& value) {
|
||||
if (name == _("Default controls"))
|
||||
behaviorContent.SetAttribute("ignoreDefaultControls", (value == "0"));
|
||||
else if (name == _("Round coordinates"))
|
||||
behaviorContent.SetAttribute("roundCoordinates", (value == "1"));
|
||||
else if (name == _("Can grab platform ledges"))
|
||||
behaviorContent.SetAttribute("canGrabPlatforms", (value == "1"));
|
||||
else if (name == _("Use frame per second dependent trajectories (deprecated)"))
|
||||
behaviorContent.SetAttribute("useLegacyTrajectory", (value == "1"));
|
||||
else if (name == _("Grab offset on Y axis"))
|
||||
behaviorContent.SetAttribute("yGrabOffset", value.To<double>());
|
||||
else {
|
||||
|
57
Extensions/PlatformBehavior/README.md
Normal file
57
Extensions/PlatformBehavior/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Platformer Extension technical documentation
|
||||
|
||||
## Floor following
|
||||
|
||||
### Horizontal search
|
||||
|
||||
When the character walks on a platform, he must follow its slope.
|
||||
The `slopeMaxAngle` property is used to calculate how much the character can move vertically to follow it.
|
||||
If the platform is too high, the platform is considered to be an obstacle and the character will stop before it.
|
||||
|
||||
When there is no obstacle detected by the horizontal search, the movement is done in 1 step and the vertical search is done at the new `x` position.
|
||||
|
||||
[](./diagrams/SlopeFollowingRequestedDeltaX.svgz)
|
||||
|
||||
Otherwise, when there is a junction, 2 vertical searches are done:
|
||||
|
||||
- one before a potential obstacle (in pink)
|
||||
- one at the end of the movement
|
||||
|
||||
[](./diagrams/SlopeFollowingClimbFactor.svgz)
|
||||
|
||||
This allows to calculate the right slope angle. Indeed, in one step, the angle could appear lower (the dotted line).
|
||||
Which means that the character could climb it during 1 frame and then stop.
|
||||
|
||||
[](./diagrams/SlopeFollowingClimbFactorMean.svgz)
|
||||
|
||||
For further details on the implementation, please take a look at the comments in:
|
||||
- the function `gdjs.PlatformerObjectRuntimeBehavior._moveX`
|
||||
- the function `gdjs.PlatformerObjectRuntimeBehavior.OnFloor.beforeMovingY`
|
||||
|
||||
### Vertical search
|
||||
|
||||
The aim of the vertical search is to find the highest platform where the character can land.
|
||||
There are 2 constraints:
|
||||
|
||||
- `allowedMinDeltaY` how much the character can go upward
|
||||
- `allowedMaxDeltaY` how much the character can go downward
|
||||
|
||||
During the search, these 2 constraints can tighten around the character.
|
||||
If they become incompatible, it means that the character can't go through the hole,
|
||||
it will go back to its original position and lose its speed.
|
||||
|
||||
There are also more obvious obstacles that cover the character in the middle and end the search directly.
|
||||
|
||||
[](./diagrams/SlopeFollowingResult.svgz)
|
||||
|
||||
Obstacles can eventually encompass the character. So platforms edges don't have any collision with character.
|
||||
To detect such cases, 2 flags are used:
|
||||
|
||||
- `foundOverHead` when an edge is over `headMaxY`
|
||||
- `foundUnderHead` when an edge is under `floorMinY`
|
||||
|
||||
[](./diagrams/SlopeFollowingContext.svgz)
|
||||
|
||||
For further details on the implementation, please take a look at the comments in:
|
||||
- the function `gdjs.PlatformerObjectRuntimeBehavior._findHighestFloorAndMoveOnTop`
|
||||
- the class `gdjs.PlatformerObjectRuntimeBehavior.FollowConstraintContext`
|
@@ -5,7 +5,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior Benchmark', function () {
|
||||
const stepCount = 6000;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makeTestRuntimeScene();
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
objects = new Array(duplicateCount);
|
||||
for (let i = 0; i < duplicateCount; ++i) {
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingContext.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingContext.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingContext.svgz
Normal file
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingContext.svgz
Normal file
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingResult.png
Normal file
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingResult.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingResult.svgz
Normal file
BIN
Extensions/PlatformBehavior/diagrams/SlopeFollowingResult.svgz
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
|
||||
*/
|
||||
namespace gdjs {
|
||||
declare var rbush: any;
|
||||
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
|
||||
|
||||
/**
|
||||
* Manages the common objects shared by objects having a
|
||||
@@ -18,12 +19,7 @@ namespace gdjs {
|
||||
* @param object The object
|
||||
*/
|
||||
constructor(runtimeScene: gdjs.RuntimeScene) {
|
||||
this._platformRBush = new rbush(9, [
|
||||
'.owner.getAABB().min[0]',
|
||||
'.owner.getAABB().min[1]',
|
||||
'.owner.getAABB().max[0]',
|
||||
'.owner.getAABB().max[1]',
|
||||
]);
|
||||
this._platformRBush = new rbush();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +42,13 @@ namespace gdjs {
|
||||
* Add a platform to the list of existing platforms.
|
||||
*/
|
||||
addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
this._platformRBush.insert(platformBehavior);
|
||||
if (platformBehavior.currentRBushAABB)
|
||||
platformBehavior.currentRBushAABB.updateAABBFromOwner();
|
||||
else
|
||||
platformBehavior.currentRBushAABB = new gdjs.BehaviorRBushAABB(
|
||||
platformBehavior
|
||||
);
|
||||
this._platformRBush.insert(platformBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -54,7 +56,7 @@ namespace gdjs {
|
||||
* added before.
|
||||
*/
|
||||
removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
|
||||
this._platformRBush.remove(platformBehavior);
|
||||
this._platformRBush.remove(platformBehavior.currentRBushAABB);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,7 +67,7 @@ namespace gdjs {
|
||||
getAllPlatformsAround(
|
||||
object: gdjs.RuntimeObject,
|
||||
maxMovementLength: number,
|
||||
result: gdjs.PlatformRuntimeBehavior[]
|
||||
result: gdjs.BehaviorRBushAABB<PlatformRuntimeBehavior>[]
|
||||
): any {
|
||||
// TODO: This would better be done using the object AABB (getAABB), as (`getCenterX`;`getCenterY`) point
|
||||
// is not necessarily in the middle of the object (for sprites for example).
|
||||
@@ -73,20 +75,37 @@ namespace gdjs {
|
||||
const oh = object.getHeight();
|
||||
const x = object.getDrawableX() + object.getCenterX();
|
||||
const y = object.getDrawableY() + object.getCenterY();
|
||||
const searchArea = gdjs.staticObject(
|
||||
const searchArea: SearchArea = gdjs.staticObject(
|
||||
PlatformObjectsManager.prototype.getAllPlatformsAround
|
||||
);
|
||||
// @ts-ignore
|
||||
) as SearchArea;
|
||||
searchArea.minX = x - ow / 2 - maxMovementLength;
|
||||
// @ts-ignore
|
||||
searchArea.minY = y - oh / 2 - maxMovementLength;
|
||||
// @ts-ignore
|
||||
searchArea.maxX = x + ow / 2 + maxMovementLength;
|
||||
// @ts-ignore
|
||||
searchArea.maxY = y + oh / 2 + maxMovementLength;
|
||||
const nearbyPlatforms = this._platformRBush.search(searchArea);
|
||||
const nearbyPlatforms: gdjs.BehaviorRBushAABB<
|
||||
PlatformRuntimeBehavior
|
||||
>[] = this._platformRBush.search(searchArea);
|
||||
|
||||
result.length = 0;
|
||||
result.push.apply(result, nearbyPlatforms);
|
||||
|
||||
// Extra check on the platform owner AABB
|
||||
// TODO: PR https://github.com/4ian/GDevelop/pull/2602 should remove the need
|
||||
// for this extra check once merged.
|
||||
for (let i = 0; i < nearbyPlatforms.length; i++) {
|
||||
const platform = nearbyPlatforms[i];
|
||||
const platformAABB = platform.behavior.owner.getAABB();
|
||||
const platformIsStillAround =
|
||||
platformAABB.min[0] <= searchArea.maxX &&
|
||||
platformAABB.min[1] <= searchArea.maxY &&
|
||||
platformAABB.max[0] >= searchArea.minX &&
|
||||
platformAABB.max[1] >= searchArea.minY;
|
||||
// Filter platforms that are not in the searched area anymore.
|
||||
// This can happen because platforms are not updated in the RBush before that
|
||||
// characters movement are being processed.
|
||||
if (platformIsStillAround) {
|
||||
result.push(platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +124,10 @@ namespace gdjs {
|
||||
_oldY: float = 0;
|
||||
_oldWidth: float = 0;
|
||||
_oldHeight: float = 0;
|
||||
_oldAngle: float = 0;
|
||||
currentRBushAABB: gdjs.BehaviorRBushAABB<
|
||||
PlatformRuntimeBehavior
|
||||
> | null = null;
|
||||
_manager: gdjs.PlatformObjectsManager;
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
@@ -173,7 +196,8 @@ namespace gdjs {
|
||||
this._oldX !== this.owner.getX() ||
|
||||
this._oldY !== this.owner.getY() ||
|
||||
this._oldWidth !== this.owner.getWidth() ||
|
||||
this._oldHeight !== this.owner.getHeight()
|
||||
this._oldHeight !== this.owner.getHeight() ||
|
||||
this._oldAngle !== this.owner.getAngle()
|
||||
) {
|
||||
if (this._registeredInManager) {
|
||||
this._manager.removePlatform(this);
|
||||
@@ -183,6 +207,7 @@ namespace gdjs {
|
||||
this._oldY = this.owner.getY();
|
||||
this._oldWidth = this.owner.getWidth();
|
||||
this._oldHeight = this.owner.getHeight();
|
||||
this._oldAngle = this.owner.getAngle();
|
||||
}
|
||||
}
|
||||
|
||||
|
783
Extensions/PlatformBehavior/tests/FlatFloorPlatformer.spec.js
Normal file
783
Extensions/PlatformBehavior/tests/FlatFloorPlatformer.spec.js
Normal file
@@ -0,0 +1,783 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 16);
|
||||
[0, 60].forEach((slopeMaxAngle) => {
|
||||
describe(`(walk on flat floors, slopeMaxAngle: ${slopeMaxAngle}°)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: slopeMaxAngle,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const fall = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const walkRight = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
const slopesDimensions = {
|
||||
26: { width: 50, height: 25 },
|
||||
45: { width: 50, height: 50 },
|
||||
};
|
||||
|
||||
it('can walk from a platform to another one', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY()
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform to a jump through', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY()
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
expect(object.getY()).to.be(
|
||||
jumpThroughPlatform.getY() - object.getHeight()
|
||||
);
|
||||
});
|
||||
|
||||
it('can walk on a platform and go through a jump through', function () {
|
||||
// Jumpthru that are ignored had a side effects on the search context.
|
||||
// It made jumpthru appear solid when a platform was tested after them.
|
||||
|
||||
// Add the jumptru 1st to make RBrush gives it 1st.
|
||||
// There is no causality but it does in the current implementation.
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setPosition(30, -15);
|
||||
jumpThroughPlatform.setCustomWidthAndHeight(60, 10);
|
||||
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
object.setPosition(10, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(20);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform to another one that not aligned', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// the 2nd platform is 1 pixel higher
|
||||
platform.getY() - 1
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform to another one with a speed under 1 pixel/second', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The 2nd platform is 1 pixels higher.
|
||||
platform.getY() - 1
|
||||
);
|
||||
// Put the object just to the left of platform2 so that
|
||||
// it try climbing on it with a very small speed.
|
||||
object.setPosition(platform2.getX() - object.getWidth(), -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform2.getX());
|
||||
expect(object.getY()).to.be(platform2.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it("can't walk from a platform to another one that is a bit too high", function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The 2nd platform is 2 pixels higher.
|
||||
platform.getY() - 2
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// walk right
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// is blocked by the 2nd platform
|
||||
expect(object.getX()).to.be(platform2.getX() - object.getWidth());
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk on a platform and be blocked by a wall', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
// the 2nd platform is 2 pixels higher
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The platform's top is over the object
|
||||
// and platform's bottom is under the object.
|
||||
platform.getY() - platform2.getHeight() + 5
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// walk right
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// is blocked by the 2nd platform
|
||||
expect(object.getX()).to.be(platform2.getX() - object.getWidth());
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can walk from a platform and fell through a jump through that is at the right but 1 pixel higher', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// Even 1 pixel is too high to follow a jump through
|
||||
// because it's like it's gone through its right or left side.
|
||||
platform.getY() - 1
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Walk right
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// Fall under the jump through platform
|
||||
for (let i = 0; i < 11; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
}
|
||||
expect(object.getY()).to.be.above(platform.getY());
|
||||
});
|
||||
|
||||
it('can walk inside a tunnel platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addTunnelPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
object.setPosition(0, 160);
|
||||
// The object falls on the bottom part of the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(200 - object.getHeight());
|
||||
|
||||
// The object walk on the bottom part of the platform.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(60);
|
||||
expect(object.getY()).to.be(200 - object.getHeight());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
// less than 1 pixel per frame (50/60)
|
||||
50,
|
||||
// a commonly used value
|
||||
1500,
|
||||
].forEach((maxFallingSpeed) => {
|
||||
describe(`(on floor, maxFallingSpeed=${
|
||||
maxFallingSpeed / 60
|
||||
} pixels per frame)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object in the air.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: maxFallingSpeed,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -100);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
// TODO The character falls one frame then land instead of staying on the platform.
|
||||
it.skip('must not move when on the floor at startup', function () {
|
||||
object.setPosition(0, platform.getY() - object.getHeight());
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check the platformer object stays still.
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('must not move when put on a platform while falling', function () {
|
||||
object.setPosition(0, platform.getY() - object.getHeight() - 300);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
object.setPosition(0, platform.getY() - object.getHeight());
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check the platformer object stays still.
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('can track object height changes', function () {
|
||||
// Put the character near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + 10,
|
||||
platform.getY() - object.getHeight() + 1
|
||||
);
|
||||
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getX()).to.be(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// Make the platform under the character feet smaller.
|
||||
object.setCustomWidthAndHeight(object.getWidth(), 9);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
// The character follows it.
|
||||
expect(object.getY()).to.be(-19); // -19 = -10 (platform y) + -9 (object height)
|
||||
|
||||
// The character walks on the platform.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
}
|
||||
expect(object.getY()).to.be(-19);
|
||||
expect(object.getX()).to.be.above(16);
|
||||
|
||||
// Make the platform under the character feet bigger.
|
||||
object.setCustomWidthAndHeight(object.getWidth(), 20);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// The character follows it.
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
});
|
||||
|
||||
it('can track platform angle changes', function () {
|
||||
// The initial pltaforms AABB are put in RBush.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Now change the angle to check that the AABB is updated in RBush.
|
||||
platform.setAngle(90);
|
||||
|
||||
// Put the character above the rotated platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() / 2,
|
||||
platform.getY() +
|
||||
(platform.getHeight() - platform.getWidth()) / 2 -
|
||||
object.getHeight() -
|
||||
10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// The character should land on it.
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getX()).to.be(30);
|
||||
expect(object.getY()).to.be(-44);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(`(walk on flat floors with custom hitbox)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 25, y: 25 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 25, y: 25 },
|
||||
{ x: 75, y: 25 },
|
||||
{ x: 75, y: 75 },
|
||||
{ x: 25, y: 75 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
object.setUnscaledWidthAndHeight(100, 100);
|
||||
object.setCustomWidthAndHeight(20, 40);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
// The actual hitbox is 10x20.
|
||||
const objectWidth = 10;
|
||||
const objectHeight = 20;
|
||||
|
||||
const fall = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const walkRight = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
};
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
it('can walk on a platform and be blocked by a wall', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
// the 2nd platform is 2 pixels higher
|
||||
const wall = addPlatformObject(runtimeScene);
|
||||
wall.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
// The platform is top is over the object
|
||||
// and platform is bottom is under the object.
|
||||
platform.getY() - wall.getHeight() + 5
|
||||
);
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
// walk right
|
||||
for (let i = 0; i < 25; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
// is blocked by the wall
|
||||
expect(object.getX()).to.be(wall.getX() - objectWidth);
|
||||
expect(object.getY()).to.be(platform.getY() - objectHeight);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Floating-point error mitigations', function () {
|
||||
it('Specific coordinates with slopeMaxAngle=0 creating Y oscillations and drift on a moving floor', function () {
|
||||
const runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Create a Sprite object that has the origin at a specific position (see below)
|
||||
// and that has a slope max angle of 0 (so it can't climb on a floor even if it's a bit higher
|
||||
// than the bottom of the object).
|
||||
const object = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1300,
|
||||
maxFallingSpeed: 1000,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 280,
|
||||
jumpSpeed: 750,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 0,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 5, y: 19 },
|
||||
centerPoint: { x: 5, y: 46 },
|
||||
points: [
|
||||
{ name: 'Center', x: 5, y: 46 },
|
||||
{ name: 'Origin', x: 5, y: 19 },
|
||||
],
|
||||
hasCustomCollisionMask: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Set the size of the object so that it results in a specific
|
||||
// Y position for the bottom of the object AABB:
|
||||
object.setUnscaledWidthAndHeight(10, 92);
|
||||
object.setCustomWidthAndHeight(10, 66.0008);
|
||||
// Origin Y is originally 19.
|
||||
// After the scaling, it is now 19*66.0008/92=13.6306.
|
||||
|
||||
// Set the Y position so that the object falls at a Y position on the floor
|
||||
// that would generate oscillations.
|
||||
object.setPosition(0, 139.3118);
|
||||
runtimeScene.addObject(object);
|
||||
|
||||
// Put a platform at a specific Y that can cause oscillations.
|
||||
const platform = addJumpThroughPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, 193.000000000001);
|
||||
// This means that the exact Y position the object should take is:
|
||||
// platform Y - height + origin Y = 193.000000000001-66.0008+13.6306 = 140.6298
|
||||
|
||||
// Wait for the object to fall on the floor
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
|
||||
|
||||
// Ensure it is on the floor
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
// The Y position won't be exact because of floating point errors.
|
||||
// expect(object.getY()).to.be(140.6298)
|
||||
expect(object.getY()).to.be.within(140.6297999, 140.6298001);
|
||||
|
||||
// Move the platform by 6 pixels to the right.
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Ensure the object followed the platform on the X axis.
|
||||
// If the floating point errors caused oscillations between two Y positions,
|
||||
// it won't work because the object will get repositioned back to its old X position
|
||||
// whenever the floor is considered "too high" for the object to reach.
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getY()).to.be.within(140.6297999, 140.6298001);
|
||||
expect(object.getX()).to.be(6);
|
||||
});
|
||||
});
|
||||
|
||||
[20, 30, 60, 120].forEach((framesPerSecond) => {
|
||||
describe(`(FPS independent trajectory: ${framesPerSecond} fps)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene(1000 / framesPerSecond);
|
||||
|
||||
// Put a platformer object on a platform
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 1500,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
useLegacyTrajectory: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
const fallOnPlatform = (maxFrameCount) => {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < maxFrameCount; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
it('can walk', function () {
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
platform.setCustomWidthAndHeight(600, 32);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// Accelerate
|
||||
for (let i = 0; i < framesPerSecond; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Reached the maximum speed
|
||||
expect(object.getX()).to.be.within(250 - epsilon, 250 + epsilon);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').getCurrentSpeed()).to.be.within(
|
||||
500 - epsilon,
|
||||
500 + epsilon
|
||||
);
|
||||
|
||||
// Decelerate
|
||||
for (let i = 0; i < framesPerSecond / 3; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Stopped
|
||||
expect(object.getX()).to.be.within(333, 334);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').getCurrentSpeed()).to.be(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user