mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Compare commits
146 Commits
add-public
...
fix/export
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b21a191d4c | ||
![]() |
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 |
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.
|
||||
|
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
|
||||
|
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,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,39 @@ 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("string", _("Expression generating the button to check"))
|
||||
.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("string",
|
||||
_("Expression generating the mouse button to check"))
|
||||
.SetParameterLongDescription(
|
||||
_("Possible values are Left, Right and Middle."))
|
||||
.MarkAsAdvanced();
|
||||
|
||||
extension
|
||||
.AddExpressionAndCondition("number",
|
||||
"TouchX",
|
||||
@@ -301,7 +334,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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
};
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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;
|
||||
@@ -203,7 +204,7 @@ namespace gdjs {
|
||||
fileSystem.mkdirSync(directory);
|
||||
result = 'ok';
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to create directory at: '" + directory + "': ",
|
||||
err
|
||||
);
|
||||
@@ -228,7 +229,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 +257,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 +288,7 @@ namespace gdjs {
|
||||
);
|
||||
result = 'ok';
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to save the variable to path: '" + savePath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -316,7 +317,7 @@ namespace gdjs {
|
||||
(err) => {
|
||||
resultVar.setString('ok');
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to save the variable to path: '" + savePath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -348,7 +349,7 @@ namespace gdjs {
|
||||
result = 'ok';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load the file at path: '" + loadPath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -378,7 +379,7 @@ namespace gdjs {
|
||||
result = 'ok';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load variable from the file at path: '" +
|
||||
loadPath +
|
||||
"': ",
|
||||
@@ -408,7 +409,7 @@ namespace gdjs {
|
||||
resultVar.setString('ok');
|
||||
}
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load variable from the file at path: '" +
|
||||
loadPath +
|
||||
"': ",
|
||||
@@ -439,7 +440,7 @@ namespace gdjs {
|
||||
resultVar.setString('ok');
|
||||
}
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
"Unable to load the file at path: '" + loadPath + "': ",
|
||||
err
|
||||
);
|
||||
@@ -465,7 +466,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 +487,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',
|
||||
|
@@ -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.
|
||||
@@ -542,7 +543,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 +615,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 +786,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 +816,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
@@ -80,12 +79,6 @@ PlatformerObjectBehavior::GetProperties(
|
||||
gd::String::From(behaviorContent.GetDoubleAttribute("yGrabOffset")));
|
||||
properties[_("Grab tolerance on X axis")].SetValue(gd::String::From(
|
||||
behaviorContent.GetDoubleAttribute("xGrabTolerance", 10)));
|
||||
properties[_("Round coordinates")]
|
||||
.SetValue(behaviorContent.GetBoolAttribute("roundCoordinates", false)
|
||||
? "true"
|
||||
: "false")
|
||||
.SetType("Boolean");
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
@@ -95,8 +88,6 @@ 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 == _("Grab offset on Y axis"))
|
||||
|
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
@@ -105,6 +105,7 @@ namespace gdjs {
|
||||
_oldY: float = 0;
|
||||
_oldWidth: float = 0;
|
||||
_oldHeight: float = 0;
|
||||
_oldAngle: float = 0;
|
||||
_manager: gdjs.PlatformObjectsManager;
|
||||
_registeredInManager: boolean = false;
|
||||
|
||||
@@ -173,7 +174,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 +185,7 @@ namespace gdjs {
|
||||
this._oldY = this.owner.getY();
|
||||
this._oldWidth = this.owner.getWidth();
|
||||
this._oldHeight = this.owner.getHeight();
|
||||
this._oldAngle = this.owner.getAngle();
|
||||
}
|
||||
}
|
||||
|
||||
|
695
Extensions/PlatformBehavior/tests/FlatFloorPlatformer.spec.js
Normal file
695
Extensions/PlatformBehavior/tests/FlatFloorPlatformer.spec.js
Normal file
@@ -0,0 +1,695 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
[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 object 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.within(-31, -30); // -30 = -10 (platform y) + -20 (object height)
|
||||
|
||||
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
|
||||
);
|
||||
expect(object.getY()).to.be(-19); // -19 = -10 (platform y) + -9 (object height)
|
||||
|
||||
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.within(17.638, 17.639);
|
||||
|
||||
object.setCustomWidthAndHeight(object.getWidth(), 20);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,934 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
describe('(falling)', 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: 1500,
|
||||
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);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
});
|
||||
|
||||
it('can fall when in the air', function () {
|
||||
for (let i = 0; i < 30; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
if (i < 10) expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
if (i < 10)
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
//Check the platform stopped the platformer object.
|
||||
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);
|
||||
|
||||
for (let i = 0; i < 35; ++i) {
|
||||
//Check that the platformer object can fall.
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getX()).to.be.within(87.5, 87.51);
|
||||
expect(object.getY()).to.be(-24.75);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
|
||||
for (let i = 0; i < 100; ++i) {
|
||||
//Let the speed on X axis go back to 0.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
});
|
||||
|
||||
it('falls when a platform is moved away', function () {
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
//Check the object is on the platform
|
||||
expect(object.getY()).to.be.within(-31, -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);
|
||||
|
||||
// move the platform away
|
||||
platform.setPosition(-100, -100);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
});
|
||||
|
||||
it('falls when a platform is removed', function () {
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object is on the platform
|
||||
expect(object.getY()).to.be.within(-31, -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);
|
||||
|
||||
// Remove the platform
|
||||
runtimeScene.markObjectForDeletion(platform);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(jump and jump sustain)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
|
||||
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: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -32);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
it('can jump', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
//Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Jump without sustaining
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 18; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
// Check that we reached the maximum height
|
||||
expect(object.getY()).to.be.within(-180, -179);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be(-180);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-180, -179);
|
||||
|
||||
// Then let the object fall
|
||||
for (let i = 0; i < 17; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
}
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getY()).to.be(-30);
|
||||
});
|
||||
|
||||
it('can jump, sustaining the jump', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
//Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Forbid to jump
|
||||
object.getBehavior('auto1').setCanNotAirJump();
|
||||
// It has no impact as the object is on a platform.
|
||||
expect(object.getBehavior('auto1').canJump()).to.be(true);
|
||||
|
||||
// Jump with sustaining as much as possible, and
|
||||
// even more (18 frames at 60fps is greater than 0.2s)
|
||||
for (let i = 0; i < 18; ++i) {
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the height reached
|
||||
expect(object.getY()).to.be(-230);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be(-235);
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
// Verify that pressing the jump key does not change anything
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check that we reached the maximum height
|
||||
expect(object.getY()).to.be(-247.5);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be(-247.5);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-247, -246);
|
||||
|
||||
// Then let the object fall
|
||||
for (let i = 0; i < 60; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be(-30);
|
||||
});
|
||||
|
||||
it('can jump, and only sustain the jump while key held', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
//Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Jump with sustaining a bit (5 frames at 60fps = 0.08s), then stop
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.within(-101, -100);
|
||||
|
||||
// Stop holding the jump key
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
for (let i = 0; i < 13; ++i) {
|
||||
// then hold it again (but it's too late, jump sustain is gone for this jump)
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check that we reached the maximum height
|
||||
expect(object.getY()).to.be.within(-206, -205);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-208, -207);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-208, -207);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-208, -207);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-206, -205);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Then let the object fall
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
for (let i = 0; i < 60; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be(-30);
|
||||
});
|
||||
|
||||
it('should not jump after falling from a platform', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object is on the platform
|
||||
// So at this point, the object could jump
|
||||
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);
|
||||
|
||||
// Fall from the platform
|
||||
for (let i = 0; i < 35; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Try to jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
});
|
||||
|
||||
it('can be allowed to jump in mid air', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Fall from the platform
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Allow to jump in mid air
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
object.getBehavior('auto1').setCanJump();
|
||||
expect(object.getBehavior('auto1').canJump()).to.be(true);
|
||||
|
||||
// Can jump in the air
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
|
||||
for (let i = 0; i < 40; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
|
||||
// Can no longer to jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
});
|
||||
|
||||
it('can allow coyote time', function () {
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Fall from the platform
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Allow to jump
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
object.getBehavior('auto1').setCanJump();
|
||||
expect(object.getBehavior('auto1').canJump()).to.be(true);
|
||||
|
||||
// Still falling from the platform
|
||||
for (let i = 0; i < 4; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Suppose that we miss an eventual time frame or some condition.
|
||||
// So we forbid to jump again:
|
||||
object.getBehavior('auto1').setCanNotAirJump();
|
||||
expect(object.getBehavior('auto1').canJump()).to.be(false);
|
||||
|
||||
// Can no longer to jump in mid air
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
});
|
||||
|
||||
it('should not grab a platform while in the ascending phase of a jump', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(12, -80);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Jump without sustaining
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
}
|
||||
// the object is against the platform side
|
||||
expect(object.getY()).to.be.within(
|
||||
topPlatform.getY(),
|
||||
topPlatform.getY() + object.getHeight()
|
||||
);
|
||||
|
||||
// try to grab the platform
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
}
|
||||
// Check that the object didn't grabbed the platform
|
||||
expect(object.getX()).to.be.above(
|
||||
topPlatform.getX() - object.getWidth() + 20
|
||||
);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
});
|
||||
|
||||
it('can grab a platform while in the descending phase of a jump', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(12, -120);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object is on the platform
|
||||
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);
|
||||
|
||||
// Jump, reach the top and go down
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 30; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
}
|
||||
// the object is against the platform side
|
||||
expect(object.getY()).to.be.within(
|
||||
topPlatform.getY() - object.getHeight(),
|
||||
topPlatform.getY()
|
||||
);
|
||||
|
||||
// Verify the object is in the falling state of the jump:
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
|
||||
// try to grab the platform
|
||||
for (let i = 0; i < 30; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check that the object grabbed the platform
|
||||
expect(object.getY()).to.be(topPlatform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
});
|
||||
|
||||
it('should not grab a platform while walking', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(20, platform.getY() - object.getHeight());
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Ensure the object falls on the platform
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object is on the platform
|
||||
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);
|
||||
|
||||
// try to grab the platform
|
||||
for (let i = 0; i < 30; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
}
|
||||
|
||||
// The object is where it could grab the top platform if it was falling.
|
||||
expect(object.getX()).to.be.within(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getX() - object.getWidth() + 2
|
||||
);
|
||||
expect(object.getY()).to.be(topPlatform.getY());
|
||||
// Check that the object didn't grabbed the platform
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(jumpthru)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
let jumpthru;
|
||||
|
||||
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: 900,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -30);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
// Put a jump thru, higher than the platform so that the object jump from under it
|
||||
// and will land on it at the end of the jump.
|
||||
jumpthru = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj2',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
platformType: 'Jumpthru',
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
jumpthru.setCustomWidthAndHeight(60, 5);
|
||||
runtimeScene.addObject(jumpthru);
|
||||
});
|
||||
|
||||
it('can jump through a jumpthru and land', function () {
|
||||
jumpthru.setPosition(0, -33);
|
||||
//Check the platform stopped the platformer object.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
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);
|
||||
|
||||
// Check that the jump starts properly, and is not stopped on the jumpthru
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-39, -38);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-47, -46);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// At this step, the object is almost on the jumpthru (-53 + 20 (object height) = -33 (jump thru Y position)),
|
||||
// but the object should not stop.
|
||||
expect(object.getY()).to.be.within(-54, -53);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-61, -60);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getY()).to.be.within(-67, -66);
|
||||
|
||||
// Verify the object is still jumping
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
|
||||
// Continue the simulation and check that position is correct in the middle of the jump
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.within(-89, -88);
|
||||
|
||||
// Verify the object is now considered as falling in its jump:
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
|
||||
// Continue simulation and check that we arrive on the jumpthru
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.within(
|
||||
jumpthru.getY() - object.getHeight(),
|
||||
jumpthru.getY() - object.getHeight() + 1
|
||||
);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
});
|
||||
|
||||
it('can jump right under a jumpthru without landing', function () {
|
||||
// A big one because the object jump to the right.
|
||||
jumpthru.setCustomWidthAndHeight(600, 20);
|
||||
const highestJumpY = -104; // actually -103.6
|
||||
// Right above the maximum reach by jumping
|
||||
jumpthru.setPosition(0, highestJumpY + object.getHeight());
|
||||
|
||||
// The object landed on the platform.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
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);
|
||||
|
||||
// The object jumps.
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 17; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
}
|
||||
// The object is at the highest of the jump.
|
||||
expect(object.getY()).to.be.within(highestJumpY, highestJumpY + 1);
|
||||
|
||||
// The object starts to fall.
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
|
||||
// The object still falls.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
}
|
||||
expect(object.getY()).to.be.above(-85);
|
||||
});
|
||||
|
||||
it('can jump right above a jumpthru and landing', function () {
|
||||
// A big one because the object jump to the right.
|
||||
jumpthru.setCustomWidthAndHeight(600, 20);
|
||||
const highestJumpY = -104; // actually -103.6
|
||||
// Right above the maximum reach by jumping
|
||||
jumpthru.setPosition(0, highestJumpY + 1 + object.getHeight());
|
||||
|
||||
// The object landed on the platform.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
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);
|
||||
|
||||
// The object jumps.
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 17; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
}
|
||||
// The object is at the highest of the jump.
|
||||
expect(object.getY()).to.be.within(highestJumpY, highestJumpY + 1);
|
||||
|
||||
// The object landed on the jumpthru.
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getY()).to.be(jumpthru.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('can fall through the jumpthru from the left side', function () {
|
||||
jumpthru.setPosition(0, -33);
|
||||
object.setPosition(0, -100);
|
||||
jumpthru.setPosition(12, -90);
|
||||
jumpthru.setCustomWidthAndHeight(60, 100);
|
||||
|
||||
// Check the jumpthru let the platformer object go through.
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
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);
|
||||
}
|
||||
// Overlapping the jumpthru
|
||||
expect(object.getX()).to.above(5);
|
||||
expect(object.getY()).to.be.within(-100, -80);
|
||||
});
|
||||
});
|
||||
|
||||
describe('and gdjs.PlatformRuntimeBehavior at same time', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
var object2;
|
||||
|
||||
beforeEach(function () {
|
||||
runtimeScene = makePlatformerTestRuntimeScene();
|
||||
|
||||
// Put a platformer object on a platform.
|
||||
object = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'PlatformerObject',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -30);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
// Put a platformer object that is also a platform itself.
|
||||
object2 = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj2',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'PlatformerObject',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
platformType: 'Platform',
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object2.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object2);
|
||||
|
||||
// Position it above the other platformer object and just on its right,
|
||||
// but one pixel too much so that the first platformer object will be moved
|
||||
// left by 1px when the second platformer object+platform falls.
|
||||
object2.setPosition(9, -60);
|
||||
});
|
||||
|
||||
it('can jump through the jumpthru', function () {
|
||||
// Check that the second object falls (it's not stopped by itself)
|
||||
expect(object2.getY()).to.be(-60);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object2.getY()).to.be(-59.75);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object2.getY()).to.be(-59.25);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object2.getY()).to.be(-58.5);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object2.getY()).to.be(-57.5);
|
||||
|
||||
//Check the first object stays on the platform.
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// Simulate more frames. Check that trying to jump won't do anything.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
object2.getBehavior('PlatformerObject').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object2.getY()).to.be(-48.75);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// Verify that the first platformer object is moved 1px to the left
|
||||
// as the falling platformer object+platform collides with it
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object2.getY()).to.be(-46.25);
|
||||
expect(object.getX()).to.be(-1);
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// Simulate more frames so that the object reaches the floor
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object2.getX()).to.be(9);
|
||||
expect(object2.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(-1);
|
||||
expect(object.getY()).to.be(-30);
|
||||
|
||||
// Start a jump for both objects
|
||||
object.getBehavior('PlatformerObject').simulateJumpKey();
|
||||
object2.getBehavior('PlatformerObject').simulateJumpKey();
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object2.getX()).to.be(9);
|
||||
expect(object2.getY()).to.be(-72.5);
|
||||
expect(object.getX()).to.be(-1);
|
||||
expect(object.getY()).to.be(-72.5);
|
||||
|
||||
// Try to go right for the first object: won't work because the other
|
||||
// object is a platform.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
object.getBehavior('PlatformerObject').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object2.getX()).to.be(9);
|
||||
expect(object2.getY()).to.be.within(-94.2, -94.1);
|
||||
expect(object.getX()).to.be(-1);
|
||||
expect(object.getY()).to.be.within(-94.2, -94.1);
|
||||
|
||||
// Try to go right for the first and second object: can do.
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
object.getBehavior('PlatformerObject').simulateRightKey();
|
||||
object2.getBehavior('PlatformerObject').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object2.getX()).to.be.within(9.83, 9.84);
|
||||
expect(object2.getY()).to.be.within(-101.2, -101.1);
|
||||
expect(object.getX()).to.be.within(-0.59, -0.58);
|
||||
expect(object.getY()).to.be.within(-101.2, -101.1);
|
||||
|
||||
// Let the object fall back on the floor.
|
||||
for (let i = 0; i < 30; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object2.getX()).to.be.within(9.83, 9.84);
|
||||
expect(object2.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be.within(-0.59, -0.58);
|
||||
expect(object.getY()).to.be(-30);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,552 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
describe('(grab platforms)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
|
||||
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: 1500,
|
||||
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);
|
||||
});
|
||||
|
||||
it('can grab, and release, a platform', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
//Put the object near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() + 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 35; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
//Check that the object grabbed the platform
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() + platform.getWidth() + 0,
|
||||
platform.getX() + platform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
//Check that the object is falling
|
||||
expect(object.getY()).to.be(3.75);
|
||||
});
|
||||
|
||||
[true, false].forEach((addTopPlatformFirst) => {
|
||||
it('can grab every platform when colliding 2', function () {
|
||||
// The 2 platforms will be simultaneously in collision
|
||||
// with the object when it grabs one.
|
||||
let upperPlatform, lowerPlatform;
|
||||
if (addTopPlatformFirst) {
|
||||
upperPlatform = addPlatformObject(runtimeScene);
|
||||
upperPlatform.setPosition(0, -10);
|
||||
upperPlatform.setCustomWidthAndHeight(60, 10);
|
||||
|
||||
lowerPlatform = addPlatformObject(runtimeScene);
|
||||
lowerPlatform.setPosition(0, 0);
|
||||
lowerPlatform.setCustomWidthAndHeight(60, 10);
|
||||
} else {
|
||||
lowerPlatform = addPlatformObject(runtimeScene);
|
||||
lowerPlatform.setPosition(0, 0);
|
||||
lowerPlatform.setCustomWidthAndHeight(60, 10);
|
||||
|
||||
upperPlatform = addPlatformObject(runtimeScene);
|
||||
upperPlatform.setPosition(0, -10);
|
||||
upperPlatform.setCustomWidthAndHeight(60, 10);
|
||||
}
|
||||
|
||||
// Put the object near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
upperPlatform.getX() + upperPlatform.getWidth() + 2,
|
||||
upperPlatform.getY() - 10
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
for (let i = 0; i < 35; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check that the object grabbed the upper platform
|
||||
expect(object.getX()).to.be.within(
|
||||
upperPlatform.getX() + upperPlatform.getWidth() + 0,
|
||||
upperPlatform.getX() + upperPlatform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(upperPlatform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// Release upper platform
|
||||
object.getBehavior('auto1').simulateReleasePlatformKey();
|
||||
for (let i = 0; i < 35; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check that the object grabbed the lower platform
|
||||
expect(object.getX()).to.be.within(
|
||||
lowerPlatform.getX() + lowerPlatform.getWidth() + 0,
|
||||
lowerPlatform.getX() + lowerPlatform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(lowerPlatform.getY());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('can grab a platform and jump', function () {
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
//Put the object near the right ledge of the platform.
|
||||
object.setPosition(
|
||||
platform.getX() + platform.getWidth() + 2,
|
||||
platform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 35; ++i) {
|
||||
object.getBehavior('auto1').simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
//Check that the object grabbed the platform
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
expect(object.getX()).to.be.within(
|
||||
platform.getX() + platform.getWidth() + 0,
|
||||
platform.getX() + platform.getWidth() + 1
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY());
|
||||
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
//Check that the object is jumping
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
}
|
||||
expect(object.getY()).to.be.below(platform.getY());
|
||||
});
|
||||
});
|
||||
|
||||
describe('(ladder)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
var scale;
|
||||
|
||||
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: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
ladder = addLadderObject(runtimeScene);
|
||||
ladder.setPosition(30, -10 - ladder.getHeight());
|
||||
});
|
||||
|
||||
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').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const climbLadder = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.below(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const releaseLadder = (frameCount) => {
|
||||
object.getBehavior('auto1').simulateReleaseLadderKey();
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const stayOnLadder = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
expect(object.getY()).to.be(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
const jumpAndAscend = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.below(lastY);
|
||||
}
|
||||
};
|
||||
const jumpAndDescend = (frameCount) => {
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
false
|
||||
);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
}
|
||||
};
|
||||
|
||||
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.getY()).to.be(-30); // -30 = -10 (platform y) + -20 (object height)
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
};
|
||||
|
||||
it('can climb and release a ladder', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(10);
|
||||
stayOnLadder(10);
|
||||
const objectPositionAfterFirstClimb = object.getY();
|
||||
releaseLadder(10);
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
expect(object.getY()).to.be.within(
|
||||
// gravity is 1500, 10 frames falling ~ 23px
|
||||
objectPositionAfterFirstClimb + 22,
|
||||
objectPositionAfterFirstClimb + 24
|
||||
);
|
||||
climbLadder(24);
|
||||
// Check that we reached the maximum height
|
||||
const playerAtLadderTop = ladder.getY() - object.getHeight();
|
||||
expect(object.getY()).to.be.within(
|
||||
playerAtLadderTop - 3,
|
||||
playerAtLadderTop
|
||||
);
|
||||
|
||||
// The player goes a little over the ladder...
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
// ...and it falls even if up is pressed
|
||||
for (let i = 0; i < 13; ++i) {
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
fall(1);
|
||||
}
|
||||
});
|
||||
|
||||
it('can jump and grab a ladder even on the ascending phase of a jump the 1st time', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
for (let i = 0; i < 2; ++i) {
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.below(-30);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
|
||||
// Grab the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
stayOnLadder(10);
|
||||
climbLadder(2);
|
||||
});
|
||||
|
||||
it('can grab a ladder while on the descending phase of a jump', function () {
|
||||
// Need a bigger ladder
|
||||
ladder.getHeight = function () {
|
||||
return 300;
|
||||
};
|
||||
ladder.setPosition(30, -10 - ladder.getHeight());
|
||||
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 19; ++i) {
|
||||
jumpAndAscend(1);
|
||||
}
|
||||
|
||||
// starting to going down
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(1);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
|
||||
stayOnLadder(10);
|
||||
climbLadder(2);
|
||||
});
|
||||
|
||||
it('can jump from ladder to ladder', function () {
|
||||
// Need a bigger ladder
|
||||
ladder.getHeight = function () {
|
||||
return 300;
|
||||
};
|
||||
ladder.setPosition(30, -10 - ladder.getHeight());
|
||||
|
||||
const ladder2 = addLadderObject(runtimeScene);
|
||||
ladder2.getHeight = function () {
|
||||
return 300;
|
||||
};
|
||||
ladder2.setPosition(ladder.getX() + ladder.getWidth(), ladder.getY());
|
||||
|
||||
object.setPosition(35, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Jump
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
jumpAndAscend(1);
|
||||
}
|
||||
|
||||
// 1st time grabbing this ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(1);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(false);
|
||||
|
||||
// Jump right
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 15; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
jumpAndAscend(1);
|
||||
}
|
||||
// leave the 1st ladder
|
||||
expect(object.getX()).to.be.above(ladder2.getX());
|
||||
// and grab the 2nd one, even if still ascending
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
// still moves a little because of inertia
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
stayOnLadder(1);
|
||||
});
|
||||
|
||||
it('can fall from a ladder right side', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(10);
|
||||
stayOnLadder(10);
|
||||
|
||||
// Fall to the ladder right
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
fall(5);
|
||||
});
|
||||
|
||||
it('can walk from a ladder', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(10);
|
||||
|
||||
// Going from the ladder to the right
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
for (let i = 0; i < 16; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
// and directly on the floor
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
});
|
||||
|
||||
it('can jump from a ladder', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(10);
|
||||
stayOnLadder(10);
|
||||
|
||||
// Jump from the ladder
|
||||
const stayY = object.getY();
|
||||
object.getBehavior('auto1').simulateJumpKey();
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
expect(object.getY()).to.be.below(stayY);
|
||||
expect(object.getBehavior('auto1').isJumping()).to.be(true);
|
||||
});
|
||||
|
||||
it('can grab a ladder when falling', function () {
|
||||
object.setPosition(30, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Climb the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
climbLadder(24);
|
||||
// Check that we reached the maximum height
|
||||
// The player goes a little over the ladder...
|
||||
object.getBehavior('auto1').simulateUpKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
fall(10);
|
||||
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(10);
|
||||
climbLadder(5);
|
||||
});
|
||||
|
||||
it('should not grab a platform when grabbed to a ladder', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(ladder.getX() + ladder.getWidth(), -50);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
object.setPosition(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getY()
|
||||
);
|
||||
// Grab the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
stayOnLadder(10);
|
||||
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// The object is where it could grab the top platform if it where falling.
|
||||
expect(object.getX()).to.be.within(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getX() - object.getWidth() + 2
|
||||
);
|
||||
expect(object.getY()).to.be(topPlatform.getY());
|
||||
// Check that the object didn't grabbed the platform
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
|
||||
stayOnLadder(10);
|
||||
});
|
||||
|
||||
it('can grab a ladder when grabbed to a platform', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(ladder.getX() + ladder.getWidth(), -50);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Fall and Grab the platform
|
||||
object.setPosition(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getY() - 10
|
||||
);
|
||||
for (let i = 0; i < 6; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
fall(1);
|
||||
}
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// try to grab the ladder
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,400 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 8);
|
||||
|
||||
describe('(moving platforms)', function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
const maxSpeed = 500;
|
||||
const maxFallingSpeed = 1500;
|
||||
const timeDelta = 1 / 60;
|
||||
const maxDeltaX = maxSpeed * timeDelta;
|
||||
const maxDeltaY = maxFallingSpeed * timeDelta;
|
||||
|
||||
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: 900,
|
||||
maxFallingSpeed: maxFallingSpeed,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: maxSpeed,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -40);
|
||||
|
||||
// Put a platform.
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
it('follows a platform moving less than one pixel', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object follow the platform, even if the
|
||||
// movement is less than one pixel.
|
||||
platform.setX(platform.getX() + 0.12);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 0.12);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
platform.setX(platform.getX() + 0.12);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
expect(object.getX()).to.be(0.36);
|
||||
});
|
||||
|
||||
it('falls from a platform moving down faster than the maximum falling speed', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object falls
|
||||
// +1 because it's the margin to check the floor
|
||||
platform.setY(platform.getY() + maxDeltaY + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(true);
|
||||
expect(object.getY()).to.be.above(-30);
|
||||
});
|
||||
|
||||
// This test doesn't pass because the platform AABB are not always updated
|
||||
// before the platformer object moves.
|
||||
//
|
||||
// When the character is put on top of the platform to follow it up,
|
||||
// the platform AABB may not has updated in RBush
|
||||
// and the platform became out of the spacial search rectangle.
|
||||
it.skip('follows a platform that is slightly overlapping its top', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// the platform is slightly overlapping the top of the object
|
||||
platform.setY(object.getY() - platform.getHeight() + 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check that the object stays on the floor
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it('must not follow a platform that is moved over its top', function () {
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
|
||||
// Check the object has not moved.
|
||||
expect(object.getY()).to.be(-30);
|
||||
expect(object.getX()).to.be(0);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// move the platform over the object
|
||||
platform.setY(object.getY() - platform.getHeight());
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// A second step to make sure that the AABB is updated in RBush.
|
||||
// TODO this is a bug
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
// Check that the object falls
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getY()).to.be.above(-30);
|
||||
});
|
||||
|
||||
it('follows a moving platform when was grabbed to another', function () {
|
||||
const topPlatform = addPlatformObject(runtimeScene);
|
||||
topPlatform.setPosition(platform.getX() + 30, -50);
|
||||
|
||||
// Fall and Grab the platform
|
||||
object.setPosition(
|
||||
topPlatform.getX() - object.getWidth(),
|
||||
topPlatform.getY() - 10
|
||||
);
|
||||
|
||||
for (let i = 0; i < 9; ++i) {
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(
|
||||
true
|
||||
);
|
||||
}
|
||||
object.getBehavior('auto1').simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(true);
|
||||
|
||||
// move the bottom platform to the object
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// the platform reach the object
|
||||
expect(platform.getY()).to.be(object.getY() + object.getHeight());
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// the object follows it and no longer grab the other platform
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').isGrabbingPlatform()).to.be(false);
|
||||
});
|
||||
|
||||
// This may be a bug. Please, remove the skip if you fixed it.
|
||||
// It fails on the last 2 expect()
|
||||
it.skip('follows a moving platform when was grabbed to a ladder', function () {
|
||||
// object is 10 pixel higher than the platform and overlap the ladder
|
||||
object.setPosition(0, platform.getY() - object.getHeight() - 10);
|
||||
const ladder = addLadderObject(runtimeScene);
|
||||
ladder.setPosition(object.getX(), platform.getY() - ladder.getHeight());
|
||||
|
||||
// Fall and Grab the platform
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);
|
||||
object.getBehavior('auto1').simulateLadderKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
|
||||
// move the bottom platform to the object
|
||||
for (let i = 0; i < 20; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(true);
|
||||
}
|
||||
// the platform reach the object
|
||||
expect(platform.getY()).to.be(object.getY() + object.getHeight());
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setY(platform.getY() - 1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// the object follows it and no longer grab the other platform
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').isOnLadder()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
});
|
||||
|
||||
[-10, -10.1, -9.9].forEach((platformY) => {
|
||||
[
|
||||
-maxDeltaY + epsilon,
|
||||
maxDeltaY - epsilon,
|
||||
-10,
|
||||
10,
|
||||
-10.1,
|
||||
10.1,
|
||||
0,
|
||||
].forEach((deltaY) => {
|
||||
[-maxDeltaX, maxDeltaX, 0].forEach((deltaX) => {
|
||||
it(`follows the platform moving (${deltaX}; ${deltaY}) with initial Y = ${platformY}`, function () {
|
||||
platform.setPosition(platform.getX(), platformY);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getX()).to.be(0);
|
||||
// The object landed right on the platform
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object follow the platform, even if the
|
||||
// movement is less than one pixel.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setPosition(
|
||||
platform.getX() + deltaX,
|
||||
platform.getY() + deltaY
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
// The object follow the platform
|
||||
// The rounding error is probably due to a separate call.
|
||||
// TODO Try to make it exact or find why
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
}
|
||||
expect(object.getX()).to.be(0 + 5 * deltaX);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[false, true].forEach((useJumpthru) => {
|
||||
describe(`(${
|
||||
useJumpthru ? 'useJumpthru' : 'regular'
|
||||
} moving platforms)`, function () {
|
||||
let runtimeScene;
|
||||
let object;
|
||||
let platform;
|
||||
const maxSpeed = 500;
|
||||
const maxFallingSpeed = 1500;
|
||||
const timeDelta = 1 / 60;
|
||||
const maxDeltaX = maxSpeed * timeDelta;
|
||||
const maxDeltaY = maxFallingSpeed * timeDelta;
|
||||
|
||||
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: 900,
|
||||
maxFallingSpeed: maxFallingSpeed,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: maxSpeed,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
object.setPosition(0, -40);
|
||||
|
||||
// Put a platform.
|
||||
if (useJumpthru) {
|
||||
platform = addJumpThroughPlatformObject(runtimeScene);
|
||||
} else {
|
||||
platform = addPlatformObject(runtimeScene);
|
||||
}
|
||||
platform.setPosition(0, -10);
|
||||
});
|
||||
|
||||
// This test doesn't pass with jumpthru
|
||||
// because jumpthru that overlap the object are excluded from collision.
|
||||
// The probability it happens is: platform speed / falling speed.
|
||||
// We could use the Y speed to be more permissive about it:
|
||||
// If the previous position according to the speed is above the platform,
|
||||
// we could let it land.
|
||||
it.skip('can land to a platform that moved up and overlapped the object', function () {
|
||||
// Put the platform away so it won't collide with the falling object
|
||||
platform.setPosition(platform.getX(), 200);
|
||||
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const oldY = object.getY();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Put the platform under the falling object and overlap it a little
|
||||
// like a platform moving quickly can do
|
||||
platform.setPosition(
|
||||
platform.getX(),
|
||||
object.getY() + object.getHeight() - 2
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
|
||||
// Check the object has landed on the platform.
|
||||
expect(object.getX()).to.be(0);
|
||||
// The object must not be inside the platform or it gets stuck
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
});
|
||||
|
||||
[-10, -10.1, -9.9].forEach((platformY) => {
|
||||
[
|
||||
-maxDeltaY + epsilon,
|
||||
maxDeltaY - epsilon,
|
||||
-10,
|
||||
10,
|
||||
-10.1,
|
||||
10.1,
|
||||
0,
|
||||
].forEach((deltaY) => {
|
||||
[-maxDeltaX, maxDeltaX, 0].forEach((deltaX) => {
|
||||
it(`follows the platform moving (${deltaX}; ${deltaY}) with initial Y = ${platformY}`, function () {
|
||||
platform.setPosition(platform.getX(), platformY);
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
}
|
||||
// Check the object has not moved.
|
||||
expect(object.getX()).to.be(0);
|
||||
// The object must not be inside the platform or it gets stuck
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
|
||||
// Check that the object follow the platform, even if the
|
||||
// movement is less than one pixel.
|
||||
for (let i = 0; i < 5; ++i) {
|
||||
platform.setPosition(
|
||||
platform.getX() + deltaX,
|
||||
platform.getY() + deltaY
|
||||
);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getBehavior('auto1').isOnFloor()).to.be(true);
|
||||
expect(object.getBehavior('auto1').isFalling()).to.be(false);
|
||||
expect(object.getBehavior('auto1').isMoving()).to.be(false);
|
||||
// The object must not be inside the platform or it gets stuck
|
||||
expect(object.getY()).to.be.within(
|
||||
platform.getY() - object.getHeight() - epsilon,
|
||||
platform.getY() - object.getHeight() + epsilon
|
||||
);
|
||||
}
|
||||
expect(object.getX()).to.be(0 + 5 * deltaX);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
229
Extensions/PlatformBehavior/tests/PlatformerTestHelper.js
Normal file
229
Extensions/PlatformBehavior/tests/PlatformerTestHelper.js
Normal file
@@ -0,0 +1,229 @@
|
||||
|
||||
const makePlatformerTestRuntimeScene = () => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
resources: {
|
||||
resources: [],
|
||||
},
|
||||
properties: { windowWidth: 800, windowHeight: 600 },
|
||||
});
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [{ name: '', visibility: true, effects: [] }],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
instances: [],
|
||||
});
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return (1 / 60) * 1000;
|
||||
};
|
||||
return runtimeScene;
|
||||
};
|
||||
|
||||
const addPlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj2',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
platform.setCustomWidthAndHeight(60, 32);
|
||||
runtimeScene.addObject(platform);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addUpSlopePlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'slope',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 0 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 100);
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addDownSlopePlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'slope',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 0, y: 0 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 100);
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addTunnelPlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'slope',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 100, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 0, y: 200 },
|
||||
{ x: 0, y: 300 },
|
||||
{ x: 100, y: 300 },
|
||||
{ x: 100, y: 200 },
|
||||
],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 300);
|
||||
platform.setCustomWidthAndHeight(100, 300);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addJumpThroughPlatformObject = (runtimeScene) => {
|
||||
const platform = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj2',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
platformType: 'Jumpthru',
|
||||
canBeGrabbed: false,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
platform.setCustomWidthAndHeight(60, 32);
|
||||
runtimeScene.addObject(platform);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const addLadderObject = (runtimeScene) => {
|
||||
const ladder = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj3',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: false,
|
||||
platformType: 'Ladder',
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
ladder.setCustomWidthAndHeight(20, 60);
|
||||
runtimeScene.addObject(ladder);
|
||||
|
||||
return ladder;
|
||||
};
|
734
Extensions/PlatformBehavior/tests/SlopePlatformer.spec.js
Normal file
734
Extensions/PlatformBehavior/tests/SlopePlatformer.spec.js
Normal file
@@ -0,0 +1,734 @@
|
||||
describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
|
||||
const epsilon = 1 / (2 << 8);
|
||||
|
||||
describe('(walk on slopes)', 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: 60,
|
||||
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 walkRightCanStop = (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.not.be.below(lastX);
|
||||
}
|
||||
};
|
||||
|
||||
const walkLeft = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.below(lastX);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.below(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 that is rotated', function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setPosition(0, -10);
|
||||
|
||||
const platform2 = addPlatformObject(runtimeScene);
|
||||
|
||||
const angle = (-30 * Math.PI) / 180;
|
||||
const centerDeltaX = platform2.getWidth() / 2;
|
||||
const centerDeltaY = platform2.getHeight() / 2;
|
||||
// to make the vertex of the 2 platform touch
|
||||
const vertexDeltaX =
|
||||
centerDeltaX * Math.cos(angle) +
|
||||
centerDeltaY * -Math.sin(angle) -
|
||||
centerDeltaX;
|
||||
const vertexDeltaY =
|
||||
centerDeltaX * Math.sin(angle) +
|
||||
centerDeltaY * Math.cos(angle) -
|
||||
centerDeltaY;
|
||||
|
||||
platform2.setAngle(-30);
|
||||
platform2.setPosition(
|
||||
platform.getX() + platform.getWidth() + vertexDeltaX,
|
||||
platform.getY() + vertexDeltaY
|
||||
);
|
||||
|
||||
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());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(platform.getY());
|
||||
});
|
||||
|
||||
[26, 45].forEach((slopeAngle) => {
|
||||
it(`can go uphill from a 0° slope to a ${slopeAngle}° slope going right`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
// This is a mirror of the previous test.
|
||||
it(`can go uphill from a 0° slope to a ${slopeAngle}° slope going left`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(50, 0);
|
||||
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(
|
||||
platform.getX() - slope.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(90, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkLeft(30);
|
||||
expect(object.getX()).to.be.below(platform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it(`can go uphill from a ${slopeAngle}° slope to a 0° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(slope.getX() + slope.getWidth(), slope.getY());
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it(`can go uphill from a ${slopeAngle}° slope to a 0° jump through platform`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
jumpThroughPlatform.setCustomWidthAndHeight(50, 50);
|
||||
jumpThroughPlatform.setPosition(
|
||||
slope.getX() + slope.getWidth(),
|
||||
slope.getY()
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be(
|
||||
jumpThroughPlatform.getY() - object.getHeight()
|
||||
);
|
||||
});
|
||||
|
||||
[
|
||||
[26, 45],
|
||||
[45, 26],
|
||||
[26, 26],
|
||||
[45, 45],
|
||||
].forEach((slopeAngles) => {
|
||||
it(`can go uphill from a ${slopeAngles[0]}° slope to a ${slopeAngles[1]}° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope1 = addUpSlopePlatformObject(runtimeScene);
|
||||
slope1.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[0]].width,
|
||||
slopesDimensions[slopeAngles[0]].height
|
||||
);
|
||||
slope1.setPosition(0, 0);
|
||||
|
||||
const slope2 = addUpSlopePlatformObject(runtimeScene);
|
||||
slope2.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[1]].width,
|
||||
slopesDimensions[slopeAngles[1]].height
|
||||
);
|
||||
slope2.setPosition(
|
||||
slope1.getX() + slope1.getWidth(),
|
||||
slope1.getY() - slope2.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope2.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be.below(slope1.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
|
||||
// TODO
|
||||
it.skip(`can go uphill from a 26° slope and be stopped by an obstacle on the head`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(100, 50);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const ceiling = addPlatformObject(runtimeScene);
|
||||
ceiling.setCustomWidthAndHeight(50, 50);
|
||||
ceiling.setPosition(
|
||||
50,
|
||||
slope.getY() - ceiling.getHeight() - object.getHeight() / 2
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk the slope and reach the ceiling.
|
||||
// It checks that the character never go left.
|
||||
walkRightCanStop(40);
|
||||
expect(object.getY()).to.be(ceiling.getY() + ceiling.getHeight());
|
||||
});
|
||||
|
||||
[26, 45].forEach((slopeAngle) => {
|
||||
it(`can go downhill from a 0° slope to a ${slopeAngle}° slope`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope.getX());
|
||||
// Gone downward following the 2nd platform.
|
||||
expect(object.getY()).to.be.above(slope.getY() - object.getHeight());
|
||||
});
|
||||
|
||||
it(`can go downhill from a ${slopeAngle}° slope to a 0° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngle].width,
|
||||
slopesDimensions[slopeAngle].height
|
||||
);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(
|
||||
slope.getX() + slope.getWidth(),
|
||||
slope.getY() + slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(platform.getX());
|
||||
// Gone downward following the 2nd platform.
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
[26, 45],
|
||||
[45, 26],
|
||||
[26, 26],
|
||||
[45, 45],
|
||||
].forEach((slopeAngles) => {
|
||||
it(`can go downhill from a ${slopeAngles[0]}° slope to a ${slopeAngles[1]}° slope`, function () {
|
||||
// Put a platform.
|
||||
const slope1 = addDownSlopePlatformObject(runtimeScene);
|
||||
slope1.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[0]].width,
|
||||
slopesDimensions[slopeAngles[0]].height
|
||||
);
|
||||
slope1.setPosition(0, 0);
|
||||
|
||||
const slope2 = addDownSlopePlatformObject(runtimeScene);
|
||||
slope2.setCustomWidthAndHeight(
|
||||
slopesDimensions[slopeAngles[1]].width,
|
||||
slopesDimensions[slopeAngles[1]].height
|
||||
);
|
||||
slope2.setPosition(
|
||||
slope1.getX() + slope1.getWidth(),
|
||||
slope1.getY() + slope1.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(11);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(30);
|
||||
expect(object.getX()).to.be.above(slope2.getX());
|
||||
// Gone downward following the 2nd platform.
|
||||
expect(object.getY()).to.be.above(slope2.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('(walk on slopes very fast)', 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: 100000,
|
||||
deceleration: 1500,
|
||||
// It will move more than 1 width every frame
|
||||
maxSpeed: 1000, // fps * width = 60 * 10 = 600 plus a big margin
|
||||
jumpSpeed: 900,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
jumpSustainTime: 0.2,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
object.setCustomWidthAndHeight(10, 20);
|
||||
runtimeScene.addObject(object);
|
||||
});
|
||||
|
||||
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.not.be.below(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);
|
||||
};
|
||||
|
||||
// TODO When the object is moving fast, sharp platform vertices can be missed.
|
||||
// Fixing this is would require to rethink how the floor is followed.
|
||||
// But, this might be an extreme enough case to don't care:
|
||||
// On a 800 width screen, a 32 width character would go through one screen in 400ms.
|
||||
// 800 / 32 / 60 = 0.416
|
||||
it.skip(`can go uphill from a 45° slope to a 0° jump through platform`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, 50);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
const jumpThroughPlatform = addJumpThroughPlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, 50);
|
||||
jumpThroughPlatform.setPosition(
|
||||
slope.getX() + slope.getWidth(),
|
||||
slope.getY()
|
||||
);
|
||||
|
||||
object.setPosition(0, -5);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(12);
|
||||
|
||||
// Walk from the 1st platform to the 2nd one.
|
||||
walkRight(6);
|
||||
expect(object.getX()).to.be.above(jumpThroughPlatform.getX());
|
||||
// Gone upward following the 2nd platform.
|
||||
expect(object.getY()).to.be(
|
||||
jumpThroughPlatform.getY() - object.getHeight()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[0, 25].forEach((slopeMaxAngle) => {
|
||||
describe(`(walk on slopes, 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 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 walkRightCanStop = (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.not.be.below(lastX);
|
||||
}
|
||||
};
|
||||
|
||||
const walkLeftCanStop = (frameCount) => {
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < frameCount; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateLeftKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.not.be.above(lastX);
|
||||
}
|
||||
};
|
||||
|
||||
(slopeMaxAngle === 0
|
||||
? [
|
||||
{ angle: 5.7, height: 5 },
|
||||
{ angle: 26, height: 25 },
|
||||
]
|
||||
: // slopeMaxAngle === 25
|
||||
[{ angle: 26, height: 25 }]
|
||||
).forEach((slopesDimension) => {
|
||||
it(`can't go uphill on a too steep slope (${slopesDimension.angle}°)`, function () {
|
||||
// Put a platform.
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
object.setPosition(0, -10);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(20);
|
||||
const fallX = object.getX();
|
||||
const fallY = object.getY();
|
||||
|
||||
// Stay still when Right is pressed
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isOnFloor()).to.be(true);
|
||||
expect(object.getX()).to.be.within(
|
||||
fallX - epsilon,
|
||||
fallX + epsilon
|
||||
);
|
||||
expect(object.getY()).to.be.within(
|
||||
fallY - epsilon,
|
||||
fallY + epsilon
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it(`can go downhill on a too steep slope (${slopesDimension.angle}°)`, function () {
|
||||
// Put a platform.
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(0, 0);
|
||||
|
||||
object.setPosition(0, -60);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(20);
|
||||
const fallX = object.getX();
|
||||
const fallY = object.getY();
|
||||
|
||||
// Fall and land on the platform in loop when Right is pressed
|
||||
const behavior = object.getBehavior('auto1');
|
||||
for (let i = 0; i < 10; ++i) {
|
||||
const lastX = object.getX();
|
||||
const lastY = object.getY();
|
||||
const lastSpeed = behavior.getCurrentSpeed();
|
||||
behavior.simulateRightKey();
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(
|
||||
behavior.isOnFloor() || behavior.isFallingWithoutJumping()
|
||||
).to.be(true);
|
||||
expect(object.getX()).to.be.above(lastX);
|
||||
expect(object.getY()).to.be.above(lastY);
|
||||
// Check that the object doesn't stop
|
||||
expect(behavior.getCurrentSpeed()).to.be.above(lastSpeed);
|
||||
}
|
||||
});
|
||||
|
||||
// The log of the character positions moving to the right
|
||||
// without any obstacle:
|
||||
// LOG: 'OnFloor 35.13888888888889 -20'
|
||||
// LOG: 'OnFloor 38.333333333333336 -20'
|
||||
// LOG: 'OnFloor 41.66666666666667 -20'
|
||||
// The character is 10 width, at 38.33 is left is 48.33
|
||||
[
|
||||
// remainingDeltaX === 1.333
|
||||
47,
|
||||
// remainingDeltaX === 0.833
|
||||
47.5,
|
||||
// remainingDeltaX === 0.333
|
||||
48,
|
||||
// remainingDeltaX is big
|
||||
49,
|
||||
// Platform tiles will result to pixel aligned junctions.
|
||||
// A rotated platform will probably result to not pixel aligned junctions.
|
||||
48.9,
|
||||
].forEach((slopeJunctionX) => {
|
||||
it(`(slopeJunctionX: ${slopeJunctionX}) can't go uphill from a 0° slope to a too steep slope (${slopesDimension.angle}°) going right`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(slopeJunctionX, 50);
|
||||
platform.setPosition(0, 0);
|
||||
|
||||
const slope = addUpSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(
|
||||
platform.getX() + platform.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(0, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk toward the 2nd platform.
|
||||
walkRightCanStop(30);
|
||||
// Is stopped at the slope junction.
|
||||
expect(object.getX()).to.be.within(
|
||||
Math.floor(slope.getX()) - object.getWidth(),
|
||||
// When the junction is not pixel aligned, the character will be stopped
|
||||
// but is able to move forward until it reaches the obstacle.
|
||||
slope.getX() - object.getWidth()
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
|
||||
// The log of the character positions moving to the left
|
||||
// without any obstacle:
|
||||
// LOG: 'OnFloor 54.861111111111114 -20'
|
||||
// LOG: 'OnFloor 51.66666666666667 -20'
|
||||
// LOG: 'OnFloor 48.333333333333336 -20'
|
||||
// This is a mirror of the previous case: x -> 100 - x
|
||||
[
|
||||
// remainingDeltaX === -1.333
|
||||
53,
|
||||
// remainingDeltaX === -0.833
|
||||
52.5,
|
||||
// remainingDeltaX === -0.333
|
||||
52,
|
||||
// remainingDeltaX is big
|
||||
51,
|
||||
// Platform tiles will result to pixel aligned junctions.
|
||||
// A rotated platform will probably result to not pixel aligned junctions.
|
||||
51.1,
|
||||
].forEach((slopeJunctionX) => {
|
||||
it(`(slopeJunctionX: ${slopeJunctionX}) can't go uphill from a 0° slope to a too steep slope (${slopesDimension.angle}°) going left`, function () {
|
||||
// Put a platform.
|
||||
const platform = addPlatformObject(runtimeScene);
|
||||
platform.setCustomWidthAndHeight(100 - slopeJunctionX, 50);
|
||||
platform.setPosition(slopeJunctionX, 0);
|
||||
|
||||
const slope = addDownSlopePlatformObject(runtimeScene);
|
||||
slope.setCustomWidthAndHeight(50, slopesDimension.height);
|
||||
slope.setPosition(
|
||||
slopeJunctionX - slope.getWidth(),
|
||||
platform.getY() - slope.getHeight()
|
||||
);
|
||||
|
||||
object.setPosition(90, -32);
|
||||
// Ensure the object falls on the platform
|
||||
fallOnPlatform(10);
|
||||
|
||||
// Walk toward the 2nd platform.
|
||||
walkLeftCanStop(30);
|
||||
// Is stopped at the slope junction.
|
||||
expect(object.getX()).to.be.within(
|
||||
// When the junction is not pixel aligned, the character will be stopped
|
||||
// but is able to move forward until it reaches the obstacle.
|
||||
platform.getX(),
|
||||
Math.ceil(platform.getX())
|
||||
);
|
||||
expect(object.getY()).to.be(platform.getY() - object.getHeight());
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,604 @@
|
||||
describe(`gdjs.PlatformerObjectRuntimeBehavior.findHighestFloorAndMoveOnTop`, function () {
|
||||
const makeTestRuntimeScene = () => {
|
||||
const runtimeGame = new gdjs.RuntimeGame({
|
||||
variables: [],
|
||||
resources: {
|
||||
resources: [],
|
||||
},
|
||||
properties: { windowWidth: 800, windowHeight: 600 },
|
||||
});
|
||||
const runtimeScene = new gdjs.RuntimeScene(runtimeGame);
|
||||
runtimeScene.loadFromScene({
|
||||
layers: [{ name: '', visibility: true, effects: [] }],
|
||||
variables: [],
|
||||
behaviorsSharedData: [],
|
||||
objects: [],
|
||||
instances: [],
|
||||
});
|
||||
runtimeScene._timeManager.getElapsedTime = function () {
|
||||
return (1 / 60) * 1000;
|
||||
};
|
||||
return runtimeScene;
|
||||
};
|
||||
|
||||
const addCharacter = (runtimeScene) => {
|
||||
const character = new gdjs.TestRuntimeObject(runtimeScene, {
|
||||
name: 'obj1',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformerObjectBehavior',
|
||||
name: 'auto1',
|
||||
gravity: 900,
|
||||
maxFallingSpeed: 1500,
|
||||
acceleration: 500,
|
||||
deceleration: 1500,
|
||||
maxSpeed: 500,
|
||||
jumpSpeed: 1500,
|
||||
canGrabPlatforms: true,
|
||||
ignoreDefaultControls: true,
|
||||
slopeMaxAngle: 60,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
});
|
||||
character.setCustomWidthAndHeight(100, 100);
|
||||
runtimeScene.addObject(character);
|
||||
return character;
|
||||
};
|
||||
|
||||
const addPlatform = (runtimeScene, collisionMask) => {
|
||||
const platform = new gdjs.TestSpriteRuntimeObject(runtimeScene, {
|
||||
name: 'platform',
|
||||
type: '',
|
||||
behaviors: [
|
||||
{
|
||||
type: 'PlatformBehavior::PlatformBehavior',
|
||||
name: 'Platform',
|
||||
canBeGrabbed: true,
|
||||
},
|
||||
],
|
||||
effects: [],
|
||||
animations: [
|
||||
{
|
||||
name: 'animation',
|
||||
directions: [
|
||||
{
|
||||
sprites: [
|
||||
{
|
||||
originPoint: { x: 0, y: 0 },
|
||||
centerPoint: { x: 50, y: 50 },
|
||||
points: [
|
||||
{ name: 'Center', x: 0, y: 0 },
|
||||
{ name: 'Origin', x: 50, y: 50 },
|
||||
],
|
||||
hasCustomCollisionMask: true,
|
||||
customCollisionMask: collisionMask,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
runtimeScene.addObject(platform);
|
||||
platform.setUnscaledWidthAndHeight(100, 100);
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
|
||||
return platform;
|
||||
};
|
||||
|
||||
const checkMoveOn = (
|
||||
characterBehavior,
|
||||
platformBehavior,
|
||||
upwardDeltaY,
|
||||
downwardDeltaY
|
||||
) => {
|
||||
const result = characterBehavior._findHighestFloorAndMoveOnTop(
|
||||
[platformBehavior],
|
||||
upwardDeltaY,
|
||||
downwardDeltaY
|
||||
);
|
||||
expect(result.highestGround).to.be(platformBehavior);
|
||||
};
|
||||
|
||||
const checkNoFloor = (
|
||||
characterBehavior,
|
||||
platformBehavior,
|
||||
upwardDeltaY,
|
||||
downwardDeltaY
|
||||
) => {
|
||||
const oldY = characterBehavior.owner.getY();
|
||||
const result = characterBehavior._findHighestFloorAndMoveOnTop(
|
||||
[platformBehavior],
|
||||
upwardDeltaY,
|
||||
downwardDeltaY
|
||||
);
|
||||
expect(result.highestGround).to.be(null);
|
||||
expect(result.isCollidingAnyPlatform).to.be(false);
|
||||
expect(characterBehavior.owner.getY()).to.be(oldY);
|
||||
};
|
||||
|
||||
const checkObstacle = (
|
||||
characterBehavior,
|
||||
platformBehavior,
|
||||
upwardDeltaY,
|
||||
downwardDeltaY
|
||||
) => {
|
||||
const oldY = characterBehavior.owner.getY();
|
||||
const result = characterBehavior._findHighestFloorAndMoveOnTop(
|
||||
[platformBehavior],
|
||||
upwardDeltaY,
|
||||
downwardDeltaY
|
||||
);
|
||||
expect(result.highestGround).to.be(null);
|
||||
expect(result.isCollidingAnyPlatform).to.be(true);
|
||||
expect(characterBehavior.owner.getY()).to.be(oldY);
|
||||
};
|
||||
|
||||
const noCollision = gdjs.PlatformerObjectRuntimeBehavior._noCollision;
|
||||
const floorIsTooHigh = gdjs.PlatformerObjectRuntimeBehavior._floorIsTooHigh;
|
||||
|
||||
[false, true].forEach((swapVerticesOrder) => {
|
||||
describe(`(swapVertexOrder: ${swapVerticesOrder})`, function () {
|
||||
const collisionMasks = {
|
||||
square: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 0, y: 100 },
|
||||
],
|
||||
],
|
||||
bottomLeftTriangle: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 100 },
|
||||
],
|
||||
],
|
||||
bottomRightTriangle: [
|
||||
[
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 100 },
|
||||
],
|
||||
],
|
||||
topRightTriangle: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 100, y: 100 },
|
||||
],
|
||||
],
|
||||
topLeftTriangle: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
],
|
||||
],
|
||||
topLeftTriangleWithLowEdge: [
|
||||
[
|
||||
{ x: -1, y: 100 },
|
||||
{ x: -1, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
],
|
||||
],
|
||||
bottomTriangle: [
|
||||
[
|
||||
{ x: 50, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 100 },
|
||||
],
|
||||
],
|
||||
topTriangle: [
|
||||
[
|
||||
{ x: 50, y: 100 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
],
|
||||
],
|
||||
leftTriangle: [
|
||||
[
|
||||
{ x: 100, y: 50 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
],
|
||||
],
|
||||
rightTriangle: [
|
||||
[
|
||||
{ x: 0, y: 50 },
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 100, y: 100 },
|
||||
],
|
||||
],
|
||||
horizontalTunnel: [
|
||||
[
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 25 },
|
||||
{ x: 100, y: 25 },
|
||||
{ x: 100, y: 0 },
|
||||
],
|
||||
[
|
||||
{ x: 0, y: 75 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 100, y: 75 },
|
||||
],
|
||||
],
|
||||
verticalTunnel: [
|
||||
[
|
||||
{ x: 25, y: 0 },
|
||||
{ x: 0, y: 0 },
|
||||
{ x: 0, y: 100 },
|
||||
{ x: 25, y: 100 },
|
||||
],
|
||||
[
|
||||
{ x: 75, y: 0 },
|
||||
{ x: 100, y: 0 },
|
||||
{ x: 100, y: 100 },
|
||||
{ x: 75, y: 100 },
|
||||
],
|
||||
],
|
||||
};
|
||||
if (swapVerticesOrder) {
|
||||
for (const key in collisionMasks) {
|
||||
if (Object.hasOwnProperty.call(collisionMasks, key)) {
|
||||
collisionMasks[key] = collisionMasks[key].reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[
|
||||
{
|
||||
description: '(An edge crossing from the left to the right)',
|
||||
mask: collisionMasks.square,
|
||||
position: [200, -100],
|
||||
},
|
||||
{
|
||||
description: '(A vertex inside and edges crossing at the bottom)',
|
||||
mask: collisionMasks.bottomTriangle,
|
||||
position: [200, -100],
|
||||
},
|
||||
{
|
||||
description: '(An edge crossing from the left to the bottom)',
|
||||
mask: collisionMasks.bottomLeftTriangle,
|
||||
position: [200, -200],
|
||||
},
|
||||
{
|
||||
description:
|
||||
'(An edge crossing from the left to the bottom with the vertex right on the left border)',
|
||||
mask: collisionMasks.bottomLeftTriangle,
|
||||
position: [300, -100],
|
||||
},
|
||||
{
|
||||
description: '(An edge crossing from the right to the bottom)',
|
||||
mask: collisionMasks.bottomRightTriangle,
|
||||
position: [200, -200],
|
||||
},
|
||||
{
|
||||
description:
|
||||
'(An edge crossing from the right to the bottom with the vertex right on the right border)',
|
||||
mask: collisionMasks.bottomRightTriangle,
|
||||
position: [100, -100],
|
||||
},
|
||||
{
|
||||
description: '(A vertex inside and edges crossing at the left)',
|
||||
mask: collisionMasks.leftTriangle,
|
||||
position: [1, -249.5],
|
||||
},
|
||||
{
|
||||
description: '(A vertex inside and edges crossing at the right)',
|
||||
mask: collisionMasks.rightTriangle,
|
||||
position: [399, -249.5],
|
||||
},
|
||||
].forEach(({ description, mask, position }) => {
|
||||
describe(description, function () {
|
||||
const runtimeScene = makeTestRuntimeScene();
|
||||
const character = addCharacter(runtimeScene);
|
||||
const behavior = character.getBehavior('auto1');
|
||||
|
||||
const platform = addPlatform(runtimeScene, mask);
|
||||
platform.setCustomWidthAndHeight(300, 300);
|
||||
platform.setPosition(position[0], position[1]);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
|
||||
it('can detect a platform away downward', function () {
|
||||
character.setPosition(300, -210.1);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect a floor to follow down', function () {
|
||||
character.setPosition(300, -210);
|
||||
checkMoveOn(behavior, platformBehavior, -10, 10);
|
||||
expect(character.getY()).to.be(-200);
|
||||
});
|
||||
|
||||
it('can detect a floor when right on it', function () {
|
||||
character.setPosition(300, -200);
|
||||
checkMoveOn(behavior, platformBehavior, -10, 10);
|
||||
expect(character.getY()).to.be(-200);
|
||||
});
|
||||
|
||||
it('can detect a floor to follow up', function () {
|
||||
character.setPosition(300, -190);
|
||||
checkMoveOn(behavior, platformBehavior, -10, 10);
|
||||
expect(character.getY()).to.be(-200);
|
||||
});
|
||||
|
||||
it('can detect an obstacle a bit too high to follow', function () {
|
||||
character.setPosition(300, -189.9);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
description: '(An edge crossing from the left to the right)',
|
||||
mask: collisionMasks.square,
|
||||
position: [200, -100],
|
||||
},
|
||||
{
|
||||
description: '(A vertex inside and edges crossing at the top)',
|
||||
mask: collisionMasks.topTriangle,
|
||||
position: [200, -100],
|
||||
},
|
||||
{
|
||||
description: '(An edge crossing from the left to the top)',
|
||||
mask: collisionMasks.topRightTriangle,
|
||||
position: [200, 0],
|
||||
},
|
||||
{
|
||||
description: '(An edge crossing from the right to the top)',
|
||||
mask: collisionMasks.topLeftTriangle,
|
||||
position: [200, 0],
|
||||
},
|
||||
{
|
||||
description: '(An edge crossing from the right to the top)',
|
||||
// An edge will be lower than the character (but not under).
|
||||
mask: collisionMasks.topLeftTriangleWithLowEdge,
|
||||
position: [180, 20],
|
||||
},
|
||||
].forEach(({ description, mask, position }) => {
|
||||
describe(description, function () {
|
||||
const runtimeScene = makeTestRuntimeScene();
|
||||
const character = addCharacter(runtimeScene);
|
||||
const behavior = character.getBehavior('auto1');
|
||||
|
||||
const platform = addPlatform(runtimeScene, mask);
|
||||
platform.setCustomWidthAndHeight(300, 300);
|
||||
platform.setPosition(position[0], position[1]);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
|
||||
it('can detect an obstacle overlapping the top', function () {
|
||||
// -10 because the character can follow a platform downward.
|
||||
character.setPosition(300, 199.9 - 10);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect a platform away downward', function () {
|
||||
character.setPosition(300, 200 - 10);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('(A platform with an hitbox under and another one above)', function () {
|
||||
const runtimeScene = makeTestRuntimeScene();
|
||||
const character = addCharacter(runtimeScene);
|
||||
const behavior = character.getBehavior('auto1');
|
||||
|
||||
const platform = addPlatform(
|
||||
runtimeScene,
|
||||
collisionMasks.horizontalTunnel
|
||||
);
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(250, -250);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
|
||||
it('can detect a tunnel ceiling', function () {
|
||||
character.setPosition(300, -210.1);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
// The character won't collide the ceiling anymore when he follows the floor.
|
||||
// So, the ceiling should not be seen as an obstacle.
|
||||
// This can happen if a tunnel is going down
|
||||
it('can detect a floor to follow down', function () {
|
||||
character.setPosition(300, -210);
|
||||
checkMoveOn(behavior, platformBehavior, -10, 10);
|
||||
expect(character.getY()).to.be(-200);
|
||||
});
|
||||
|
||||
it('can detect a floor when right on it', function () {
|
||||
character.setPosition(300, -200);
|
||||
checkMoveOn(behavior, platformBehavior, -10, 10);
|
||||
expect(character.getY()).to.be(-200);
|
||||
});
|
||||
|
||||
it('can detect a floor to follow up', function () {
|
||||
character.setPosition(300, -190);
|
||||
checkMoveOn(behavior, platformBehavior, -10, 10);
|
||||
expect(character.getY()).to.be(-200);
|
||||
});
|
||||
|
||||
it('can detect an obstacle a bit too high to follow', function () {
|
||||
character.setPosition(300, -189.9);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect a too thin horizontal tunnel', function () {
|
||||
platform.setCustomWidthAndHeight(200, 199.8);
|
||||
platform.setPosition(250, -250.1);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(A platform with an hitbox on the left and another one on the right)', function () {
|
||||
const runtimeScene = makeTestRuntimeScene();
|
||||
const character = addCharacter(runtimeScene);
|
||||
const behavior = character.getBehavior('auto1');
|
||||
|
||||
const platform = addPlatform(
|
||||
runtimeScene,
|
||||
collisionMasks.verticalTunnel
|
||||
);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
|
||||
it('can fell inside a vertical tunnel that fit the character', function () {
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(250, -250);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can fell inside a vertical tunnel', function () {
|
||||
platform.setCustomWidthAndHeight(200.2, 200);
|
||||
platform.setPosition(249.9, -250);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect a too thin vertical tunnel', function () {
|
||||
platform.setCustomWidthAndHeight(199.8, 200);
|
||||
platform.setPosition(250.1, -250);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('(A platform sharing a vertex with the character})', function () {
|
||||
const runtimeScene = makeTestRuntimeScene();
|
||||
const character = addCharacter(runtimeScene);
|
||||
const behavior = character.getBehavior('auto1');
|
||||
|
||||
const platform = addPlatform(runtimeScene, collisionMasks.square);
|
||||
const platformBehavior = platform.getBehavior('Platform');
|
||||
|
||||
it('can detect a platform at its exact position', function () {
|
||||
platform.setCustomWidthAndHeight(100, 100);
|
||||
platform.setPosition(300, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassing platform sharing the top left corner', function () {
|
||||
// Shared vertex at (300, -200)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(300, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassed platform sharing the top left corner', function () {
|
||||
// Shared vertex at (300, -200)
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(300, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassing platform sharing the top right corner', function () {
|
||||
// Shared vertex at (400, -200)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(200, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassed platform sharing the top right corner', function () {
|
||||
// Shared vertex at (400, -200)
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(350, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassing platform sharing the bottom left corner', function () {
|
||||
// Shared vertex at (300, -100)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(300, -300);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassed platform sharing the bottom left corner', function () {
|
||||
// Shared vertex at (300, -100)
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(300, -150);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassing platform sharing the bottom right corner', function () {
|
||||
// Shared vertex at (400, -100)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(200, -300);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can detect an encompassed platform sharing the bottom right corner', function () {
|
||||
// Shared vertex at (400, -100)
|
||||
platform.setCustomWidthAndHeight(50, 50);
|
||||
platform.setPosition(350, -150);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkObstacle(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can be next to a platform sharing the top left corner', function () {
|
||||
// Shared vertex at (300, -200)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(100, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can be next to a platform sharing the top right corner', function () {
|
||||
// Shared vertex at (400, -200)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(400, -200);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can be next to a platform sharing the bottom left corner', function () {
|
||||
// Shared vertex at (300, -100)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(100, -300);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
|
||||
it('can be next to a platform sharing the bottom right corner', function () {
|
||||
// Shared vertex at (400, -100)
|
||||
platform.setCustomWidthAndHeight(200, 200);
|
||||
platform.setPosition(400, -300);
|
||||
|
||||
character.setPosition(300, -200);
|
||||
checkNoFloor(behavior, platformBehavior, -10, 10);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
||||
namespace gdjs {
|
||||
export namespace screenshot {
|
||||
const logger = new gdjs.Logger('Screenshot');
|
||||
|
||||
/**
|
||||
* Save a screenshot of the game.
|
||||
* @param runtimeScene The scene
|
||||
@@ -22,13 +24,13 @@ namespace gdjs {
|
||||
}
|
||||
fileSystem.writeFile(savePath, content, 'base64', (err) => {
|
||||
if (err) {
|
||||
console.error(
|
||||
logger.error(
|
||||
'Unable to save the screenshot at path: ' + savePath
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error(
|
||||
logger.error(
|
||||
'Screenshot are not supported on rendering engines without canvas.'
|
||||
);
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Tilemap object');
|
||||
|
||||
/**
|
||||
* The PIXI.js renderer for the Tile map runtime object.
|
||||
*
|
||||
@@ -81,7 +83,7 @@ namespace gdjs {
|
||||
.getJsonManager()
|
||||
.loadJson(this._object._tilemapJsonFile, (error, tileMapJsonData) => {
|
||||
if (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
'An error happened while loading a Tilemap JSON data:',
|
||||
error
|
||||
);
|
||||
@@ -95,7 +97,7 @@ namespace gdjs {
|
||||
this._object._tilesetJsonFile,
|
||||
(error, tilesetJsonData) => {
|
||||
if (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
'An error happened while loading Tileset JSON data:',
|
||||
error
|
||||
);
|
||||
|
@@ -76,11 +76,19 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
updateXOffset(): void {
|
||||
this._tiledSprite.tilePosition.x = -this._object._xOffset;
|
||||
// Known PIXI.js issue, the coordinates should not exceed the width/height of the texture,
|
||||
// otherwise the texture will be pixelated over time.
|
||||
// See https://github.com/pixijs/pixijs/issues/7891#issuecomment-947549553
|
||||
this._tiledSprite.tilePosition.x =
|
||||
-this._object._xOffset % this._tiledSprite.texture.width;
|
||||
}
|
||||
|
||||
updateYOffset(): void {
|
||||
this._tiledSprite.tilePosition.y = -this._object._yOffset;
|
||||
// Known PIXI.js issue, the coordinates should not exceed the width/height of the texture,
|
||||
// otherwise the texture will be pixelated over time.
|
||||
// See https://github.com/pixijs/pixijs/issues/7891#issuecomment-947549553
|
||||
this._tiledSprite.tilePosition.y =
|
||||
-this._object._yOffset % this._tiledSprite.texture.height;
|
||||
}
|
||||
|
||||
setColor(rgbColor): void {
|
||||
|
@@ -1,22 +0,0 @@
|
||||
Copyright (c) 2013 Jeremy Kahn
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
571
Extensions/TweenBehavior/shifty.d.ts
vendored
Normal file
571
Extensions/TweenBehavior/shifty.d.ts
vendored
Normal file
@@ -0,0 +1,571 @@
|
||||
// Shifty.js 2.16.0 type definitions by arthuro555
|
||||
declare namespace shifty {
|
||||
// index.js
|
||||
|
||||
type easingFunction = (position: number) => number;
|
||||
type startFunction = (state: any, data?: any) => any;
|
||||
type finishFunction = (promisedData: shifty.promisedData) => any;
|
||||
/**
|
||||
* Gets called for every tick of the tween. This function is not called on the
|
||||
* final tick of the animation.
|
||||
*/
|
||||
type renderFunction = (
|
||||
state: any,
|
||||
data: any | undefined,
|
||||
timeElapsed: number
|
||||
) => any;
|
||||
type scheduleFunction = (callback: Function, timeout: number) => any;
|
||||
interface tweenConfig {
|
||||
/**
|
||||
* Starting position. If omitted, {@link * shifty.Tweenable#get} is used.
|
||||
*/
|
||||
from?: any;
|
||||
/**
|
||||
* Ending position. The keys of this Object should
|
||||
* match those of `to`.
|
||||
*/
|
||||
to?: any;
|
||||
/**
|
||||
* How many milliseconds to animate for.
|
||||
*/
|
||||
duration?: number;
|
||||
/**
|
||||
* How many milliseconds to wait before starting the
|
||||
* tween.
|
||||
*/
|
||||
delay?: number;
|
||||
/**
|
||||
* Executes when the tween begins.
|
||||
*/
|
||||
start?: shifty.startFunction;
|
||||
/**
|
||||
* Executes when the tween
|
||||
* completes. This will get overridden by {@link shifty.Tweenablethen } if that
|
||||
* is called, and it will not fire if {@link shifty.Tweenablecancel } is
|
||||
* called.
|
||||
*/
|
||||
finish?: shifty.finishFunction;
|
||||
/**
|
||||
* Executes on every tick. Shifty
|
||||
* assumes a [retained mode](https://en.wikipedia.org/wiki/Retained_mode)
|
||||
* rendering environment, which in practice means that `render` only gets
|
||||
* called when the tween state changes. Importantly, this means that `render`
|
||||
* is _not_ called when a tween is not animating (for instance, when it is
|
||||
* paused or waiting to start via the `delay` option). This works naturally
|
||||
* with DOM environments, but you may need to account for this design in more
|
||||
* custom environments such as `<canvas>`.
|
||||
*
|
||||
* Legacy property name: `step`.
|
||||
*/
|
||||
render?: shifty.renderFunction;
|
||||
/**
|
||||
* Easing curve name(s) or {@link shifty.easingFunction }(s) to apply
|
||||
* to the properties of the tween. If this is an Object, the keys should
|
||||
* correspond to `to`/`from`. You can learn more about this in the {@tutorial
|
||||
* easing-function-in-depth} tutorial.
|
||||
*/
|
||||
easing?: Record<string, easingFunction> | string | easingFunction;
|
||||
/**
|
||||
* Data that is passed to {@link * shifty.startFunction}, {@link shifty.renderFunction }, and {@link * shifty.promisedData}. Legacy property name: `attachment`.
|
||||
*/
|
||||
data?: any;
|
||||
/**
|
||||
* Promise constructor for when you want
|
||||
* to use Promise library or polyfill Promises in unsupported environments.
|
||||
*/
|
||||
promise?: Function;
|
||||
}
|
||||
type promisedData = {
|
||||
/**
|
||||
* The current state of the tween.
|
||||
*/
|
||||
state: any;
|
||||
/**
|
||||
* The `data` Object that the tween was configured with.
|
||||
*/
|
||||
data: any;
|
||||
/**
|
||||
* The {@link shifty.Tweenable } instance to
|
||||
* which the tween belonged.
|
||||
*/
|
||||
tweenable: Tweenable;
|
||||
};
|
||||
/**
|
||||
* Is called when a tween is created to determine if a filter is needed.
|
||||
* Filters are only added to a tween when it is created so that they are not
|
||||
* unnecessarily processed if they don't apply during an update tick.
|
||||
*/
|
||||
type doesApplyFilter = (tweenable: any) => boolean;
|
||||
/**
|
||||
* Is called when a tween is created. This should perform any setup needed by
|
||||
* subsequent per-tick calls to {@link shifty.beforeTween } and {@link * shifty.afterTween}.
|
||||
*/
|
||||
type tweenCreatedFilter = (tweenable: any) => any;
|
||||
/**
|
||||
* Is called right before a tween is processed in a tick.
|
||||
*/
|
||||
type beforeTweenFilter = (tweenable: any) => any;
|
||||
/**
|
||||
* Is called right after a tween is processed in a tick.
|
||||
*/
|
||||
type afterTweenFilter = (tweenable: any) => any;
|
||||
/**
|
||||
* An Object that contains functions that are called at key points in a tween's
|
||||
* lifecycle. Shifty can only process `Number`s internally, but filters can
|
||||
* expand support for any type of data. This is the mechanism that powers
|
||||
* [string interpolation]{@tutorial string-interpolation}.
|
||||
*/
|
||||
type filter = {
|
||||
/**
|
||||
* Is called when a tween is
|
||||
* created.
|
||||
*/
|
||||
doesApply: shifty.doesApplyFilter;
|
||||
/**
|
||||
* Is called when a tween is
|
||||
* created.
|
||||
*/
|
||||
tweenCreated: shifty.tweenCreatedFilter;
|
||||
/**
|
||||
* Is called right before a
|
||||
* tween starts.
|
||||
*/
|
||||
beforeTween: shifty.beforeTweenFilter;
|
||||
/**
|
||||
* Is called right after a tween
|
||||
* ends.
|
||||
*/
|
||||
afterTween: shifty.afterTweenFilter;
|
||||
};
|
||||
|
||||
// easing-functions.js
|
||||
export function linear(pos: number): number;
|
||||
export function easeInQuad(pos: any): number;
|
||||
export function easeOutQuad(pos: any): number;
|
||||
export function easeInOutQuad(pos: any): number;
|
||||
export function easeInCubic(pos: any): number;
|
||||
export function easeOutCubic(pos: any): number;
|
||||
export function easeInOutCubic(pos: any): number;
|
||||
export function easeInQuart(pos: any): number;
|
||||
export function easeOutQuart(pos: any): number;
|
||||
export function easeInOutQuart(pos: any): number;
|
||||
export function easeInQuint(pos: any): number;
|
||||
export function easeOutQuint(pos: any): number;
|
||||
export function easeInOutQuint(pos: any): number;
|
||||
export function easeInSine(pos: any): number;
|
||||
export function easeOutSine(pos: any): number;
|
||||
export function easeInOutSine(pos: any): number;
|
||||
export function easeInExpo(pos: any): number;
|
||||
export function easeOutExpo(pos: any): number;
|
||||
export function easeInOutExpo(pos: any): number;
|
||||
export function easeInCirc(pos: any): number;
|
||||
export function easeOutCirc(pos: any): number;
|
||||
export function easeInOutCirc(pos: any): number;
|
||||
export function easeOutBounce(pos: any): number;
|
||||
export function easeInBack(pos: any): number;
|
||||
export function easeOutBack(pos: any): number;
|
||||
export function easeInOutBack(pos: any): number;
|
||||
export function elastic(pos: any): number;
|
||||
export function swingFromTo(pos: any): number;
|
||||
export function swingFrom(pos: any): number;
|
||||
export function swingTo(pos: any): number;
|
||||
export function bounce(pos: any): number;
|
||||
export function bouncePast(pos: any): number;
|
||||
export function easeFromTo(pos: any): number;
|
||||
export function easeFrom(pos: any): number;
|
||||
export function easeTo(pos: any): number;
|
||||
|
||||
// bezier.js
|
||||
|
||||
export function setBezierFunction(
|
||||
name: string,
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
): any;
|
||||
export function unsetBezierFunction(name: string): boolean;
|
||||
|
||||
// interpolate.js
|
||||
|
||||
export function interpolate<T extends Object>(
|
||||
from: T,
|
||||
to: T,
|
||||
position: number,
|
||||
easing: Record<string, easingFunction> | string | easingFunction,
|
||||
delay?: number
|
||||
): T;
|
||||
|
||||
// scene.js
|
||||
|
||||
export class Scene {
|
||||
/**
|
||||
* The {@link shifty.Scene} class provides a way to control groups of {@link
|
||||
* shifty.Tweenable}s. It is lightweight, minimalistic, and meant to provide
|
||||
* performant {@link shifty.Tweenable} batch control that users of Shifty
|
||||
* might otherwise have to implement themselves. It is **not** a robust
|
||||
* timeline solution, and it does **not** provide utilities for sophisticated
|
||||
* animation sequencing or orchestration. If that is what you need for your
|
||||
* project, consider using a more robust tool such as
|
||||
* [Rekapi](http://jeremyckahn.github.io/rekapi/doc/) (a timeline layer built
|
||||
* on top of Shifty).
|
||||
*
|
||||
* Please be aware that {@link shifty.Scene} does **not** perform any
|
||||
* automatic cleanup. If you want to remove a {@link shifty.Tweenable} from a
|
||||
* {@link shifty.Scene}, you must do so explicitly with either {@link
|
||||
* shifty.Scene#remove} or {@link shifty.Scene#empty}.
|
||||
*
|
||||
* <p class="codepen" data-height="677" data-theme-id="0" data-default-tab="js,result" data-user="jeremyckahn" data-slug-hash="qvZKbe" style="height: 677px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid black; margin: 1em 0; padding: 1em;" data-pen-title="Shifty Scene Demo">
|
||||
* <span>See the Pen <a href="https://codepen.io/jeremyckahn/pen/qvZKbe/">
|
||||
* Shifty Scene Demo</a> by Jeremy Kahn (<a href="https://codepen.io/jeremyckahn">@jeremyckahn</a>)
|
||||
* on <a href="https://codepen.io">CodePen</a>.</span>
|
||||
* </p>
|
||||
* <script async src="https://static.codepen.io/assets/embed/ei.js"></script>
|
||||
* @param {...shifty.Tweenable} tweenables
|
||||
* @see https://codepen.io/jeremyckahn/pen/qvZKbe
|
||||
* @constructs shifty.Scene
|
||||
*/
|
||||
constructor(...tweenables: Tweenable[]);
|
||||
|
||||
/**
|
||||
* A copy of the internal {@link shifty.Tweenable}s array.
|
||||
* @member shifty.Scene#tweenables
|
||||
* @type {Array.<shifty.Tweenable>}
|
||||
* @readonly
|
||||
*/
|
||||
get tweenables(): Tweenable[];
|
||||
|
||||
/**
|
||||
* The {@link external:Promise}s for all {@link shifty.Tweenable}s in this
|
||||
* {@link shifty.Scene} that have been configured with {@link
|
||||
* shifty.Tweenable#setConfig}. Note that each call of {@link
|
||||
* shifty.Scene#play} or {@link shifty.Scene#pause} creates new {@link
|
||||
* external:Promise}s:
|
||||
*
|
||||
* const scene = new Scene(new Tweenable());
|
||||
* scene.play();
|
||||
*
|
||||
* Promise.all(scene.promises).then(() =>
|
||||
* // Plays the scene again upon completion, but a new promise is
|
||||
* // created so this line only runs once.
|
||||
* scene.play()
|
||||
* );
|
||||
*
|
||||
* @member shifty.Scene#promises
|
||||
* @type {Array.<external:Promise>}
|
||||
* @readonly
|
||||
*/
|
||||
get promises(): Promise<Object>[];
|
||||
|
||||
/**
|
||||
* Add a {@link shifty.Tweenable} to be controlled by this {@link
|
||||
* shifty.Scene}.
|
||||
* @method shifty.Scene#add
|
||||
* @param {shifty.Tweenable} tweenable
|
||||
* @return {shifty.Tweenable} The {@link shifty.Tweenable} that was added.
|
||||
*/
|
||||
add(tweenable: Tweenable): Tweenable;
|
||||
|
||||
/**
|
||||
* Remove a {@link shifty.Tweenable} that is controlled by this {@link
|
||||
* shifty.Scene}.
|
||||
* @method shifty.Scene#remove
|
||||
* @param {shifty.Tweenable} tweenable
|
||||
* @return {shifty.Tweenable} The {@link shifty.Tweenable} that was removed.
|
||||
*/
|
||||
remove(tweenable: Tweenable): Tweenable;
|
||||
|
||||
/**
|
||||
* [Remove]{@link shifty.Scene#remove} all {@link shifty.Tweenable}s in this {@link
|
||||
* shifty.Scene}.
|
||||
* @method shifty.Scene#empty
|
||||
* @return {Array.<shifty.Tweenable>} The {@link shifty.Tweenable}s that were
|
||||
* removed.
|
||||
*/
|
||||
empty(): Array<Tweenable>;
|
||||
|
||||
/**
|
||||
* Is `true` if any {@link shifty.Tweenable} in this {@link shifty.Scene} is
|
||||
* playing.
|
||||
* @method shifty.Scene#isPlaying
|
||||
* @return {boolean}
|
||||
*/
|
||||
isPlaying(): boolean;
|
||||
|
||||
/**
|
||||
* Play all {@link shifty.Tweenable}s from their beginning.
|
||||
* @method shifty.Scene#play
|
||||
* @return {shifty.Scene}
|
||||
*/
|
||||
play(): Scene;
|
||||
|
||||
/**
|
||||
* {@link shifty.Tweenable#pause} all {@link shifty.Tweenable}s in this
|
||||
* {@link shifty.Scene}.
|
||||
* @method shifty.Scene#pause
|
||||
* @return {shifty.Scene}
|
||||
*/
|
||||
pause(): Scene;
|
||||
|
||||
/**
|
||||
* {@link shifty.Tweenable#resume} all paused {@link shifty.Tweenable}s.
|
||||
* @method shifty.Scene#resume
|
||||
* @return {shifty.Scene}
|
||||
*/
|
||||
resume(): Scene;
|
||||
|
||||
/**
|
||||
* {@link shifty.Tweenable#stop} all {@link shifty.Tweenable}s in this {@link
|
||||
* shifty.Scene}.
|
||||
* @method shifty.Scene#stop
|
||||
* @param {boolean} [gotoEnd]
|
||||
* @return {shifty.Scene}
|
||||
*/
|
||||
stop(gotoEnd?: boolean): Scene;
|
||||
}
|
||||
|
||||
// tweenable.js
|
||||
|
||||
/**
|
||||
* @method shifty.tween
|
||||
* @param {shifty.tweenConfig} [config={}]
|
||||
* @description Standalone convenience method that functions identically to
|
||||
* {@link shifty.Tweenable#tween}. You can use this to create tweens without
|
||||
* needing to set up a {@link shifty.Tweenable} instance.
|
||||
*
|
||||
* ```
|
||||
* import { tween } from 'shifty';
|
||||
*
|
||||
* tween({ from: { x: 0 }, to: { x: 10 } }).then(
|
||||
* () => console.log('All done!')
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @returns {shifty.Tweenable} A new {@link shifty.Tweenable} instance.
|
||||
*/
|
||||
export function tween(config?: tweenConfig): Tweenable;
|
||||
export function tweenProps(
|
||||
forPosition: number,
|
||||
currentState: any,
|
||||
originalState: any,
|
||||
targetState: any,
|
||||
duration: number,
|
||||
timestamp: number,
|
||||
easing: Record<any, string | Function>
|
||||
): Object;
|
||||
export function processTweens(): void;
|
||||
export function scheduleUpdate(): void;
|
||||
export function composeEasingObject(
|
||||
fromTweenParams: any,
|
||||
easing?: any | string | Function,
|
||||
composedEasing?: any
|
||||
): any | Function;
|
||||
export class Tweenable {
|
||||
/**
|
||||
* @method shifty.Tweenable.now
|
||||
* @static
|
||||
* @returns {number} The current timestamp.
|
||||
*/
|
||||
static now: () => number;
|
||||
/**
|
||||
* @param {Object} [initialState={}] The values that the initial tween should
|
||||
* start at if a `from` value is not provided to {@link
|
||||
* shifty.Tweenable#tween} or {@link shifty.Tweenable#setConfig}.
|
||||
* @param {shifty.tweenConfig} [config] Configuration object to be passed to
|
||||
* {@link shifty.Tweenable#setConfig}.
|
||||
* @constructs shifty.Tweenable
|
||||
*/
|
||||
constructor(initialState?: Object, config?: tweenConfig);
|
||||
private _config: tweenConfig;
|
||||
private _data: Object;
|
||||
private _delay: number;
|
||||
private _filters: filter[];
|
||||
private _next: any;
|
||||
private _previous: any;
|
||||
private _timestamp: number;
|
||||
private _resolve: any;
|
||||
private _reject: (reason?: any) => void;
|
||||
private _currentState: any;
|
||||
private _originalState: Object;
|
||||
private _targetState: Object;
|
||||
private _start: () => void;
|
||||
private _render: () => void;
|
||||
private _promiseCtor: PromiseConstructor;
|
||||
/**
|
||||
* Applies a filter to Tweenable instance.
|
||||
* @param {string} filterName The name of the filter to apply.
|
||||
* @private
|
||||
*/
|
||||
private _applyFilter;
|
||||
private _isPlaying: boolean;
|
||||
private _pausedAtTime: number;
|
||||
private _duration: any;
|
||||
private _scheduleId: any;
|
||||
private _easing: any;
|
||||
|
||||
/**
|
||||
* Configure and start a tween. If this {@link shifty.Tweenable}'s instance
|
||||
* is already running, then it will stop playing the old tween and
|
||||
* immediately play the new one.
|
||||
* @method shifty.Tweenable#tween
|
||||
* @param {shifty.tweenConfig} [config] Gets passed to {@link
|
||||
* shifty.Tweenable#setConfig}.
|
||||
* @return {shifty.Tweenable}
|
||||
*/
|
||||
tween(config?: tweenConfig): this;
|
||||
|
||||
/**
|
||||
* Configure a tween that will start at some point in the future. Aside from
|
||||
* `delay`, `from`, and `to`, each configuration option will automatically
|
||||
* default to the same option used in the preceding tween of this {@link
|
||||
* shifty.Tweenable} instance.
|
||||
* @method shifty.Tweenable#setConfig
|
||||
* @param {shifty.tweenConfig} [config={}]
|
||||
* @return {shifty.Tweenable}
|
||||
*/
|
||||
setConfig(config?: tweenConfig): this;
|
||||
|
||||
/**
|
||||
* Overrides any `finish` function passed via a {@link shifty.tweenConfig}.
|
||||
* @method shifty.Tweenable#then
|
||||
* @param {function} onFulfilled Receives {@link shifty.promisedData} as the
|
||||
* first parameter.
|
||||
* @param {function} onRejected Receives {@link shifty.promisedData} as the
|
||||
* first parameter.
|
||||
* @return {external:Promise}
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
|
||||
*/
|
||||
then(onFulfilled: Function, onRejected?: Function): Promise<any>;
|
||||
|
||||
private _promise: Promise<any>;
|
||||
|
||||
/**
|
||||
* @method shifty.Tweenable#catch
|
||||
* @param {function} onRejected Receives {@link shifty.promisedData} as the
|
||||
* first parameter.
|
||||
* @return {external:Promise}
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
|
||||
*/
|
||||
catch(onRejected: Function): Promise<any>;
|
||||
|
||||
/**
|
||||
* @method shifty.Tweenable#get
|
||||
* @return {Object} The current state.
|
||||
*/
|
||||
get(): Object;
|
||||
|
||||
/**
|
||||
* Set the current state.
|
||||
* @method shifty.Tweenable#set
|
||||
* @param {Object} state The state to set.
|
||||
*/
|
||||
set(state: Object): void;
|
||||
|
||||
/**
|
||||
* Pause a tween. Paused tweens can be resumed from the point at which they
|
||||
* were paused. If a tween is not running, this is a no-op.
|
||||
* @method shifty.Tweenable#pause
|
||||
* @return {shifty.Tweenable}
|
||||
*/
|
||||
pause(): this;
|
||||
|
||||
/**
|
||||
* Resume a paused tween.
|
||||
* @method shifty.Tweenable#resume
|
||||
* @return {shifty.Tweenable}
|
||||
*/
|
||||
resume(): this;
|
||||
|
||||
private _resume(currentTime?: number): any;
|
||||
|
||||
/**
|
||||
* Move the state of the animation to a specific point in the tween's
|
||||
* timeline. If the animation is not running, this will cause {@link
|
||||
* shifty.renderFunction} handlers to be called.
|
||||
* @method shifty.Tweenable#seek
|
||||
* @param {millisecond} millisecond The millisecond of the animation to seek
|
||||
* to. This must not be less than `0`.
|
||||
* @return {shifty.Tweenable}
|
||||
*/
|
||||
seek(millisecond: number): this;
|
||||
|
||||
/**
|
||||
* Stops a tween. If a tween is not running, this is a no-op. This method
|
||||
* does not cancel the tween {@link external:Promise}. For that, use {@link
|
||||
* shifty.Tweenable#cancel}.
|
||||
* @param {boolean} [gotoEnd] If `false`, the tween just stops at its current
|
||||
* state. If `true`, the tweened object's values are instantly set to the
|
||||
* target values.
|
||||
* @method shifty.Tweenable#stop
|
||||
* @return {shifty.Tweenable}
|
||||
*/
|
||||
stop(gotoEnd?: boolean): this;
|
||||
|
||||
/**
|
||||
* {@link shifty.Tweenable#stop}s a tween and also `reject`s its {@link
|
||||
* external:Promise}. If a tween is not running, this is a no-op. Prevents
|
||||
* calling any provided `finish` function.
|
||||
* @param {boolean} [gotoEnd] Is propagated to {@link shifty.Tweenable#stop}.
|
||||
* @method shifty.Tweenable#cancel
|
||||
* @return {shifty.Tweenable}
|
||||
* @see https://github.com/jeremyckahn/shifty/issues/122
|
||||
*/
|
||||
cancel(gotoEnd?: boolean): this;
|
||||
|
||||
/**
|
||||
* Whether or not a tween is running.
|
||||
* @method shifty.Tweenable#isPlaying
|
||||
* @return {boolean}
|
||||
*/
|
||||
isPlaying(): boolean;
|
||||
|
||||
/**
|
||||
* @method shifty.Tweenable#setScheduleFunction
|
||||
* @param {shifty.scheduleFunction} scheduleFunction
|
||||
* @deprecated Will be removed in favor of {@link shifty.Tweenable.setScheduleFunction} in 3.0.
|
||||
*/
|
||||
setScheduleFunction(scheduleFunction: scheduleFunction): void;
|
||||
|
||||
/**
|
||||
* Get and optionally set the data that gets passed as `data` to {@link
|
||||
* shifty.promisedData}, {@link shifty.startFunction} and {@link
|
||||
* shifty.renderFunction}.
|
||||
* @param {Object} [data]
|
||||
* @method shifty.Tweenable#data
|
||||
* @return {Object} The internally stored `data`.
|
||||
*/
|
||||
data(data?: any): any;
|
||||
|
||||
/**
|
||||
* `delete` all "own" properties. Call this when the {@link
|
||||
* shifty.Tweenable} instance is no longer needed to free memory.
|
||||
* @method shifty.Tweenable#dispose
|
||||
*/
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export namespace Tweenable {
|
||||
/**
|
||||
* Set a custom schedule function.
|
||||
*
|
||||
* By default,
|
||||
* [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window.requestAnimationFrame)
|
||||
* is used if available, otherwise
|
||||
* [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/Window.setTimeout)
|
||||
* is used.
|
||||
* @method shifty.Tweenable.setScheduleFunction
|
||||
* @param {shifty.scheduleFunction} fn The function to be
|
||||
* used to schedule the next frame to be rendered.
|
||||
* @return {shifty.scheduleFunction} The function that was set.
|
||||
*/
|
||||
export function setScheduleFunction(fn: scheduleFunction): scheduleFunction;
|
||||
export const filters: any;
|
||||
}
|
||||
|
||||
// token.js
|
||||
|
||||
export function tweenCreated(tweenable: any): void;
|
||||
export function beforeTween(tweenable: any): void;
|
||||
export function afterTween(tweenable: any): void;
|
||||
export function doesApply(tweenable: any): boolean;
|
||||
}
|
19
Extensions/TweenBehavior/shifty.js
vendored
19
Extensions/TweenBehavior/shifty.js
vendored
File diff suppressed because one or more lines are too long
16
Extensions/TweenBehavior/shifty.js.LICENSE.txt
Normal file
16
Extensions/TweenBehavior/shifty.js.LICENSE.txt
Normal file
@@ -0,0 +1,16 @@
|
||||
/*!
|
||||
* TERMS OF USE - EASING EQUATIONS
|
||||
* Open source under the BSD License.
|
||||
* Easing Equations (c) 2003 Robert Penner, all rights reserved.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* All equations are adapted from Thomas Fuchs'
|
||||
* [Scripty2](https://github.com/madrobby/scripty2/blob/master/src/effects/transitions/penner.js).
|
||||
*
|
||||
* Based on Easing Equations (c) 2003 [Robert
|
||||
* Penner](http://www.robertpenner.com/), all rights reserved. This work is
|
||||
* [subject to terms](http://www.robertpenner.com/easing_terms_of_use.html).
|
||||
*/
|
||||
|
||||
/*! Shifty 2.16.0 - https://github.com/jeremyckahn/shifty */
|
1
Extensions/TweenBehavior/shifty.js.map
Normal file
1
Extensions/TweenBehavior/shifty.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,6 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Video object PIXI renderer');
|
||||
|
||||
import PIXI = GlobalPIXIModule.PIXI;
|
||||
|
||||
/**
|
||||
@@ -160,7 +162,7 @@ namespace gdjs {
|
||||
.then(() => {})
|
||||
.catch(() => {
|
||||
// Autoplay was prevented.
|
||||
console.warn(
|
||||
logger.warn(
|
||||
'The video did not start because: video is invalid or no interaction with the game has been captured before (this is blocked by the navigator: https://goo.gl/xX8pDD)'
|
||||
);
|
||||
});
|
||||
|
@@ -54,6 +54,12 @@ BaseObjectExtension::BaseObjectExtension() {
|
||||
objectConditions["Angle"]
|
||||
.SetFunctionName("getAngle")
|
||||
.SetIncludeFile("runtimeobject.js");
|
||||
objectConditions["BoundingBoxLeft"].SetFunctionName("getAABBLeft");
|
||||
objectConditions["BoundingBoxTop"].SetFunctionName("getAABBTop");
|
||||
objectConditions["BoundingBoxRight"].SetFunctionName("getAABBRight");
|
||||
objectConditions["BoundingBoxBottom"].SetFunctionName("getAABBBottom");
|
||||
objectConditions["BoundingBoxCenterX"].SetFunctionName("getAABBCenterX");
|
||||
objectConditions["BoundingBoxCenterY"].SetFunctionName("getAABBCenterY");
|
||||
objectActions["Rotate"].SetFunctionName("rotate").SetIncludeFile(
|
||||
"runtimeobject.js");
|
||||
objectActions["RotateTowardAngle"]
|
||||
@@ -193,6 +199,12 @@ BaseObjectExtension::BaseObjectExtension() {
|
||||
objectExpressions["Y"].SetFunctionName("getY");
|
||||
objectExpressions["CenterX"].SetFunctionName("getCenterXInScene");
|
||||
objectExpressions["CenterY"].SetFunctionName("getCenterYInScene");
|
||||
objectExpressions["BoundingBoxLeft"].SetFunctionName("getAABBLeft");
|
||||
objectExpressions["BoundingBoxTop"].SetFunctionName("getAABBTop");
|
||||
objectExpressions["BoundingBoxRight"].SetFunctionName("getAABBRight");
|
||||
objectExpressions["BoundingBoxBottom"].SetFunctionName("getAABBBottom");
|
||||
objectExpressions["BoundingBoxCenterX"].SetFunctionName("getAABBCenterX");
|
||||
objectExpressions["BoundingBoxCenterY"].SetFunctionName("getAABBCenterY");
|
||||
objectExpressions["ZOrder"].SetFunctionName("getZOrder");
|
||||
objectExpressions["Plan"].SetFunctionName("getZOrder"); // Deprecated
|
||||
objectExpressions["Width"].SetFunctionName("getWidth");
|
||||
|
@@ -27,6 +27,10 @@ MouseExtension::MouseExtension() {
|
||||
"gdjs.evtTools.input.isMouseButtonPressed"); // Deprecated
|
||||
GetAllConditions()["MouseButtonReleased"].SetFunctionName(
|
||||
"gdjs.evtTools.input.isMouseButtonReleased");
|
||||
GetAllConditions()["MouseButtonFromTextPressed"].SetFunctionName(
|
||||
"gdjs.evtTools.input.isMouseButtonPressed");
|
||||
GetAllConditions()["MouseButtonFromTextReleased"].SetFunctionName(
|
||||
"gdjs.evtTools.input.isMouseButtonReleased");
|
||||
GetAllActions()["CacheSouris"].SetFunctionName(
|
||||
"gdjs.evtTools.input.hideCursor");
|
||||
GetAllActions()["MontreSouris"].SetFunctionName(
|
||||
|
@@ -82,7 +82,8 @@ bool Exporter::ExportWholePixiProject(
|
||||
// Export engine libraries
|
||||
helper.AddLibsInclude(
|
||||
/*pixiRenderers=*/true,
|
||||
/*websocketDebuggerClient=*/false,
|
||||
/*includeWebsocketDebuggerClient=*/false,
|
||||
/*includeWindowMessageDebuggerClient=*/false,
|
||||
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
|
||||
@@ -119,7 +120,7 @@ bool Exporter::ExportWholePixiProject(
|
||||
helper.ExportProjectData(
|
||||
fs, exportedProject, codeOutputDir + "/data.js", noRuntimeGameOptions);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
|
||||
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
|
||||
|
||||
gd::String source = gdjsRoot + "/Runtime/index.html";
|
||||
|
@@ -101,7 +101,10 @@ bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
|
||||
// Export engine libraries
|
||||
AddLibsInclude(/*pixiRenderers=*/true,
|
||||
/*websocketDebuggerClient=*/true,
|
||||
/*includeWebsocketDebuggerClient=*/
|
||||
!options.websocketDebuggerServerAddress.empty(),
|
||||
/*includeWindowMessageDebuggerClient=*/
|
||||
options.useWindowMessageDebuggerClient,
|
||||
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
|
||||
@@ -147,10 +150,10 @@ bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
}
|
||||
runtimeGameOptions.AddChild("projectDataOnlyExport")
|
||||
.SetBoolValue(options.projectDataOnlyExport);
|
||||
runtimeGameOptions.AddChild("debuggerServerAddress")
|
||||
.SetStringValue(options.debuggerServerAddress);
|
||||
runtimeGameOptions.AddChild("debuggerServerPort")
|
||||
.SetStringValue(options.debuggerServerPort);
|
||||
runtimeGameOptions.AddChild("websocketDebuggerServerAddress")
|
||||
.SetStringValue(options.websocketDebuggerServerAddress);
|
||||
runtimeGameOptions.AddChild("websocketDebuggerServerPort")
|
||||
.SetStringValue(options.websocketDebuggerServerPort);
|
||||
|
||||
// Pass in the options the list of scripts files - useful for hot-reloading.
|
||||
auto &scriptFilesElement = runtimeGameOptions.AddChild("scriptFiles");
|
||||
@@ -521,12 +524,14 @@ bool ExporterHelper::CompleteIndexFile(
|
||||
}
|
||||
|
||||
void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
bool websocketDebuggerClient,
|
||||
bool includeWebsocketDebuggerClient,
|
||||
bool includeWindowMessageDebuggerClient,
|
||||
gd::String gdevelopLogoStyle,
|
||||
std::vector<gd::String> &includesFiles) {
|
||||
// First, do not forget common includes (they must be included before events
|
||||
// generated code files).
|
||||
InsertUnique(includesFiles, "libs/jshashtable.js");
|
||||
InsertUnique(includesFiles, "logger.js");
|
||||
InsertUnique(includesFiles, "gd.js");
|
||||
InsertUnique(includesFiles, "libs/rbush.js");
|
||||
InsertUnique(includesFiles, "inputmanager.js");
|
||||
@@ -570,10 +575,16 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
InsertUnique(includesFiles, "splash/gd-logo-light.js");
|
||||
}
|
||||
|
||||
if (websocketDebuggerClient) {
|
||||
InsertUnique(includesFiles, "websocket-debugger-client/hot-reloader.js");
|
||||
if (includeWebsocketDebuggerClient || includeWindowMessageDebuggerClient) {
|
||||
InsertUnique(includesFiles, "debugger-client/hot-reloader.js");
|
||||
InsertUnique(includesFiles, "debugger-client/abstract-debugger-client.js");
|
||||
}
|
||||
if (includeWebsocketDebuggerClient) {
|
||||
InsertUnique(includesFiles, "debugger-client/websocket-debugger-client.js");
|
||||
}
|
||||
if (includeWindowMessageDebuggerClient) {
|
||||
InsertUnique(includesFiles,
|
||||
"websocket-debugger-client/websocket-debugger-client.js");
|
||||
"debugger-client/window-message-debugger-client.js");
|
||||
}
|
||||
|
||||
if (pixiRenderers) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user