mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
192 Commits
add-public
...
fix/npm-in
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3d4766e5eb | ||
![]() |
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.
|
||||
|
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
|
||||
|
37
.travis.yml
37
.travis.yml
@@ -17,10 +17,6 @@ cache:
|
||||
directories:
|
||||
- $HOME/.npm
|
||||
|
||||
env:
|
||||
global:
|
||||
- GCC_VERSION="4.8"
|
||||
|
||||
services:
|
||||
# Virtual Framebuffer 'fake' X server for SFML
|
||||
- xvfb
|
||||
@@ -40,7 +36,6 @@ addons:
|
||||
# Build dependencies:
|
||||
- cmake
|
||||
- p7zip-full
|
||||
- g++-4.8
|
||||
# SFML dependencies:
|
||||
- libopenal-dev
|
||||
- libjpeg-dev
|
||||
@@ -60,28 +55,24 @@ before_install:
|
||||
- sudo dpkg --force-all -i libstdc++6
|
||||
|
||||
install:
|
||||
#Get the correct version of gcc/g++
|
||||
- if [ "$CXX" = "g++" ]; then export CXX="g++-${GCC_VERSION}" CC="gcc-${GCC_VERSION}"; fi
|
||||
# Ensure we use a recent version of Node.js (and npm).
|
||||
- nvm install v16 && nvm use v16
|
||||
#Compile the tests only for GDCore
|
||||
- mkdir .build-tests
|
||||
- cd .build-tests
|
||||
- cmake -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE -DCMAKE_CXX_COMPILER=$(which $CXX) -DCMAKE_C_COMPILER=$(which $CC) ..
|
||||
- make -j 4
|
||||
- cd ..
|
||||
#- mkdir .build-tests
|
||||
#- cd .build-tests
|
||||
#- cmake -DBUILD_GDJS=FALSE -DBUILD_TESTS=TRUE -DCMAKE_CXX_COMPILER=$(which $CXX) -DCMAKE_C_COMPILER=$(which $CC) ..
|
||||
#- make -j 4
|
||||
#- cd ..
|
||||
# Install Emscripten (for GDevelop.js)
|
||||
- git clone https://github.com/juj/emsdk.git
|
||||
- cd emsdk
|
||||
- ./emsdk install 1.39.6
|
||||
- ./emsdk activate 1.39.6
|
||||
- source ./emsdk_env.sh
|
||||
- cd ..
|
||||
# Install GDevelop.js dependencies and compile it
|
||||
- cd GDevelop.js
|
||||
- npm install -g grunt-cli
|
||||
- npm install
|
||||
- npm run build
|
||||
- cd ..
|
||||
- cd emsdk && ./emsdk install 1.39.6 && ./emsdk activate 1.39.6 && cd ..
|
||||
# Install GDevelop.js dependencies
|
||||
- cd GDevelop.js && npm install && cd ..
|
||||
# Build GDevelop.js
|
||||
- cd GDevelop.js && source ../emsdk/emsdk_env.sh && npm run build && cd ..
|
||||
- nvm use v16 # Restore latest Node.js version.
|
||||
#Install newIDE tests dependencies
|
||||
- npm -v
|
||||
- cd newIDE/app && npm install
|
||||
- cd ../..
|
||||
#Install GDJS tests dependencies
|
||||
|
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
1317
Extensions/PlatformBehavior/tests/JumpAndFallingPlatformer.spec.js
Normal file
1317
Extensions/PlatformBehavior/tests/JumpAndFallingPlatformer.spec.js
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user